diff a/.jcheck/conf b/.jcheck/conf
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,12 +1,12 @@
 [general]
-project=jdk
+project=babylon
 jbs=JDK
 version=23
 
 [checks]
-error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists
+error=author,committer,merge,executable,symlink,message,hg-tag,whitespace,problemlists
 
 [repository]
 tags=(?:jdk-(?:[1-9]([0-9]*)(?:\.(?:0|[1-9][0-9]*)){0,4})(?:\+(?:(?:[0-9]+))|(?:-ga)))|(?:jdk[4-9](?:u\d{1,3})?-(?:(?:b\d{2,3})|(?:ga)))|(?:hs\d\d(?:\.\d{1,2})?-b\d\d)
 branches=
 
diff a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -8,5 +8,39 @@
 - [doc/building.md](doc/building.md) (markdown version)
 
 See <https://openjdk.org/> for more information about the OpenJDK
 Community and the JDK and see <https://bugs.openjdk.org> for JDK issue
 tracking.
+
+## Particulars related to Babylon
+
+The Babylon JDK builds like any other JDK, see the build instructions above.
+
+### Code shared between `java.base` and `jdk.compiler` modules
+
+A subset of code in `java.base` is copied with package renaming into
+the `jdk.compiler` module. This is the set of code required to build
+and serialize code models. Due to bootstrapping constraints, compiling
+the compiler it cannot depend on all code in `java.base`. In the future
+we may come up with a better solution. For now the build has been modified
+to copy the code, which leverages the script `cr-util/copy-to-compiler.sh`.
+
+### Testing
+
+Specific compiler tests can be executed using `jtreg`, for example:
+
+```
+jtreg -jdk:./build/macosx-x86_64-server-release/jdk/ -ea -esa -avm -va test/langtools/tools/javac/reflect/
+```
+
+Specific runtime tests can be executed using `jtreg`, for example:
+
+```
+jtreg -jdk:./build/macosx-x86_64-server-release/jdk/ -ea -esa -avm -va test/jdk/java/lang/reflect/code/
+```
+
+In addition, the runtime tests can be executed using make with the test group
+`jdk_lang_reflect_code` as follows:
+
+```
+make test TEST=jdk_lang_reflect_code
+```
diff a/cr-examples/spirv/README.md b/cr-examples/spirv/README.md
--- /dev/null
+++ b/cr-examples/spirv/README.md
@@ -0,0 +1,13 @@
+Example code showing use of code reflection in translation from Java to
+the SPIR-V intermediate language.  For more information on SPIR-V see:
+https://www.khronos.org/spir.
+
+
+Dependencies:
+
+This code depends on the Beehive SPIR-V Toolkit (https://github.com/beehive-lab/beehive-spirv-toolkit)
+which must be cloned and installed locally. For example:
+
+git clone https://github.com/beehive-lab/beehive-spirv-toolkit
+cd beehive-spirv-toolkit
+mvn clean install
diff a/cr-examples/spirv/pom.xml b/cr-examples/spirv/pom.xml
--- /dev/null
+++ b/cr-examples/spirv/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2024 Intel Corporation. All rights reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation.  Oracle designates this
+particular file as subject to the "Classpath" exception as provided
+by Oracle in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+or visit www.oracle.com if you need additional information or have any
+questions.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>intel.code</groupId>
+    <artifactId>spirv</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>23</maven.compiler.source>
+        <maven.compiler.target>23</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.10.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+             <groupId>beehive-lab</groupId>
+             <artifactId>beehive-spirv-lib</artifactId>
+             <version>0.0.3</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <compilerArgs>
+                        <arg>--enable-preview</arg>
+                        <arg>--add-modules=jdk.incubator.vector</arg>
+                    </compilerArgs>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                    <showDeprecation>true</showDeprecation>
+                    <failOnError>true</failOnError>
+                    <showWarnings>true</showWarnings>
+                    <showDeprecation>true</showDeprecation>
+                </configuration>
+            </plugin>
+             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.1.2</version>
+                <configuration>
+                    <argLine>--enable-preview</argLine>
+                    <argLine>--add-modules=jdk.incubator.vector</argLine>
+                    <redirectTestOutputToFile>false</redirectTestOutputToFile>
+                </configuration>
+            </plugin>
+            <plugin>
+              <groupId>org.codehaus.mojo</groupId>
+              <artifactId>exec-maven-plugin</artifactId>
+              <version>1.4.0</version>
+              <configuration>
+                <mainClass>intel.code.spirv.ExampleUseTest</mainClass>
+                <configuration>
+                    <argLine>--enable-preview</argLine>
+                    <argLine>--add-modules=jdk.incubator.vector</argLine>
+                </configuration>
+             </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/PointerType.java b/cr-examples/spirv/src/main/java/intel/code/spirv/PointerType.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/PointerType.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.Objects;
+import java.util.List;
+
+public final class PointerType extends SpirvType {
+    static final String NAME = "spirv.pointer";
+    private final TypeElement referentType;
+    private final TypeElement storageType;
+
+    public PointerType(TypeElement referentType, TypeElement storageType)
+    {
+        this.referentType = referentType;
+        this.storageType = storageType;
+    }
+
+    public TypeElement referentType()
+    {
+        return referentType;
+    }
+
+    public TypeElement storageType()
+    {
+        return storageType;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == null || obj.getClass() != PointerType.class) return false;
+        PointerType pt = (PointerType)obj;
+        return pt.referentType().equals(referentType) && pt.storageType.equals(storageType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(referentType, storageType);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME, List.of(referentType.externalize(), storageType.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvModuleGenerator.java b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvModuleGenerator.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvModuleGenerator.java
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.function.Function;
+import java.io.IOException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.math.BigInteger;
+import jdk.incubator.vector.VectorSpecies;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.Vector;
+import jdk.incubator.vector.IntVector;
+import jdk.incubator.vector.FloatVector;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.JavaType;
+import uk.ac.manchester.beehivespirvtoolkit.lib.SPIRVHeader;
+import uk.ac.manchester.beehivespirvtoolkit.lib.SPIRVModule;
+import uk.ac.manchester.beehivespirvtoolkit.lib.SPIRVFunction;
+import uk.ac.manchester.beehivespirvtoolkit.lib.SPIRVBlock;
+import uk.ac.manchester.beehivespirvtoolkit.lib.instructions.*;
+import uk.ac.manchester.beehivespirvtoolkit.lib.instructions.operands.*;
+import uk.ac.manchester.beehivespirvtoolkit.lib.disassembler.Disassembler;
+import uk.ac.manchester.beehivespirvtoolkit.lib.disassembler.SPIRVDisassemblerOptions;
+import uk.ac.manchester.beehivespirvtoolkit.lib.disassembler.SPVByteStreamReader;
+
+public class SpirvModuleGenerator {
+    public static MemorySegment generateModule(String moduleName, CoreOp.FuncOp func) {
+        SpirvOps.FuncOp spirvFunc = TranslateToSpirvModel.translateFunction(func);
+        MemorySegment module = SpirvModuleGenerator.generateModule(moduleName, spirvFunc);
+        return module;
+    }
+
+    public static MemorySegment generateModule(String moduleName, SpirvOps.FuncOp func) {
+        return new SpirvModuleGenerator().generateModuleInternal(moduleName, func);
+    }
+
+    public static void writeModuleToFile(MemorySegment module, String filepath)  {
+        ByteBuffer buffer = module.asByteBuffer();
+        File out = new File(filepath);
+        try (FileChannel channel = new FileOutputStream(out, false).getChannel()) {
+            channel.write(buffer);
+        }
+        catch (IOException e)  {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String disassembleModule(MemorySegment module) {
+        SPVByteStreamReader input = new SPVByteStreamReader(new ByteArrayInputStream(module.toArray(ValueLayout.JAVA_BYTE)));
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (PrintStream ps = new PrintStream(out))  {
+            SPIRVDisassemblerOptions options = new SPIRVDisassemblerOptions(false, false, false, false, true);
+            Disassembler dis = new Disassembler(input, ps, options);
+            dis.run();
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return new String(out.toByteArray());
+    }
+
+    private record SpirvResult(SPIRVId type, SPIRVId address, SPIRVId value) {}
+
+    private final SPIRVModule module;
+    private final Symbols symbols;
+
+    private SpirvModuleGenerator() {
+        this.module = new SPIRVModule(new SPIRVHeader(1, 2, 32, 0, 0));
+        this.symbols = new Symbols();
+    }
+
+    private MemorySegment generateModuleInternal(String moduleName, SpirvOps.FuncOp func) {
+        initModule();
+        generateFunction(moduleName, moduleName, func);
+        ByteBuffer buffer = ByteBuffer.allocateDirect(module.getByteCount());
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        module.close().write(buffer);
+        buffer.flip();
+        return MemorySegment.ofBuffer(buffer);
+    }
+
+    private void generateFunction(String moduleName, String fnName, SpirvOps.FuncOp func) {
+        TypeElement returnType = func.invokableType().returnType();
+        SPIRVId functionID = nextId(fnName);
+        String signature = func.invokableType().returnType().toString();
+        List<TypeElement> paramTypes = func.invokableType().parameterTypes();
+        // build signature string
+        for (int i = 0; i < paramTypes.size(); i++) {
+            signature += "_" + paramTypes.get(i).toString();
+        }
+        // declare function type if not already present
+        SPIRVId functionSig = getIdOrNull(signature);
+        if (functionSig == null) {
+            SPIRVId[] typeIdsArray = new SPIRVId[paramTypes.size()];
+            for (int i = 0; i < paramTypes.size(); i++) {
+                typeIdsArray[i] = spirvType(paramTypes.get(i).toString());
+            }
+            functionSig = nextId(fnName + "Signature");
+            module.add(new SPIRVOpTypeFunction(functionSig, spirvType(returnType.toString()), new SPIRVMultipleOperands<>(typeIdsArray)));
+            addId(signature, functionSig);
+        }
+        // declare function as modeule entry point
+        SPIRVId spirvReturnType = spirvType(returnType.toString());
+        SPIRVFunction function = (SPIRVFunction)module.add(new SPIRVOpFunction(spirvReturnType, functionID, SPIRVFunctionControl.DontInline(), functionSig));
+        SPIRVOpLabel entryPoint = new SPIRVOpLabel(nextId());
+        SPIRVBlock entryBlock = (SPIRVBlock)function.add(entryPoint);
+        SPIRVMultipleOperands<SPIRVId> operands = new SPIRVMultipleOperands<>(getId("globalInvocationId"), getId("globalSize"), getId("subgroupSize"), getId("subgroupId"));
+        module.add(new SPIRVOpEntryPoint(SPIRVExecutionModel.Kernel(), functionID, new SPIRVLiteralString(fnName), operands));
+
+        translateBody(func.body(), function, entryBlock);
+        function.add(new SPIRVOpFunctionEnd());
+    }
+
+    private void translateBody(Body body, SPIRVFunction function, SPIRVBlock entryBlock) {
+        int labelNumber = 0;
+        SPIRVBlock spirvBlock = entryBlock;
+        for (int bi = 1; bi < body.blocks().size(); bi++)  {
+            Block block = body.blocks().get(bi);
+            String blockName = String.valueOf(block.hashCode());
+            SPIRVOpLabel blockLabel = new SPIRVOpLabel(nextId());
+            SPIRVBlock newBlock = (SPIRVBlock)function.add(blockLabel);
+            symbols.putBlock(block, newBlock);
+            symbols.putLabel(block, blockLabel);
+        }
+        for (Value param : body.entryBlock().parameters()) {
+            SPIRVId paramId = nextId();
+            addResult(param, new SpirvResult(spirvType(param.type().toString()), null, paramId));
+        }
+        for (int bi = 0; bi < body.blocks().size(); bi++)  {
+            Block block = body.blocks().get(bi);
+            if (bi > 0) {
+                spirvBlock = symbols.getBlock(block);
+            }
+            List<Op> ops = block.ops();
+            for (Op op : block.ops()) {
+                // debug("---------- spirv op = %s", op.toText());
+                switch (op)  {
+                    case SpirvOps.VariableOp vop -> {
+                        String typeName = vop.varType().toString();
+                        SPIRVId type = spirvType(typeName);
+                        SPIRVId varType = spirvVariableType(type);
+                        SPIRVId var = nextId(vop.varName());
+                        spirvBlock.add(new SPIRVOpVariable(varType, var, SPIRVStorageClass.Function(), new SPIRVOptionalOperand<>()));
+                        addResult(vop.result(), new SpirvResult(varType, var, null));
+                    }
+                    case SpirvOps.FunctionParameterOp fpo -> {
+                        SPIRVId result = nextId();
+                        SPIRVId type = spirvType(fpo.resultType().toString());
+                        function.add(new SPIRVOpFunctionParameter(type, result));
+                        addResult(fpo.result(), new SpirvResult(type, null, result));
+                    }
+                    case SpirvOps.LoadOp lo -> {
+                        if (((JavaType)lo.resultType()).equals(JavaType.type(VectorSpecies.class))) {
+                            addResult(lo.result(), new SpirvResult(getType("int"), null, getConst("int_EIGHT")));
+                        }
+                        else {
+                            SPIRVId type = spirvType(lo.resultType().toString());
+                            SpirvResult toLoad = getResult(lo.operands().get(0));
+                            SPIRVId varAddr = toLoad.address();
+                            SPIRVId result = nextId();
+                            spirvBlock.add(new SPIRVOpLoad(type, result, varAddr, align(type.getName())));
+                            addResult(lo.result(), new SpirvResult(type, varAddr, result));
+                        }
+                    }
+                    case SpirvOps.StoreOp so -> {
+                        SpirvResult var = getResult(so.operands().get(0));
+                        SPIRVId varAddr = var.address();
+                        SPIRVId value = getResult(so.operands().get(1)).value();
+                        spirvBlock.add(new SPIRVOpStore(varAddr, value, align(var.type().getName())));
+                    }
+                    case SpirvOps.IAddOp _, SpirvOps.FAddOp _ -> {
+                        SPIRVId intType = getType("int");
+                        SPIRVId longType = getType("long");
+                        SPIRVId floatType = getType("float");
+                        SPIRVId lhs = getResult(op.operands().get(0)).value();
+                        SPIRVId rhs = getResult(op.operands().get(1)).value();
+                        SPIRVId lhsType = spirvType(op.resultType().toString());
+                        SPIRVId ans = nextId();
+                        if (lhsType == intType) spirvBlock.add(new SPIRVOpIAdd(intType, ans, lhs, rhs));
+                        else if (lhsType == longType) spirvBlock.add(new SPIRVOpIAdd(longType, ans, lhs, rhs));
+                        else if (lhsType == floatType) spirvBlock.add(new SPIRVOpFAdd(floatType, ans, lhs, rhs));
+                        else unsupported("type", lhsType.getName());
+                        addResult(op.result(), new SpirvResult(lhsType, null, ans));
+                    }
+                    case SpirvOps.IMulOp _, SpirvOps.FMulOp _, SpirvOps.IDivOp _, SpirvOps.FDivOp _ -> {
+                        SPIRVId intType = getType("int");
+                        SPIRVId longType = getType("long");
+                        SPIRVId floatType = getType("float");
+                        SPIRVId lhs = getResult(op.operands().get(0)).value();
+                        SPIRVId rhs = getResult(op.operands().get(1)).value();
+                        SPIRVId lhsType = spirvType(op.resultType().toString());
+                        SPIRVId rhsType = getResult(op.operands().get(1)).type();
+                        SPIRVId ans = nextId();
+                        if (lhsType == intType) {
+                            if (op instanceof SpirvOps.IMulOp) spirvBlock.add(new SPIRVOpIMul(intType, ans, lhs, rhs));
+                            else if (op instanceof SpirvOps.IDivOp) spirvBlock.add(new SPIRVOpSDiv(intType, ans, lhs, rhs));
+                        }
+                        else if (lhsType == longType) {
+                            SPIRVId rhsId = rhsType == intType ? nextId() : rhs;
+                            if (rhsType == intType) spirvBlock.add(new SPIRVOpSConvert(longType, rhsId, rhs));
+                            if (op instanceof SpirvOps.IMulOp) spirvBlock.add(new SPIRVOpIMul(longType, ans, lhs, rhsId));
+                            else if (op instanceof SpirvOps.IDivOp) spirvBlock.add(new SPIRVOpSDiv(longType, ans, lhs, rhs));
+                        }
+                        else if (lhsType == floatType) {
+                            if (op instanceof SpirvOps.FMulOp) spirvBlock.add(new SPIRVOpFMul(floatType, ans, lhs, rhs));
+                            else if (op instanceof SpirvOps.FDivOp) spirvBlock.add(new SPIRVOpFDiv(floatType, ans, lhs, rhs));
+                        }
+                        else unsupported("type", lhsType);
+                        addResult(op.result(), new SpirvResult(lhsType, null, ans));
+                    }
+                    case SpirvOps.ModOp mop -> {
+                        SPIRVId type = getType(mop.operands().get(0).type().toString());
+                        SPIRVId lhs = getResult(mop.operands().get(0)).value();
+                        SPIRVId rhs = getResult(mop.operands().get(1)).value();
+                        SPIRVId result = nextId();
+                        spirvBlock.add(new SPIRVOpUMod(type, result, lhs, rhs));
+                        addResult(mop.result(), new SpirvResult(type, null, result));
+                    }
+                    case SpirvOps.IEqualOp eqop -> {
+                        SPIRVId boolType = getType("bool");
+                        SPIRVId intType = getType("int");
+                        SPIRVId longType = getType("long");
+                        SPIRVId floatType = getType("float");
+                        SPIRVId lhs = getResult(op.operands().get(0)).value();
+                        SPIRVId rhs = getResult(op.operands().get(1)).value();
+                        SPIRVId lhsType = spirvType(op.resultType().toString());
+                        SPIRVId ans = nextId();
+                        if (lhsType == intType) spirvBlock.add(new SPIRVOpIEqual(boolType, ans, lhs, rhs));
+                        else if (lhsType == longType) spirvBlock.add(new SPIRVOpIEqual(boolType, ans, lhs, rhs));
+                        else unsupported("type", lhsType.getName());
+                        addResult(op.result(), new SpirvResult(lhsType, null, ans));
+                    }
+                    case SpirvOps.CallOp call -> {
+                        if (call.callDescriptor().equals(MethodRef.ofString("spirvdemo.IntArray::get(long)int")) ||
+                            call.callDescriptor().equals(MethodRef.ofString("spirvdemo.FloatArray::get(long)float"))) {
+                            SPIRVId longType = getType("long");
+                            String arrayTypeName = call.operands().get(0).type().toString();
+                            SpirvResult arrayResult = getResult(call.operands().get(0));
+                            SPIRVId arrayAddr = arrayResult.address();
+                            SPIRVId arrayType = spirvType(arrayTypeName);
+                            SPIRVId elementType = spirvElementType(arrayTypeName);
+                            int nIndexes = call.operands().size() - 1;
+                            SPIRVId index = getResult(call.operands().get(1)).value();
+                            SPIRVId array = nextId();
+                            spirvBlock.add(new SPIRVOpLoad(arrayType, array, arrayAddr, align(arrayType.getName())));
+                            SPIRVId resultAddr = nextId();
+                            spirvBlock.add(new SPIRVOpInBoundsPtrAccessChain(arrayType, resultAddr, array, index, new SPIRVMultipleOperands<>()));
+                            SPIRVId result = nextId();
+                            spirvBlock.add(new SPIRVOpLoad(elementType, result, resultAddr, align(elementType.getName())));
+                            addResult(call.result(), new SpirvResult(elementType, resultAddr, result));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.ofString("spirvdemo.IntArray::set(long, int)void")) ||
+                                call.callDescriptor().equals(MethodRef.ofString("spirvdemo.FloatArray::set(long, float)void"))) {
+                            SPIRVId longType = getType("long");
+                            String arrayTypeName = call.operands().get(0).type().toString();
+                            SpirvResult arrayResult = getResult(call.operands().get(0));
+                            SPIRVId arrayAddr = arrayResult.address();
+                            SPIRVId arrayType = spirvType(arrayTypeName);
+                            SPIRVId elementType = spirvElementType(arrayTypeName);
+                            int nIndexes = call.operands().size() - 2;
+                            int valueIndex = nIndexes + 1;
+                            SPIRVId index = getResult(call.operands().get(1)).value();
+                            SPIRVId array = nextId();
+                            spirvBlock.add(new SPIRVOpLoad(arrayType, array, arrayAddr, align(arrayType.getName())));
+                            SPIRVId dest = nextId();
+                            spirvBlock.add(new SPIRVOpInBoundsPtrAccessChain(arrayType, dest, array, index, new SPIRVMultipleOperands<>()));
+                            SPIRVId value = getResult(call.operands().get(valueIndex)).value();
+                            spirvBlock.add(new SPIRVOpStore(dest, value, align(elementType.getName())));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "fromArray", IntVector.class, VectorSpecies.class, int[].class, int.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "fromArray", FloatVector.class, VectorSpecies.class, float[].class, int.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SpirvResult speciesResult = getResult(call.operands().get(0));
+                            SpirvResult arrayResult = getResult(call.operands().get(1));
+                            String arrayType = arrayResult.type().getName();
+                            int laneCount = 8;  //TODO: remove hard code, instruction below needs a literal
+                            String vTypeName = ((ClassType)call.callDescriptor().refType()).toClassName();
+                            SPIRVId vType = spirvVectorType(vTypeName, laneCount);
+                            SPIRVId array = arrayResult.value();
+                            SPIRVId index = getResult(call.operands().get(2)).value();
+                            SPIRVId vectorIndex = nextId();
+                            spirvBlock.add(new SPIRVOpSDiv(getType("int"), vectorIndex, index, speciesResult.value()));
+                            SPIRVId longIndex = nextId();
+                            spirvBlock.add(new SPIRVOpSConvert(getType("long"), longIndex, vectorIndex));
+                            SPIRVId vector = nextId();
+                            SPIRVMultipleOperands<SPIRVId> operands = new SPIRVMultipleOperands<>(longIndex, array, new SPIRVId(laneCount)); // TODO: lanes must be a literal
+                            spirvBlock.add(new SPIRVOpExtInst(vType, vector, oclExtension, new SPIRVLiteralExtInstInteger(171, "vloadn"), operands));
+                            addResult(call.result(), new SpirvResult(vType, null, vector));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "fromMemorySegment", IntVector.class, VectorSpecies.class, MemorySegment.class, long.class, ByteOrder.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "fromMemorySegment", FloatVector.class, VectorSpecies.class, MemorySegment.class, long.class, ByteOrder.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SPIRVId species = getResult(call.operands().get(0)).value();
+                            SPIRVId lanesLong = nextId();
+                            spirvBlock.add(new SPIRVOpSConvert(getType("long"), lanesLong, species));
+                            int laneCount = 8; //TODO: remove hard code, vloadn instruction below needs a literal lane count, get value from env
+                            SPIRVId segment = getResult(call.operands().get(1)).value();
+                            String vTypeName = ((ClassType)call.callDescriptor().refType()).toClassName();
+                            SPIRVId vType = spirvVectorType(vTypeName, laneCount);
+                            SPIRVId temp = nextId();
+                            spirvBlock.add(new SPIRVOpConvertPtrToU(getType("long"), temp, segment));
+                            SPIRVId typedSegment = nextId();
+                            SPIRVId pointerType = (SPIRVId)map(x -> x.equals(vTypeName), "jdk.incubator.vector.IntVector", "jdk.incubator.vector.FloatVector", getType("ptrInt"), getType("ptrFloat"));
+                            spirvBlock.add(new SPIRVOpConvertUToPtr(pointerType, typedSegment, temp));
+                            SPIRVId offset = getResult(call.operands().get(2)).value();
+                            SPIRVId vectorIndex = nextId();
+                            spirvBlock.add(new SPIRVOpSDiv(getType("long"), vectorIndex, offset, lanesLong)); // divide by lane count
+                            SPIRVId finalIndex = nextId();
+                            SPIRVId vector = nextId();
+                            SPIRVMultipleOperands<SPIRVId> operands = new SPIRVMultipleOperands<>(vectorIndex, typedSegment, new SPIRVId(laneCount)); // TODO: lanes must be a literal
+                            spirvBlock.add(new SPIRVOpExtInst(vType, vector, oclExtension, new SPIRVLiteralExtInstInteger(171, "vloadn"), operands));
+                            addResult(call.result(), new SpirvResult(vType, null, vector));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "intoArray", void.class, int[].class, int.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "intoArray", void.class, float[].class, int.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SpirvResult vectorResult = getResult(call.operands().get(0));
+                            SPIRVId vector = vectorResult.value();
+                            SPIRVId vectorType = vectorResult.type();
+                            SpirvResult arrayResult = getResult(call.operands().get(1));
+                            SPIRVId array = arrayResult.value();
+                            SPIRVId index = getResult(call.operands().get(2)).value();
+                            SPIRVId vectorIndex = nextId();
+                            spirvBlock.add(new SPIRVOpShiftRightArithmetic(getType("int"), vectorIndex, index, vectorExponent(vectorType.getName())));
+                            SPIRVId longIndex = nextId();
+                            spirvBlock.add(new SPIRVOpSConvert(getType("long"), longIndex, vectorIndex));
+                            SPIRVMultipleOperands<SPIRVId> operandsR = new SPIRVMultipleOperands<>(vector, longIndex, array);
+                            spirvBlock.add(new SPIRVOpExtInst(getType("void"), nextId(), oclExtension, new SPIRVLiteralExtInstInteger(172, "vstoren"), operandsR));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "intoMemorySegment", void.class, MemorySegment.class, long.class, ByteOrder.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "intoMemorySegment", void.class, MemorySegment.class, long.class, ByteOrder.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SpirvResult vectorResult = getResult(call.operands().get(0));
+                            SPIRVId vector = vectorResult.value();
+                            SPIRVId vectorType = vectorResult.type();
+                            SpirvResult segmentResult = getResult(call.operands().get(1));;
+                            SPIRVId segment = segmentResult.value();
+                            SPIRVId temp = nextId();
+                            spirvBlock.add(new SPIRVOpConvertPtrToU(getType("long"), temp, segment));
+                            SPIRVId typedSegment = nextId();
+                            String vectorElementType = vectorElementType(vectorType).getName();
+                            SPIRVId pointerType = (SPIRVId)map(x -> x.equals(vectorElementType), "int", "float", getType("ptrInt"), getType("ptrFloat"));
+                            spirvBlock.add(new SPIRVOpConvertUToPtr(pointerType, typedSegment, temp));
+                            SPIRVId offset = getResult(call.operands().get(2)).value();
+                            SPIRVId vectorIndex = nextId();
+                            int laneCount = laneCount(vectorType.getName());
+                            spirvBlock.add(new SPIRVOpShiftRightArithmetic(getType("long"), vectorIndex, offset, vectorExponent(vectorType.getName())));
+                            SPIRVMultipleOperands<SPIRVId> operandsR = new SPIRVMultipleOperands<>(vector, vectorIndex, typedSegment);
+                            spirvBlock.add(new SPIRVOpExtInst(getId("void"), nextId(), oclExtension, new SPIRVLiteralExtInstInteger(172, "vstoren"), operandsR));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "reduceLanes", int.class, VectorOperators.Associative.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "reduceLanes", float.class, VectorOperators.Associative.class))) {
+                            SpirvResult vectorResult = getResult(call.operands().get(0));
+                            SPIRVId vectorType = vectorResult.type();
+                            SPIRVId vector = vectorResult.value();
+                            String vTypeName = vectorType.getName();
+                            SPIRVId elementType = vectorElementType(vectorType);
+                            Op reduceOp = ((Op.Result)call.operands().get(1)).op();
+                            if (reduceOp instanceof SpirvOps.FieldLoadOp flo) {
+                                assert flo.fieldDescriptor().refType().equals(JavaType.type(VectorOperators.class));
+                                assert flo.fieldDescriptor().name().equals("ADD");
+                                String operation = flo.fieldDescriptor().name();
+                            }
+                            else unsupported("operation expression", reduceOp.toText());
+                            String tempTag = nextTempTag();
+                            SPIRVId temp_0 = nextId(tempTag + 0);
+                            spirvBlock.add(new SPIRVOpCompositeExtract(elementType, temp_0, vector, new SPIRVMultipleOperands<>(new SPIRVLiteralInteger(0))));
+                            for (int lane = 1; lane < laneCount(vectorType.getName()); lane++) {
+                                SPIRVId temp = nextId(tempTag + lane);
+                                SPIRVId element = nextId();
+                                spirvBlock.add(new SPIRVOpCompositeExtract(elementType, element, vector, new SPIRVMultipleOperands<>(new SPIRVLiteralInteger(lane))));
+                                if (elementType == getType("int")) {
+                                    spirvBlock.add(new SPIRVOpIAdd(elementType, temp, getId(tempTag + (lane - 1)), element));
+                                }
+                                else if (elementType == getType("float")) {
+                                    spirvBlock.add(new SPIRVOpFAdd(elementType, temp, getId(tempTag + (lane - 1)), element));
+                                }
+                                else unsupported("type", elementType.getName());
+                            }
+                            addResult(call.result(), new SpirvResult(elementType, null, getId(tempTag + (laneCount(vectorType.getName()) - 1))));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "add", IntVector.class, Vector.class))
+                              || call.callDescriptor().equals(MethodRef.method(IntVector.class, "mul", IntVector.class, Vector.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "add", FloatVector.class, Vector.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "mul", FloatVector.class, Vector.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SpirvResult lhsResult = getResult(call.operands().get(0));
+                            SPIRVId lhsType = lhsResult.type();
+                            SPIRVId lhs = lhsResult.value();
+                            SPIRVId rhs = getResult(call.operands().get(1)).value();
+                            SPIRVId add = nextId();
+                            if (call.callDescriptor().name().equals("add")) {
+                                spirvBlock.add(lhsType.getName().endsWith("int") ? new SPIRVOpIAdd(lhsType, add, lhs, rhs) : new SPIRVOpFAdd(lhsType, add, lhs, rhs));
+                            }
+                            else if (call.callDescriptor().name().equals("mul")) {
+                                spirvBlock.add(lhsType.getName().endsWith("int") ? new SPIRVOpIMul(lhsType, add, lhs, rhs) : new SPIRVOpFMul(lhsType, add, lhs, rhs));
+                            }
+                            addResult(call.result(), new SpirvResult(lhsType, null, add));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(FloatVector.class, "fma", FloatVector.class, Vector.class, Vector.class))) {
+                            SPIRVId oclExtension = getId("oclExtension");
+                            SpirvResult aResult = getResult(call.operands().get(0));
+                            SPIRVId vType = aResult.type();
+                            SPIRVId a = aResult.value();
+                            SPIRVId b = getResult(call.operands().get(1)).value();
+                            SPIRVId c = getResult(call.operands().get(2)).value();
+                            String vTypeStr = vType.getName();
+                            assert vTypeStr.endsWith("float");
+                            SPIRVId result  = nextId();
+                            SPIRVMultipleOperands<SPIRVId> operands = new SPIRVMultipleOperands<>(a, b, c);
+                            spirvBlock.add(new SPIRVOpExtInst(vType, result, oclExtension, new SPIRVLiteralExtInstInteger(26, "fma"), operands));
+                            addResult(call.result(), new SpirvResult(vType, null, result));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "zero", IntVector.class, VectorSpecies.class))
+                             || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "zero", FloatVector.class, VectorSpecies.class))) {
+                            SpirvResult speciesResult = getResult(call.operands().get(0));
+                            SPIRVId vType = spirvType(((ClassType)call.callDescriptor().refType()).toClassName());
+                            String elementType = vectorElementType(vType).getName();
+                            SPIRVId value = getId(elementType + "_ZERO");
+                            int laneCount = laneCount(vType.getName());
+                            assert laneCount == 8 || laneCount == 16;
+                            SPIRVId vector = nextId();
+                            SPIRVMultipleOperands<SPIRVId> operands = spirvOperands(value, laneCount);
+                            spirvBlock.add(new SPIRVOpCompositeConstruct(vType, vector, operands));
+                            addResult(call.result(), new SpirvResult(vType, null, vector));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(IntVector.class, "lane", int.class, int.class))
+                              || call.callDescriptor().equals(MethodRef.method(FloatVector.class, "lane", float.class, int.class)))  {
+                            SpirvResult lhsResult = getResult(call.operands().get(0));
+                            SPIRVId lhsType = lhsResult.type();
+                            SPIRVId lhs = lhsResult.value();
+                            String vTypeStr = lhsType.getName();
+                            SPIRVId vType = lhsResult.type();
+                            SPIRVId elementType = vectorElementType(vType);
+                            SPIRVId result = nextId();
+                            Op laneOp = ((Op.Result)call.operands().get(1)).op();
+                            assert laneOp instanceof SpirvOps.ConstantOp;
+                            int lane = (int)((SpirvOps.ConstantOp)laneOp).value();
+                            spirvBlock.add(new SPIRVOpCompositeExtract(elementType, result, lhsResult.value(), new SPIRVMultipleOperands<>(new SPIRVLiteralInteger(lane))));
+                            addResult(call.result(), new SpirvResult(elementType, null, result));
+                        }
+                        else if (call.callDescriptor().equals(MethodRef.method(VectorSpecies.class, "length", int.class))) {
+                            addResult(call.result(), new SpirvResult(getType("int"), null, getConst("int_EIGHT"))); // TODO: remove hardcode
+                        }
+                        else unsupported("method", call.callDescriptor());
+                    }
+                    case SpirvOps.ConstantOp cop -> {
+                        SPIRVId type = spirvType(cop.resultType().toString());
+                        SPIRVId result = nextId();
+                        Object value = cop.value();
+                        if (type == getType("int")) {
+                            module.add(new SPIRVOpConstant(type, result, new SPIRVContextDependentInt(new BigInteger(String.valueOf(value)))));
+                        }
+                        else if (type == getType("long")) {
+                            module.add(new SPIRVOpConstant(type, result, new SPIRVContextDependentLong(new BigInteger(String.valueOf(value)))));
+                        }
+                        else if (type == getType("float")) {
+                            module.add(new SPIRVOpConstant(type, result, new SPIRVContextDependentFloat((float)value)));
+                        }
+                        else unsupported("type", cop.resultType());
+                        addResult(cop.result(), new SpirvResult(type, null, result));
+                    }
+                    case SpirvOps.ConvertOp scop -> {
+                        SPIRVId toType = spirvType(scop.resultType().toString());
+                        SPIRVId to = nextId();
+                        SpirvResult valueResult = getResult(scop.operands().get(0));
+                        SPIRVId from = valueResult.value();
+                        SPIRVId fromType = valueResult.type();
+                        if (isIntegerType(fromType)) {
+                            if (isIntegerType(toType)) {
+                                spirvBlock.add(new SPIRVOpSConvert(toType, to, from));
+                            }
+                            else if (isFloatType(toType)) {
+                                spirvBlock.add(new SPIRVOpConvertSToF(toType, to, from));
+                            }
+                            else unsupported("conversion type", scop.resultType());
+                        }
+                        else unsupported("conversion type", scop.operands().get(0));
+                        addResult(scop.result(), new SpirvResult(toType, null, to));
+                    }
+                    case SpirvOps.InBoundAccessChainOp iacop -> {
+                        SPIRVId type = spirvType(iacop.resultType().toString());
+                        SPIRVId result = nextId();
+                        SPIRVId object = getResult(iacop.operands().get(0)).value();
+                        SPIRVId index = getResult(iacop.operands().get(1)).value();
+                        spirvBlock.add(new SPIRVOpInBoundsPtrAccessChain(type, result, object, index, new SPIRVMultipleOperands<>()));
+                        addResult(iacop.result(), new SpirvResult(type, result, null));
+                    }
+                    case SpirvOps.FieldLoadOp flo -> {
+                        if (flo.operands().size() > 0 && flo.operands().get(0).type().equals(JavaType.ofString("spirvdemo.GPU$Index"))) {
+                            SpirvResult result;
+                            int group = -1;
+                            int index = -1;
+                            String fieldName = flo.fieldDescriptor().name();
+                            switch(fieldName) {
+                                case "x": group = 0; index = 0; break;
+                                case "y": group = 0; index = 1; break;
+                                case "z": group = 0; index = 2; break;
+                                case "w": group = 1; index = 0; break;
+                                case "h": group = 1; index = 1; break;
+                                case "d": group = 1; index = 2; break;
+                            }
+                            switch (group) {
+                                case 0: result = globalId(index, spirvBlock); break;
+                                case 1: result = globalSize(index, spirvBlock); break;
+                                default: throw new RuntimeException("Unknown Index field: " + fieldName);
+                            }
+                            addResult(flo.result(), result);
+                        }
+                        else if (((JavaType)flo.resultType()).equals(JavaType.type(VectorSpecies.class))) {
+                            addResult(flo.result(), new SpirvResult(getType("int"), null, getConst("int_EIGHT")));
+                        }
+                        else if (flo.fieldDescriptor().refType().equals(JavaType.type(VectorOperators.class))) {
+                            // currently ignored
+                        }
+                        else if (flo.fieldDescriptor().refType().equals(JavaType.type(ByteOrder.class))) {
+                            // currently ignored
+                        }
+                        else unsupported("field load", ((ClassType)flo.fieldDescriptor().refType()).toClassName() + "." + flo.fieldDescriptor().name());
+                    }
+                    case SpirvOps.BranchOp bop -> {
+                        SPIRVId trueLabel = symbols.getLabel(bop.branch()).getResultId();
+                        spirvBlock.add(new SPIRVOpBranch(trueLabel));
+                    }
+                    case SpirvOps.ConditionalBranchOp cbop -> {
+                        SPIRVId test = getResult(cbop.operands().get(0)).value();
+                        SPIRVId trueLabel = symbols.getLabel(cbop.trueBranch()).getResultId();
+                        SPIRVId falseLabel = symbols.getLabel(cbop.falseBranch()).getResultId();
+                        spirvBlock.add(new SPIRVOpBranchConditional(test, trueLabel, falseLabel, new SPIRVMultipleOperands<SPIRVLiteralInteger>()));
+                    }
+                    case SpirvOps.LtOp ltop -> {
+                        SPIRVId lhs = getResult(ltop.operands().get(0)).value();
+                        SPIRVId rhs = getResult(ltop.operands().get(1)).value();
+                        SPIRVId boolType = getType("bool");
+                        SPIRVId result = nextId();
+                        spirvBlock.add(new SPIRVOpSLessThan(boolType, result, lhs, rhs));
+                        addResult(ltop.result(), new SpirvResult(boolType, null, result));
+                    }
+                    case SpirvOps.ReturnOp rop -> {
+                        if (rop.operands().size() == 0) {
+                            spirvBlock.add(new SPIRVOpReturn());
+                        }
+                        else {
+                            SPIRVId returnValue = getResult(rop.operands().get(0)).value();
+                            spirvBlock.add(new SPIRVOpReturnValue(returnValue));
+                        }
+                    }
+                    default -> unsupported("op", op.getClass());
+                }
+            }
+        }
+    }
+
+    private void initModule() {
+        module.add(new SPIRVOpCapability(SPIRVCapability.Addresses()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Linkage()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Kernel()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Int8()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Int16()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Int64()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Vector16()));
+        module.add(new SPIRVOpCapability(SPIRVCapability.Float16()));
+        module.add(new SPIRVOpMemoryModel(SPIRVAddressingModel.Physical64(), SPIRVMemoryModel.OpenCL()));
+
+        // OpenCL extension provides built-in variables suitable for kernel programming
+        // Import extention and declare fourn variables
+        SPIRVId oclExtension = nextId("oclExtension");
+        module.add(new SPIRVOpExtInstImport(oclExtension, new SPIRVLiteralString("OpenCL.std")));
+
+        SPIRVId globalInvocationId = nextId("globalInvocationId");
+        SPIRVId globalSize = nextId("globalSize");
+        SPIRVId subgroupSize = nextId("subgroupSize");
+        SPIRVId subgroupId = nextId("subgroupId");
+
+        module.add(new SPIRVOpDecorate(globalInvocationId, SPIRVDecoration.BuiltIn(SPIRVBuiltIn.GlobalInvocationId())));
+        module.add(new SPIRVOpDecorate(globalInvocationId, SPIRVDecoration.Constant()));
+        module.add(new SPIRVOpDecorate(globalInvocationId, SPIRVDecoration.LinkageAttributes(new SPIRVLiteralString("spirv_BuiltInGlobalInvocationId"), SPIRVLinkageType.Import())));
+        module.add(new SPIRVOpDecorate(globalSize, SPIRVDecoration.BuiltIn(SPIRVBuiltIn.GlobalSize())));
+        module.add(new SPIRVOpDecorate(globalSize, SPIRVDecoration.Constant()));
+        module.add(new SPIRVOpDecorate(globalSize, SPIRVDecoration.LinkageAttributes(new SPIRVLiteralString("spirv_BuiltInGlobalSize"), SPIRVLinkageType.Import())));
+        module.add(new SPIRVOpDecorate(subgroupSize, SPIRVDecoration.BuiltIn(SPIRVBuiltIn.SubgroupSize())));
+        module.add(new SPIRVOpDecorate(subgroupSize, SPIRVDecoration.Constant()));
+        module.add(new SPIRVOpDecorate(subgroupSize, SPIRVDecoration.LinkageAttributes(new SPIRVLiteralString("spirv_BuiltInSubgroupSize"), SPIRVLinkageType.Import())));
+        module.add(new SPIRVOpDecorate(subgroupId, SPIRVDecoration.BuiltIn(SPIRVBuiltIn.SubgroupId())));
+        module.add(new SPIRVOpDecorate(subgroupId, SPIRVDecoration.Constant()));
+        module.add(new SPIRVOpDecorate(subgroupId, SPIRVDecoration.LinkageAttributes(new SPIRVLiteralString("spirv_BuiltInSubgroupId"), SPIRVLinkageType.Import())));
+
+        module.add(new SPIRVOpVariable(getType("ptrV3long"), globalInvocationId, SPIRVStorageClass.Input(), new SPIRVOptionalOperand<>()));
+        module.add(new SPIRVOpVariable(getType("ptrV3long"), globalSize, SPIRVStorageClass.Input(), new SPIRVOptionalOperand<>()));
+        module.add(new SPIRVOpVariable(getType("ptrV3long"), subgroupSize, SPIRVStorageClass.Input(), new SPIRVOptionalOperand<>()));
+        module.add(new SPIRVOpVariable(getType("ptrV3long"), subgroupId, SPIRVStorageClass.Input(), new SPIRVOptionalOperand<>()));
+    }
+
+    private SPIRVId spirvType(String javaType) {
+        SPIRVId ans = switch(javaType) {
+            case "byte" -> getType("byte");
+            case "short" -> getType("short");
+            case "int" -> getType("int");
+            case "long" -> getType("long");
+            case "float" -> getType("float");
+            case "double" -> getType("double");
+            case "int[]" -> getType("int[]");
+            case "float[]" -> getType("float[]");
+            case "double[]" -> getType("double[]");
+            case "long[]" -> getType("long[]");
+            case "bool" -> getType("bool");
+            case "spirvdemo.IntArray" -> getType("int[]");
+            case "spirvdemo.FloatArray" -> getType("float[]");
+            case "jdk.incubator.vector.IntVector" -> spirvVectorType("IntVector", 8);
+            case "jdk.incubator.vector.FloatVector" -> spirvVectorType("FloatVector", 8);
+            case "jdk.incubator.vector.VectorSpecies<java.lang.Integer>" -> getType("int");
+            case "jdk.incubator.vector.VectorSpecies<java.lang.Long>" -> getType("long");
+            case "jdk.incubator.vector.VectorSpecies<java.lang.Float>" -> getType("int");
+            case "VectorSpecies" -> getType("int");
+            case "void" -> getType("void");
+            case "spirvdemo.GPU$Index" -> getType("ptrGPUIndex");
+            case "java.lang.foreign.MemorySegment" -> getType("ptrByte");
+            default -> null;
+        };
+        if (ans == null) unsupported("type", javaType);
+        return ans;
+    }
+
+    private SPIRVId spirvElementType(String javaType) {
+        SPIRVId ans = switch(javaType) {
+            case "byte[]" -> getType("byte");
+            case "short[]" -> getType("short");
+            case "int[]" -> getType("int");
+            case "long[]" -> getType("long");
+            case "float[]" -> getType("float");
+            case "double[]" -> getType("double");
+            case "boolean[]" -> getType("bool");
+            case "spirvdemo.IntArray" -> getType("int");
+            case "spirvdemo.FloatArray" -> getType("float");
+            case "jdk.incubator.vector.LongVector" -> getType("long");
+            case "jdk.incubator.vector.FloatVector" -> getType("float");
+            case "IntVector" -> getType("int");
+            case "LongVector" -> getType("long");
+            case "FloatVector" -> getType("float");
+            case "java.lang.foreign.MemorySegment" -> getType("byte");
+            default -> null;
+        };
+        if (ans == null) unsupported("type", javaType);
+        return ans;
+    }
+
+    private SPIRVId vectorElementType(SPIRVId type) {
+        SPIRVId ans = switch(type.getName()) {
+            case "v8int" -> getType("int");
+            case "v16int" -> getType("int");
+            case "v8long" -> getType("long");
+            case "v8float" -> getType("float");
+            case "v16float" -> getType("float");
+            default -> null;
+        };
+        if (ans == null) unsupported("type", type.getName());
+        return ans;
+    }
+
+    private SPIRVId spirvVariableType(SPIRVId spirvType) {
+        SPIRVId ans = switch(spirvType.getName()) {
+            case "byte" -> getType("ptrByte");
+            case "short" -> getType("ptrShort");
+            case "int" -> getType("ptrInt");
+            case "long" -> getType("ptrLong");
+            case "float" -> getType("ptrFloat");
+            case "double" -> getType("ptrDouble");
+            case "boolean" -> getType("ptrBool");
+            case "int[]" -> getType("ptrInt[]");
+            case "long[]" -> getType("ptrLong[]");
+            case "float[]" -> getType("ptrFloat[]");
+            case "double[]" -> getType("ptrDouble[]");
+            case "v8int" -> getType("ptrV8int");
+            case "v16int" -> getType("ptrV16int");
+            case "v8long" -> getType("ptrV8long");
+            case "v8float" -> getType("ptrV8float");
+            case "v16float" -> getType("ptrV16float");
+            case "ptrGPUIndex" -> getType("ptrPtrGPUIndex");
+            case "ptrByte" -> getType("ptrPtrByte");
+            default -> null;
+        };
+        if (ans == null) unsupported("type", spirvType.getName());
+        return ans;
+    }
+
+    private SPIRVId spirvVectorType(String javaVectorType, int vectorLength) {
+        String prefix = "v" + vectorLength;
+        String elementType = spirvElementType(javaVectorType).getName();
+        return getType(prefix + elementType);
+    }
+
+    private int alignment(String spirvType) {
+        int ans = switch(spirvType) {
+            case "byte" -> 1;
+            case "short" -> 2;
+            case "int" -> 4;
+            case "long" -> 8;
+            case "float" -> 4;
+            case "double" -> 8;
+            case "boolean" -> 1;
+            case "v8int" -> 32;
+            case "v16int" -> 64;
+            case "v8long" -> 64;
+            case "v8float" -> 32;
+            case "v16float" -> 64;
+            case "ptrGPUIndex" -> 32;
+            case "int[]" -> 8;
+            case "long[]" -> 8;
+            case "float[]" -> 8;
+            case "double[]" -> 8;
+            case "ptrByte" -> 8;
+            case "ptrInt" -> 8;
+            case "ptrInt[]" -> 8;
+            case "ptrLong" -> 8;
+            case "ptrLong[]" -> 8;
+            case "ptrFloat" -> 8;
+            case "ptrFloat[]" -> 8;
+            case "ptrV8int" -> 8;
+            case "ptrV8float" -> 8;
+            case "ptrPtrGPUIndex" -> 8;
+            default -> 0;
+        };
+        if (ans == 0) unsupported("type", spirvType);
+        return ans;
+    }
+
+    private int laneCount(String vectorType) {
+        int ans = switch(vectorType) {
+            case "v8int" -> 8;
+            case "v8long" -> 8;
+            case "v8float" -> 8;
+            case "v16int" -> 16;
+            case "v16float" -> 16;
+            default -> 0;
+        };
+        if (ans == 0) unsupported("type", vectorType);
+        return ans;
+    }
+
+    private SPIRVId vectorExponent(String vectorType) {
+        SPIRVId ans = null;
+        switch(vectorType) {
+            case "v8int" -> ans = getId("int_THREE");
+            case "v8long" -> ans = getId("int_THREE");
+            case "v8float" -> ans = getId("int_THREE");
+            case "v16int" -> ans = getId("int_FOUR");
+            case "v16float" -> ans = getId("int_FOUR");
+            default -> unsupported("type", vectorType);
+        };
+        return ans;
+    }
+
+    private Set<String> moduleTypes = new HashSet<>();
+
+    private SPIRVId getType(String name) {
+        if (!moduleTypes.contains(name)) {
+            switch (name) {
+                case "void" -> module.add(new SPIRVOpTypeVoid(nextId(name)));
+                case "bool" -> module.add(new SPIRVOpTypeBool(nextId(name)));
+                case "byte" -> module.add(new SPIRVOpTypeInt(nextId(name), new SPIRVLiteralInteger(8), new SPIRVLiteralInteger(0)));
+                case "short" -> module.add(new SPIRVOpTypeInt(nextId(name), new SPIRVLiteralInteger(16), new SPIRVLiteralInteger(0)));
+                case "int" -> module.add(new SPIRVOpTypeInt(nextId(name), new SPIRVLiteralInteger(32), new SPIRVLiteralInteger(0)));
+                case "long" -> module.add(new SPIRVOpTypeInt(nextId(name), new SPIRVLiteralInteger(64), new SPIRVLiteralInteger(0)));
+                case "float" -> module.add(new SPIRVOpTypeFloat(nextId(name), new SPIRVLiteralInteger(32)));
+                case "double" -> module.add(new SPIRVOpTypeFloat(nextId(name), new SPIRVLiteralInteger(64)));
+                case "ptrByte" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("byte")));
+                case "ptrInt" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("int")));
+                case "ptrLong" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("long")));
+                case "ptrFloat" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("float")));
+                case "short[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("short")));
+                case "int[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("int")));
+                case "long[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("long")));
+                case "float[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("float")));
+                case "double[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("double")));
+                case "boolean[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("boolean")));
+                case "ptrInt[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("int[]")));
+                case "ptrLong[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("long[]")));
+                case "ptrFloat[]" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("float[]")));
+                case "spirvdemo.GPUIndex" -> module.add(new SPIRVOpTypeStruct(nextId(name), new SPIRVMultipleOperands<>(getType("long"), getType("long"), getType("long"))));
+                case "ptrGPUIndex" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("spirvdemo.GPUIndex")));
+                case "ptrCrossGroupByte"-> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.CrossWorkgroup(), getType("byte")));
+                case "ptrPtrGPUIndex" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrGPUIndex")));
+                case "ptrPtrByte" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrByte")));
+                case "v3long" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("long"), new SPIRVLiteralInteger(3)));
+                case "v8int" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("int"), new SPIRVLiteralInteger(8)));
+                case "v8long" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("long"), new SPIRVLiteralInteger(8)));
+                case "v16int" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("int"), new SPIRVLiteralInteger(16)));
+                case "v8float" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("float"), new SPIRVLiteralInteger(8)));
+                case "v16float" -> module.add(new SPIRVOpTypeVector(nextId(name), getType("float"), new SPIRVLiteralInteger(16)));
+                case "ptrV3long" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Input(), getType("v3long")));
+                case "ptrV8long" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("v8long")));
+                case "ptrV8int" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("v8int")));
+                case "ptrV16int" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("v16int")));
+                case "ptrV8float" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("v8float")));
+                case "ptrV16float" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("v16float")));
+                case "ptrPtrV8int" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrV8int")));
+                case "ptrPtrV16int" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrV16int")));
+                case "ptrPtrV8float" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrV8float")));
+                case "ptrPtrV16float" -> module.add(new SPIRVOpTypePointer(nextId(name), SPIRVStorageClass.Function(), getType("ptrV16float")));
+                default -> unsupported("type", name);
+            }
+            moduleTypes.add(name);
+        }
+        return getId(name);
+    }
+
+    private Set<String> moduleConstants = new HashSet<>();
+
+    private SPIRVId getConst(String name) {
+        if (!moduleConstants.contains(name)) {
+            switch (name) {
+                case "int_ZERO" -> module.add(new SPIRVOpConstant(getType("int"), nextId("int_ZERO"), new SPIRVContextDependentInt(new BigInteger("0"))));
+                case "int_ONE" -> module.add(new SPIRVOpConstant(getType("int"), nextId("int_ONE"), new SPIRVContextDependentInt(new BigInteger("1"))));
+                case "int_TWO" -> module.add(new SPIRVOpConstant(getType("int"), nextId("int_TWO"), new SPIRVContextDependentInt(new BigInteger("2"))));
+                case "int_EIGHT" -> module.add(new SPIRVOpConstant(getType("int"), nextId("int_EIGHT"), new SPIRVContextDependentInt(new BigInteger("8"))));
+                default -> unsupported("constant", name);
+            }
+            moduleConstants.add(name);
+        }
+        return getId(name);
+    }
+
+    private SPIRVOptionalOperand<SPIRVMemoryAccess> align(int align) {
+        return new SPIRVOptionalOperand<>(SPIRVMemoryAccess.Aligned(new SPIRVLiteralInteger(align)));
+    }
+
+    private SPIRVOptionalOperand<SPIRVMemoryAccess> align(String type) {
+        return align(alignment(type));
+    }
+
+    private SPIRVMultipleOperands<SPIRVId> spirvOperands(SPIRVId value, int count) {
+        SPIRVId[] values = new SPIRVId[count];
+        Arrays.fill(values, value);
+        return new SPIRVMultipleOperands<>(values);
+    }
+
+    private SPIRVOptionalOperand<SPIRVMemoryAccess> none() {
+        return new SPIRVOptionalOperand<>();
+    }
+
+    private SpirvResult globalSize(int index, SPIRVBlock spirvBlock) {
+        SPIRVId longType = getType("long");
+        SPIRVId v3long = getId("v3long");
+        SPIRVId globalSizeId = getId("globalSize");
+        SPIRVId globalSizes = nextId();
+        spirvBlock.add(new SPIRVOpLoad(v3long, globalSizes, globalSizeId, align(32)));
+        SPIRVId globalSize = nextId();
+        spirvBlock.add(new SPIRVOpCompositeExtract(longType, globalSize, globalSizes, new SPIRVMultipleOperands<>(new SPIRVLiteralInteger(index))));
+        return new SpirvResult(longType, null, globalSize);
+    }
+
+    private SpirvResult globalId(int index, SPIRVBlock spirvBlock) {
+        SPIRVId longType = getType("long");
+        SPIRVId v3long = getId("v3long");
+        SPIRVId globalInvocationId = getId("globalInvocationId");
+        SPIRVId globalIds = nextId();
+        spirvBlock.add(new SPIRVOpLoad(v3long, globalIds, globalInvocationId, align(32)));
+        SPIRVId globalIndex = nextId();
+        spirvBlock.add(new SPIRVOpCompositeExtract(longType, globalIndex, globalIds, new SPIRVMultipleOperands<>(new SPIRVLiteralInteger(index))));
+        return new SpirvResult(longType, null, globalIndex);
+    }
+
+    private SPIRVId nextId() {
+        return module.getNextId();
+    }
+
+    private SPIRVId nextId(String name) {
+        SPIRVId ans = nextId();
+        ans.setName(name);
+        symbols.putId(name, ans);
+        module.add(new SPIRVOpName(ans, new SPIRVLiteralString(name)));
+        return ans;
+    }
+
+    private static int counter = 0;
+
+    private String nextTempTag() {
+        counter++;
+        return "temp_" + counter + "_";
+    }
+
+    private boolean isIntegerType(SPIRVId type) {
+        String name = type.getName();
+        return name.equals("short") || name.equals("int") || name.equals("long");
+    }
+
+    private boolean isFloatType(SPIRVId type) {
+        String name = type.getName();
+        return name.equals("float") || name.equals("double");
+    }
+
+    private boolean isVectorSpecies(String javaType) {
+        return javaType.equals("VectorSpecies");
+    }
+
+    private boolean isVectorType(String javaType) {
+        return javaType.equals("IntVector") || javaType.equals("FloatVector");
+    }
+
+    private void addId(String name, SPIRVId id) {
+        symbols.putId(name, id);
+    }
+
+    private SPIRVId getId(String name) {
+        SPIRVId ans = symbols.getId(name);
+        assert ans != null : name + " not found";
+        return ans;
+    }
+
+    private SPIRVId getIdOrNull(String name) {
+        return symbols.getId(name);
+    }
+
+    private static Object map(Function<Object, Boolean> test, Object... args) {
+        int len = args.length;
+        assert len >= 2 && len % 2 == 0;
+        int pairs = len / 2;
+        for (int i = 0; i < pairs; i++) {
+            if (test.apply(args[i])) return args[i + pairs];
+        }
+        throw new RuntimeException("No match: " + args[0]);
+    }
+
+    private void unsupported(String message, Object value) {
+        throw new RuntimeException("Unsupported " + message + ": " + value);
+    }
+
+    private void addResult(Value value, SpirvResult result) {
+        assert symbols.getResult(value) == null : "result already present";
+        symbols.putResult(value, result);
+    }
+
+    private SpirvResult getResult(Value value) {
+        return symbols.getResult(value);
+    }
+
+    private static class Symbols {
+        private final HashMap<Value, SpirvResult> results;
+        private final HashMap<String, SPIRVId> ids;
+        private final HashMap<Block, SPIRVBlock> blocks;
+        private final HashMap<Block, SPIRVOpLabel> labels;
+
+        public Symbols() {
+            this.results = new HashMap<>();
+            this.ids = new HashMap<>();
+            this.blocks = new HashMap<>();
+            this.labels = new HashMap<>();
+        }
+
+        public void putId(String name, SPIRVId id) {
+            ids.put(name, id);
+        }
+
+        public SPIRVId getId(String name) {
+            return ids.get(name);
+        }
+
+        public void putBlock(Block block, SPIRVBlock spirvBlock) {
+            blocks.put(block, spirvBlock);
+        }
+
+        public SPIRVBlock getBlock(Block block) {
+            return blocks.get(block);
+        }
+
+        public void putLabel(Block block, SPIRVOpLabel spirvLabel) {
+            labels.put(block, spirvLabel);
+        }
+
+        public SPIRVOpLabel getLabel(Block block) {
+            return labels.get(block);
+        }
+
+        public void putResult(Value value, SpirvResult result) {
+            results.put(value, result);
+        }
+
+        public SpirvResult getResult(Value value) {
+            return results.get(value);
+        }
+
+        public String toString() {
+            return String.format("results %s\n\nids %s\n\nblocks %s\nlabels %s\n", results.keySet(), ids.keySet(), blocks.keySet(), labels.keySet());
+        }
+    }
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOp.java b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOp.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOp.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.util.List;
+import java.util.Map;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+
+public abstract class SpirvOp extends Op {
+    private final TypeElement type;
+
+    SpirvOp(String opName) {
+        super(opName, List.of());
+        this.type = JavaType.VOID;
+    }
+
+    SpirvOp(String opName, TypeElement type, List<Value> operands) {
+        super(opName, operands);
+        this.type = type;
+    }
+
+    SpirvOp(String opName, TypeElement type, List<Value> operands, Map<String, Object> attributes) {
+        super(opName, operands);
+        this.type = type;
+    }
+
+    SpirvOp(SpirvOp that, CopyContext cc) {
+        super(that, cc);
+        this.type = that.type;
+    }
+
+    @Override
+    public TypeElement resultType() {
+        return type;
+    }
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOps.java b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOps.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvOps.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.util.List;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+
+public class SpirvOps {
+    private static final String NAME_PREFIX = "spirv.";
+
+    public static final class ModuleOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "module";
+
+        private final String name;
+
+        public ModuleOp(String moduleName) {
+            super(OPNAME);
+            this.name = moduleName;
+        }
+
+        public ModuleOp(ModuleOp that, CopyContext cc) {
+            super(that, cc);
+            this.name = that.name;
+        }
+
+        @Override
+        public ModuleOp transform(CopyContext cc, OpTransformer ot) {
+            return new ModuleOp(this, cc);
+        }
+    }
+
+    public static final class LoadOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "load";
+
+        public LoadOp(TypeElement resultType, List<Value> operands) {
+            super(OPNAME, resultType, operands);
+        }
+
+        public LoadOp(LoadOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LoadOp transform(CopyContext cc, OpTransformer ot) {
+            return new LoadOp(this, cc);
+        }
+    }
+
+    public static final class FieldLoadOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "fieldload";
+        private final FieldRef fieldDesc;
+
+        public FieldLoadOp(TypeElement resultType, FieldRef fieldRef, List<Value> operands) {
+            super(OPNAME, resultType, operands);
+            this.fieldDesc = fieldRef;
+        }
+
+        public FieldLoadOp(FieldLoadOp that, CopyContext cc) {
+            super(that, cc);
+            this.fieldDesc = that.fieldDesc;
+        }
+
+        @Override
+        public FieldLoadOp transform(CopyContext cc, OpTransformer ot) {
+            return new FieldLoadOp(this, cc);
+        }
+
+        public FieldRef fieldDescriptor() {
+            return fieldDesc;
+        }
+    }
+
+    public static final class StoreOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "store";
+
+        public StoreOp(Value dest, Value value) {
+            super(NAME, JavaType.VOID, List.of(dest, value));
+        }
+
+        public StoreOp(StoreOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public StoreOp transform(CopyContext cc, OpTransformer ot) {
+            return new StoreOp(this, cc);
+        }
+    }
+
+    public static final class CallOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "call";
+        private MethodRef descriptor;
+
+        public CallOp(MethodRef descriptor, List<Value> operands) {
+            super(nameString(descriptor), descriptor.type().returnType(), operands);
+            this.descriptor = descriptor;
+        }
+
+        public CallOp(CallOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public CallOp transform(CopyContext cc, OpTransformer ot) {
+            return new CallOp(this, cc);
+        }
+
+        public MethodRef callDescriptor() {
+            return descriptor;
+        }
+
+        private static String nameString(MethodRef descriptor) {
+            return OPNAME + " @" + descriptor.refType() + "::" + descriptor.name();
+        }
+    }
+
+    public static final class ArrayLengthOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "arraylength";
+
+        public ArrayLengthOp(TypeElement resultType, List<Value> operands) {
+            super(NAME, resultType, operands);
+        }
+
+        public ArrayLengthOp(ArrayLengthOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ArrayLengthOp transform(CopyContext cc, OpTransformer ot) {
+            return new ArrayLengthOp(this, cc);
+        }
+    }
+
+    public static final class ConstantOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "constant";
+        private final Object value;
+
+        public ConstantOp(TypeElement resultType, Object value) {
+                super(OPNAME, resultType, List.of());
+                this.value = value;
+        }
+
+        public ConstantOp(ConstantOp that, CopyContext cc) {
+            super(that, cc);
+            this.value = that.value;
+        }
+
+        @Override
+        public ConstantOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConstantOp(this, cc);
+        }
+
+        public Object value() {
+            return value;
+        }
+    }
+
+    public static final class ConvertOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "sconvert";
+
+        public ConvertOp(TypeElement resultType, List<Value> operands) {
+                super(OPNAME, resultType, operands);
+        }
+
+        public ConvertOp(ConvertOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ConvertOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConvertOp(this, cc);
+        }
+    }
+
+    public static final class IAddOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "iadd";
+
+        public IAddOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public IAddOp(IAddOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public IAddOp transform(CopyContext cc, OpTransformer ot) {
+            return new IAddOp(this, cc);
+        }
+    }
+
+    public static final class FAddOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fadd";
+
+        public FAddOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FAddOp(FAddOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FAddOp transform(CopyContext cc, OpTransformer ot) {
+            return new FAddOp(this, cc);
+        }
+    }
+
+    public static final class ISubOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "isub";
+
+        public ISubOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public ISubOp(ISubOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ISubOp transform(CopyContext cc, OpTransformer ot) {
+            return new ISubOp(this, cc);
+        }
+    }
+
+    public static final class FSubOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fsub";
+
+        public FSubOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FSubOp(FSubOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FSubOp transform(CopyContext cc, OpTransformer ot) {
+            return new FSubOp(this, cc);
+        }
+    }
+
+    public static final class IMulOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "imul";
+
+        public IMulOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public IMulOp(IMulOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public IMulOp transform(CopyContext cc, OpTransformer ot) {
+            return new IMulOp(this, cc);
+        }
+    }
+
+    public static final class FMulOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fmul";
+
+        public FMulOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FMulOp(FMulOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FMulOp transform(CopyContext cc, OpTransformer ot) {
+            return new FMulOp(this, cc);
+        }
+    }
+
+    public static final class IDivOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "idiv";
+
+        public IDivOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public IDivOp(IDivOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public IDivOp transform(CopyContext cc, OpTransformer ot) {
+            return new IDivOp(this, cc);
+        }
+    }
+
+    public static final class FDivOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fdiv";
+
+        public FDivOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FDivOp(FDivOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FDivOp transform(CopyContext cc, OpTransformer ot) {
+            return new FDivOp(this, cc);
+        }
+    }
+
+    public static final class ModOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "mod";
+
+        public ModOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public ModOp(ModOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ModOp transform(CopyContext cc, OpTransformer ot) {
+            return new ModOp(this, cc);
+        }
+    }
+
+    public static final class IEqualOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "iequal";
+
+        public IEqualOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public IEqualOp(IEqualOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public IEqualOp transform(CopyContext cc, OpTransformer ot) {
+            return new IEqualOp(this, cc);
+        }
+    }
+
+    public static final class FEqualOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fequal";
+
+        public FEqualOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FEqualOp(FEqualOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FEqualOp transform(CopyContext cc, OpTransformer ot) {
+            return new FEqualOp(this, cc);
+        }
+    }
+
+    public static final class INotEqualOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "inotequal";
+
+        public INotEqualOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public INotEqualOp(INotEqualOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public INotEqualOp transform(CopyContext cc, OpTransformer ot) {
+            return new INotEqualOp(this, cc);
+        }
+    }
+
+
+    public static final class FNotEqualOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "fnotequal";
+
+        public FNotEqualOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public FNotEqualOp(FNotEqualOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FNotEqualOp transform(CopyContext cc, OpTransformer ot) {
+            return new FNotEqualOp(this, cc);
+        }
+    }
+
+    public static final class LtOp extends SpirvOp {
+        public static final String NAME = NAME_PREFIX + "lt";
+
+        public LtOp(TypeElement resultType, List<Value> operands) {
+                super(NAME, resultType, operands);
+        }
+
+        public LtOp(LtOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LtOp transform(CopyContext cc, OpTransformer ot) {
+            return new LtOp(this, cc);
+        }
+    }
+
+    public static final class BranchOp extends SpirvOp implements Op.BlockTerminating {
+        public static final String NAME = NAME_PREFIX + "br";
+        private final Block.Reference successor;
+
+        public BranchOp(Block.Reference successor) {
+            super(NAME, JavaType.VOID, List.of());
+            this.successor = successor;
+        }
+
+        public BranchOp(BranchOp that, CopyContext cc) {
+            super(that, cc);
+            this.successor = that.successor;
+        }
+
+        @Override
+        public BranchOp transform(CopyContext cc, OpTransformer ot) {
+            return new BranchOp(this, cc);
+        }
+
+        public Block branch() {
+            return successor.targetBlock();
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return List.of(successor);
+        }
+    }
+
+    public static final class ConditionalBranchOp extends SpirvOp implements Op.BlockTerminating {
+        public static final String NAME = NAME_PREFIX + "brcond";
+        private final Block.Reference trueBlock;
+        private final Block.Reference falseBlock;
+
+        public ConditionalBranchOp(Block.Reference trueBlock, Block.Reference falseBlock, List<Value> operands) {
+                super(NAME, JavaType.VOID, operands);
+                this.trueBlock = trueBlock;
+                this.falseBlock = falseBlock;
+        }
+
+        public ConditionalBranchOp(ConditionalBranchOp that, CopyContext cc) {
+            super(that, cc);
+            this.trueBlock = that.trueBlock;
+            this.falseBlock = that.falseBlock;
+        }
+
+        @Override
+        public ConditionalBranchOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConditionalBranchOp(this, cc);
+        }
+
+        public Block trueBranch() {
+            return trueBlock.targetBlock();
+        }
+
+        public Block falseBranch() {
+            return falseBlock.targetBlock();
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return List.of(trueBlock, falseBlock);
+        }
+    }
+
+    public static final class VariableOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "variable";
+        private final String varName;
+        private final TypeElement varType;
+
+        public VariableOp(String varName, TypeElement type, TypeElement varType) {
+            super(OPNAME + " @" + varName, type, List.of());
+            this.varName = varName;
+            this.varType = varType;
+        }
+
+        public VariableOp(VariableOp that, CopyContext cc) {
+            super(that, cc);
+            this.varName = that.varName;
+            this.varType = that.varType;
+        }
+
+        @Override
+        public VariableOp transform(CopyContext cc, OpTransformer ot) {
+            return new VariableOp(this, cc);
+        }
+
+        public TypeElement varType() {
+            return varType;
+        }
+
+        public String varName() {
+            return varName;
+        }
+    }
+
+    public static final class CompositeExtractOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "compositeExtract";
+
+        public CompositeExtractOp(TypeElement resultType, List<Value> operands) {
+                super(OPNAME, resultType, operands);
+        }
+
+        public CompositeExtractOp(CompositeExtractOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public CompositeExtractOp transform(CopyContext cc, OpTransformer ot) {
+            return new CompositeExtractOp(this, cc);
+        }
+    }
+
+    public static final class InBoundAccessChainOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "inBoundAccessChain";
+
+        public InBoundAccessChainOp(TypeElement resultType, List<Value> operands) {
+                super(OPNAME, resultType, operands);
+        }
+
+        public InBoundAccessChainOp(InBoundAccessChainOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public InBoundAccessChainOp transform(CopyContext cc, OpTransformer ot) {
+            return new InBoundAccessChainOp(this, cc);
+        }
+    }
+
+    public static final class ReturnOp extends SpirvOp implements Op.Terminating {
+        public static final String OPNAME = "return";
+
+        public ReturnOp(TypeElement resultType, List<Value> operands) {
+            super(OPNAME, resultType, operands);
+        }
+
+        public ReturnOp(ReturnOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ReturnOp transform(CopyContext cc, OpTransformer ot) {
+            return new ReturnOp(this, cc);
+        }
+    }
+
+    public static final class FunctionParameterOp extends SpirvOp {
+        public static final String OPNAME = NAME_PREFIX + "function parameter";
+
+        public FunctionParameterOp(TypeElement resultType, List<Value> operands) {
+            super(OPNAME, resultType, operands);
+        }
+
+        public FunctionParameterOp(FunctionParameterOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public FunctionParameterOp transform(CopyContext cc, OpTransformer ot) {
+            return new FunctionParameterOp(this, cc);
+        }
+    }
+
+    public static final class FuncOp extends SpirvOp implements Op.Invokable {
+        public static enum Control {
+            INLINE,
+            DONTINLINE,
+            PURE,
+            CONST,
+            NONE
+        }
+
+        public static final String OPNAME = NAME_PREFIX + "function";
+        private final String functionName;
+        private final FunctionType functionType;
+        private final Body body;
+
+
+        public FuncOp(String name, FunctionType functionType, Body.Builder builder) {
+            super(OPNAME + "_" + name);
+            this.functionName = name;
+            this.functionType = functionType;
+            this.body = builder.build(this);
+        }
+
+        public FuncOp(FuncOp that, CopyContext cc) {
+            super(that, cc);
+            this.functionName = that.functionName;
+            this.functionType = that.functionType;
+            this.body = that.body;
+        }
+
+        @Override
+        public FuncOp transform(CopyContext cc, OpTransformer ot) {
+            return new FuncOp(this, cc);
+        }
+
+        @Override
+        public Body body() {
+            return body;
+        }
+
+        public String functionName() {
+            return functionName;
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public FunctionType invokableType() {
+            return functionType;
+        }
+    }
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvType.java b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvType.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/SpirvType.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.lang.reflect.code.TypeElement;
+
+public abstract sealed class SpirvType implements TypeElement permits PointerType, StorageType {
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/StorageType.java b/cr-examples/spirv/src/main/java/intel/code/spirv/StorageType.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/StorageType.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.util.List;
+import java.util.Objects;
+
+public sealed abstract class StorageType extends SpirvType
+    permits StorageType.Input, StorageType.Workgroup, StorageType.CrossWorkgroup, StorageType.Private, StorageType.Function {
+
+    public static final Input INPUT = new Input();
+    public static final Workgroup WORKGROUP = new Workgroup();
+    public static final CrossWorkgroup CROSSWORKGROUP= new CrossWorkgroup();
+    public static final Private PRIVATE = new Private();
+    public static final Function FUNCTION = new Function();
+
+    protected final String NAME;
+
+    protected StorageType(String name) {
+        this.NAME = name;
+    }
+
+    static final class Input extends StorageType {
+        protected Input(){
+            super("Input");
+        }
+    }
+
+    static final class Workgroup extends StorageType {
+        protected Workgroup() {
+            super("Workgroup");
+        }
+    }
+
+    static final class CrossWorkgroup extends StorageType
+    {
+        protected CrossWorkgroup() {
+            super("CrossWorkgroup");
+        }
+    }
+
+    static final class Private extends StorageType
+    {
+        protected Private() {
+            super("Private");
+        }
+    }
+
+    static final class Function extends StorageType
+    {
+        protected Function() {
+            super("Function");
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj != null && obj.getClass() != this.getClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(NAME);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME, List.of());
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+}
diff a/cr-examples/spirv/src/main/java/intel/code/spirv/TranslateToSpirvModel.java b/cr-examples/spirv/src/main/java/intel/code/spirv/TranslateToSpirvModel.java
--- /dev/null
+++ b/cr-examples/spirv/src/main/java/intel/code/spirv/TranslateToSpirvModel.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+
+public class TranslateToSpirvModel  {
+    private Map<Block, Block.Builder> blockMap;    // Java block to spirv block builder
+    private Map<Value, Value> valueMap;            // Java model Value to Spirv model Value
+
+    public static SpirvOps.FuncOp translateFunction(CoreOp.FuncOp func) {
+        CoreOp.FuncOp lowFunc = lowerMethod(func);
+        TranslateToSpirvModel instance = new TranslateToSpirvModel();
+        Body.Builder bodyBuilder = instance.translateBody(lowFunc.body(), lowFunc, null);
+        return new SpirvOps.FuncOp(lowFunc.funcName(), lowFunc.invokableType(), bodyBuilder);
+    }
+
+    private TranslateToSpirvModel() {
+        blockMap = new HashMap<>();
+        valueMap = new HashMap<>();
+    }
+
+    private Body.Builder translateBody(Body body, Op parentOp, Body.Builder parentBody) {
+        Body.Builder bodyBuilder = Body.Builder.of(parentBody, body.bodyType());
+        Block.Builder spirvBlock = bodyBuilder.entryBlock();
+        blockMap.put(body.entryBlock(), spirvBlock);
+        List<Block> blocks = body.blocks();
+        // map Java blocks to spirv blocks
+        for (Block b : blocks.subList(1, blocks.size()))  {
+            Block.Builder loweredBlock = spirvBlock.block(b.parameterTypes());
+            blockMap.put(b, loweredBlock);
+            spirvBlock = loweredBlock;
+        }
+        // map entry block parameters to spirv function parameter
+        spirvBlock = bodyBuilder.entryBlock();
+        List<SpirvOp> paramOps = new ArrayList<>();
+        List<SpirvOps.VariableOp> varOps = new ArrayList<>();
+        Block entryBlock = body.entryBlock();
+        int paramCount = entryBlock.parameters().size();
+        for (int i = 0; i < paramCount; i++) {
+            Block.Parameter bp = entryBlock.parameters().get(i);
+            assert entryBlock.ops().get(i) instanceof CoreOp.VarOp;
+            SpirvOp funcParam = new SpirvOps.FunctionParameterOp(bp.type(), List.of());
+            spirvBlock.op(funcParam);
+            valueMap.put(bp, funcParam.result());
+            paramOps.add(funcParam);
+        }
+        // SPIR-V Variable ops must be the first ops in a function's entry block and do not include initialization.
+        // Emit all SPIR-V Variable ops first and emit initializing stores afterward, at the CR model VarOp position.
+        for (int i = 0; i < paramCount; i++) {
+            CoreOp.VarOp jvop = (CoreOp.VarOp)entryBlock.ops().get(i);
+            TypeElement resultType = new PointerType(jvop.varType(), StorageType.CROSSWORKGROUP);
+            SpirvOps.VariableOp svop = new SpirvOps.VariableOp((String)jvop.attributes().get(""), resultType, jvop.varType());
+            spirvBlock.op(svop);
+            valueMap.put(jvop.result(), svop.result());
+            varOps.add(svop);
+        }
+        // add non-function-parameter variables
+        for (int bi = 0; bi < body.blocks().size(); bi++)  {
+            Block block = body.blocks().get(bi);
+            spirvBlock = blockMap.get(block);
+            List<Op> ops = block.ops();
+            for (int i = (bi == 0 ? paramCount : 0); i < ops.size(); i++) {
+                if (bi > 0) spirvBlock = blockMap.get(block);
+                Op op = ops.get(i);
+                if (op instanceof CoreOp.VarOp jvop) {
+                    TypeElement resultType = new PointerType(jvop.varType(), StorageType.CROSSWORKGROUP);
+                    SpirvOps.VariableOp svop = new SpirvOps.VariableOp((String)jvop.attributes().get(""), resultType, jvop.varType());
+                    bodyBuilder.entryBlock().op(svop);
+                    valueMap.put(jvop.result(), svop.result());
+                    varOps.add(svop);
+                }
+            }
+        }
+        for (int bi = 0; bi < body.blocks().size(); bi++)  {
+            Block block = body.blocks().get(bi);
+            spirvBlock = blockMap.get(block);
+            for (Op op : block.ops()) {
+                switch (op) {
+                    case CoreOp.ReturnOp rop -> {
+                        spirvBlock.op(new SpirvOps.ReturnOp(rop.resultType(), mapOperands(rop)));
+                    }
+                    case CoreOp.VarOp vop -> {
+                        Value dest = valueMap.get(vop.result());
+                        Value value = valueMap.get(vop.operands().get(0));
+                        // init variable here; declaration has been moved to top of function
+                        spirvBlock.op(new SpirvOps.StoreOp(dest, value));
+                    }
+                    case CoreOp.VarAccessOp.VarLoadOp vlo -> {
+                        List<Value> operands = mapOperands(vlo);
+                        SpirvOps.LoadOp load = new SpirvOps.LoadOp(vlo.resultType(), operands);
+                        spirvBlock.op(load);
+                        valueMap.put(vlo.result(), load.result());
+                    }
+                    case CoreOp.VarAccessOp.VarStoreOp vso -> {
+                        Value dest = valueMap.get(vso.varOp().result());
+                        Value value = valueMap.get(vso.operands().get(1));
+                        spirvBlock.op(new SpirvOps.StoreOp(dest, value));
+                    }
+                    case CoreOp.ArrayAccessOp.ArrayLoadOp alo -> {
+                        Value array = valueMap.get(alo.operands().get(0));
+                        Value index = valueMap.get(alo.operands().get(1));
+                        TypeElement arrayType = array.type();
+                        SpirvOps.ConvertOp convert = new SpirvOps.ConvertOp(JavaType.type(long.class), List.of(index));
+                        spirvBlock.op(new SpirvOps.LoadOp(arrayType, List.of(array)));
+                        spirvBlock.op(convert);
+                        SpirvOp ibac = new SpirvOps.InBoundAccessChainOp(arrayType, List.of(array, convert.result()));
+                        spirvBlock.op(ibac);
+                        SpirvOp load = new SpirvOps.LoadOp(alo.resultType(), List.of(ibac.result()));
+                        spirvBlock.op(load);
+                        valueMap.put(alo.result(), load.result());
+                    }
+                    case CoreOp.ArrayAccessOp.ArrayStoreOp aso -> {
+                        Value array = valueMap.get(aso.operands().get(0));
+                        Value index = valueMap.get(aso.operands().get(1));
+                        TypeElement arrayType = array.type();
+                        SpirvOp ibac = new SpirvOps.InBoundAccessChainOp(arrayType, List.of(array, index));
+                        spirvBlock.op(ibac);
+                        spirvBlock.op(new SpirvOps.StoreOp(ibac.result(), valueMap.get(aso.operands().get(2))));
+                    }
+                    case CoreOp.ArrayLengthOp alo -> {
+                        Op len = new SpirvOps.ArrayLengthOp(JavaType.INT, List.of(valueMap.get(alo.operands().get(0))));
+                        spirvBlock.op(len);
+                        valueMap.put(alo.result(), len.result());
+                    }
+                    case CoreOp.AddOp aop -> {
+                        TypeElement type = aop.operands().get(0).type();
+                        List<Value> operands = mapOperands(aop);
+                        SpirvOp addOp;
+                        if (isIntegerType(type)) addOp = new SpirvOps.IAddOp(type, operands);
+                        else if (isFloatType(type)) addOp = new SpirvOps.FAddOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(addOp);
+                        valueMap.put(aop.result(), addOp.result());
+                     }
+                    case CoreOp.SubOp sop -> {
+                        TypeElement  type = sop.operands().get(0).type();
+                        List<Value> operands = mapOperands(sop);
+                        SpirvOp subOp;
+                        if (isIntegerType(type)) subOp = new SpirvOps.ISubOp(type, operands);
+                        else if (isFloatType(type)) subOp = new SpirvOps.FSubOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(subOp);
+                        valueMap.put(sop.result(), subOp.result());
+                     }
+                    case CoreOp.MulOp mop -> {
+                        TypeElement type = mop.operands().get(0).type();
+                        List<Value> operands = mapOperands(mop);
+                        SpirvOp mulOp;
+                        if (isIntegerType(type)) mulOp = new SpirvOps.IMulOp(type, operands);
+                        else if (isFloatType(type)) mulOp = new SpirvOps.FMulOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(mulOp);
+                        valueMap.put(mop.result(), mulOp.result());
+                    }
+                    case CoreOp.DivOp dop -> {
+                        TypeElement type = dop.operands().get(0).type();
+                        List<Value> operands = mapOperands(dop);
+                        SpirvOp divOp;
+                        if (isIntegerType(type)) divOp = new SpirvOps.IDivOp(type, operands);
+                        else if (isFloatType(type)) divOp = new SpirvOps.FDivOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(divOp);
+                        valueMap.put(dop.result(), divOp.result());
+                    }
+                    case CoreOp.ModOp mop -> {
+                        TypeElement type = mop.operands().get(0).type();
+                        List<Value> operands = mapOperands(mop);
+                        SpirvOp modOp = new SpirvOps.ModOp(type, operands);
+                        spirvBlock.op(modOp);
+                        valueMap.put(mop.result(), modOp.result());
+                    }
+                    case CoreOp.EqOp eqop -> {
+                        TypeElement type = eqop.operands().get(0).type();
+                        List<Value> operands = mapOperands(eqop);
+                        SpirvOp seqop;
+                        if (isIntegerType(type)) seqop = new SpirvOps.IEqualOp(type, operands);
+                        else if (isFloatType(type)) seqop = new SpirvOps.FEqualOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(seqop);
+                        valueMap.put(eqop.result(), seqop.result());
+                    }
+                    case CoreOp.NeqOp neqop -> {
+                        TypeElement type = neqop.operands().get(0).type();
+                        List<Value> operands = mapOperands(neqop);
+                        SpirvOp sneqop;
+                        if (isIntegerType(type)) sneqop = new SpirvOps.INotEqualOp(type, operands);
+                        else if (isFloatType(type)) sneqop = new SpirvOps.FNotEqualOp(type, operands);
+                        else throw unsupported("type", type);
+                        spirvBlock.op(sneqop);
+                        valueMap.put(neqop.result(), sneqop.result());
+                    }
+                    case CoreOp.LtOp ltop -> {
+                        TypeElement type = ltop.operands().get(0).type();
+                        List<Value> operands = mapOperands(ltop);
+                        SpirvOp sltop = new SpirvOps.LtOp(type, operands);
+                        spirvBlock.op(sltop);
+                        valueMap.put(ltop.result(), sltop.result());
+                    }
+                    case CoreOp.InvokeOp inv -> {
+                        List<Value> operands = mapOperands(inv);
+                        SpirvOp spirvCall = new SpirvOps.CallOp(inv.invokeDescriptor(), operands);
+                        spirvBlock.op(spirvCall);
+                        valueMap.put(inv.result(), spirvCall.result());
+                    }
+                    case CoreOp.ConstantOp cop -> {
+                        SpirvOp scop = new SpirvOps.ConstantOp(cop.resultType(), cop.value());
+                        spirvBlock.op(scop);
+                        valueMap.put(cop.result(), scop.result());
+                    }
+                    case CoreOp.ConvOp cop -> {
+                        List<Value> operands = mapOperands(cop);
+                        SpirvOp scop = new SpirvOps.ConvertOp(cop.resultType(), operands);
+                        spirvBlock.op(scop);
+                        valueMap.put(cop.result(), scop.result());
+                    }
+                    case CoreOp.FieldAccessOp.FieldLoadOp flo -> {
+                        SpirvOp load = new SpirvOps.FieldLoadOp(flo.resultType(), flo.fieldDescriptor(), mapOperands(flo));
+                        spirvBlock.op(load);
+                        valueMap.put(flo.result(), load.result());
+                    }
+                    case CoreOp.BranchOp bop -> {
+                        Block.Reference successor = blockMap.get(bop.branch().targetBlock()).successor();
+                        spirvBlock.op(new SpirvOps.BranchOp(successor));
+                    }
+                    case CoreOp.ConditionalBranchOp cbop -> {
+                        Block trueBlock = cbop.trueBranch().targetBlock();
+                        Block falseBlock = cbop.falseBranch().targetBlock();
+                        Block.Reference spvTrueBlock = blockMap.get(trueBlock).successor();
+                        Block.Reference spvFalseBlock = blockMap.get(falseBlock).successor();
+                        spirvBlock.op(new SpirvOps.ConditionalBranchOp(spvTrueBlock, spvFalseBlock, mapOperands(cbop)));
+                    }
+                    default -> unsupported("op", op.getClass());
+                }
+            } //ops
+        } // blocks
+        return bodyBuilder;
+    }
+
+    private RuntimeException unsupported(String message, Object value) {
+        return new RuntimeException("Unsupported " + message + ": " + value);
+    }
+
+    private static CoreOp.FuncOp lowerMethod(CoreOp.FuncOp fop) {
+        CoreOp.FuncOp lfop = fop.transform((block, op) -> {
+            if (op instanceof Op.Lowerable lop)  {
+                return lop.lower(block);
+            }
+            else {
+                block.op(op);
+                return block;
+            }
+        });
+        return lfop;
+    }
+
+    private List<Value> mapOperands(Op op) {
+        List<Value> operands = new ArrayList<>();
+        for (Value javaValue : op.operands()) {
+            Value spirvValue = valueMap.get(javaValue);
+            assert spirvValue != null : "no value mapping from %s" + javaValue;
+            operands.add(spirvValue);
+        }
+        return operands;
+    }
+
+    private boolean isIntegerType(TypeElement type) {
+        return type.equals(JavaType.INT) || type.equals(JavaType.LONG);
+    }
+
+    private boolean isFloatType(TypeElement type) {
+        return type.equals(JavaType.FLOAT) || type.equals(JavaType.DOUBLE);
+    }
+}
diff a/cr-examples/spirv/src/test/java/intel/code/spirv/ExampleUseTest.java b/cr-examples/spirv/src/test/java/intel/code/spirv/ExampleUseTest.java
--- /dev/null
+++ b/cr-examples/spirv/src/test/java/intel/code/spirv/ExampleUseTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Intel Corporation. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package intel.code.spirv;
+
+import java.lang.reflect.Method;
+import java.lang.foreign.MemorySegment;
+import java.lang.runtime.CodeReflection;
+import java.lang.reflect.code.op.CoreOp;
+import org.junit.jupiter.api.Test;
+
+public class ExampleUseTest {
+
+    @CodeReflection
+    public static void matrixMultiply(float[] a, float[] b, float[] c, int size) {
+        for (int i = 0; i < size; i++) {
+            for (int j = 0; j < size; j++) {
+                float sum = 0f;
+                for (int k = 0; k < size; k++) {
+                    sum += a[i * size + k] * b[k * size + j];
+                }
+                c[i * size + j] = sum;
+            }
+        }
+    }
+
+    @Test
+    public void test() throws Exception {
+        String methodName = "matrixMultiply";
+        Method method = ExampleUseTest.class.getDeclaredMethod(methodName, float[].class, float[].class, float[].class, int.class);
+
+        CoreOp.FuncOp javaFunc = method.getCodeModel().get();
+        SpirvOps.FuncOp spirvFunc = TranslateToSpirvModel.translateFunction(javaFunc);
+        MemorySegment spirvBinary = SpirvModuleGenerator.generateModule(methodName, spirvFunc);
+
+        System.out.println("\n------- Java Model -------");
+        System.out.println(javaFunc.toText());
+        System.out.println("------- SPIR-V Model -------");
+        System.out.println(spirvFunc.toText());
+        System.out.println("------- SPIR-V Module -------");
+        System.out.println(SpirvModuleGenerator.disassembleModule(spirvBinary));
+   }
+}
diff a/cr-examples/triton/.gitignore b/cr-examples/triton/.gitignore
--- /dev/null
+++ b/cr-examples/triton/.gitignore
@@ -0,0 +1,2 @@
+/.idea/
+/target/
diff a/cr-examples/triton/README.md b/cr-examples/triton/README.md
--- /dev/null
+++ b/cr-examples/triton/README.md
@@ -0,0 +1,3 @@
+Example using code reflection with a Java-based Triton programming model
+inspired by Triton and its Python programming model, see
+https://openai.com/research/triton.
diff a/cr-examples/triton/pom.xml b/cr-examples/triton/pom.xml
--- /dev/null
+++ b/cr-examples/triton/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation.  Oracle designates this
+particular file as subject to the "Classpath" exception as provided
+by Oracle in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+or visit www.oracle.com if you need additional information or have any
+questions.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>oracle.code</groupId>
+    <artifactId>triton</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>23</maven.compiler.source>
+        <maven.compiler.target>23</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.10.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>3.1.2</version>
+                    <configuration>
+                        <argLine>--enable-preview
+                        </argLine>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <compilerArgs>
+                        <arg>--enable-preview</arg>
+                    </compilerArgs>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                    <showDeprecation>true</showDeprecation>
+                    <failOnError>true</failOnError>
+                    <showWarnings>true</showWarnings>
+                    <showDeprecation>true</showDeprecation>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff a/cr-examples/triton/src/main/java/oracle/code/triton/ArithMathOps.java b/cr-examples/triton/src/main/java/oracle/code/triton/ArithMathOps.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/ArithMathOps.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.*;
+import java.lang.reflect.code.type.JavaType;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ArithMathOps {
+
+    static abstract class ArithMathOp extends ExternalizableOp {
+        final TypeElement resultType;
+
+        public ArithMathOp(ExternalizedOp def) {
+            super(def);
+
+            this.resultType = def.resultType();
+        }
+
+        ArithMathOp(ArithMathOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.resultType = that.resultType;
+        }
+
+        ArithMathOp(String name, TypeElement resultType, List<? extends Value> operands) {
+            super(name, operands);
+
+            this.resultType = resultType;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    @OpFactory.OpDeclaration(ConstantOp.NAME)
+    public static class ConstantOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.constant";
+        public static final String ATTRIBUTE_CONSTANT_VALUE = NAME + ".value";
+
+        final Object value;
+
+        public static ConstantOp create(ExternalizedOp def) {
+            if (!def.operands().isEmpty()) {
+                throw new IllegalArgumentException("Operation must have zero operands");
+            }
+
+            Object value = def.extractAttributeValue(ATTRIBUTE_CONSTANT_VALUE,true,
+                    v -> processConstantValue(def.resultType(), v));
+            return new ConstantOp(def, value);
+        }
+
+        static Object processConstantValue(TypeElement t, Object value) {
+            if (t.equals(JavaType.BOOLEAN)) {
+                if (value instanceof String s) {
+                    return Boolean.valueOf(s);
+                } else if (value instanceof Boolean) {
+                    return value;
+                }
+            } else if (t.equals(JavaType.BYTE)) {
+                if (value instanceof String s) {
+                    return Byte.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.byteValue();
+                }
+            } else if (t.equals(JavaType.SHORT)) {
+                if (value instanceof String s) {
+                    return Short.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.shortValue();
+                }
+            } else if (t.equals(JavaType.CHAR)) {
+                if (value instanceof String s) {
+                    return s.charAt(0);
+                } else if (value instanceof Character) {
+                    return value;
+                }
+            } else if (t.equals(JavaType.INT)) {
+                if (value instanceof String s) {
+                    return Integer.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.intValue();
+                }
+            } else if (t.equals(JavaType.LONG)) {
+                if (value instanceof String s) {
+                    return Long.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.longValue();
+                }
+            } else if (t.equals(JavaType.FLOAT)) {
+                if (value instanceof String s) {
+                    return Float.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.floatValue();
+                }
+            } else if (t.equals(JavaType.DOUBLE)) {
+                if (value instanceof String s) {
+                    return Double.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.doubleValue();
+                }
+            } else if (t instanceof TensorType tt) {
+                return processConstantValue(tt.eType(), value);
+            }
+
+            throw new UnsupportedOperationException("Unsupported constant type and value: " + t + " " + value);
+        }
+
+        ConstantOp(ExternalizedOp def, Object value) {
+            super(def);
+
+            this.value = value;
+        }
+
+        ConstantOp(ConstantOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.value = that.value;
+        }
+
+        @Override
+        public ConstantOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConstantOp(this, cc);
+        }
+
+        ConstantOp(TypeElement type, Object value) {
+            super(NAME, type, List.of());
+
+            this.value = value;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> attrs = new HashMap<>(super.attributes());
+            attrs.put("", value);
+            return attrs;
+        }
+
+        public Object value() {
+            return value;
+        }
+    }
+
+    @OpFactory.OpDeclaration(AddOp.NAME)
+    public static class AddOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.add";
+
+        public AddOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        AddOp(AddOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AddOp transform(CopyContext cc, OpTransformer ot) {
+            return new AddOp(this, cc);
+        }
+
+        AddOp(Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), false), a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(SubOp.NAME)
+    public static class SubOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.sub";
+
+        public SubOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        SubOp(SubOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public SubOp transform(CopyContext cc, OpTransformer ot) {
+            return new SubOp(this, cc);
+        }
+
+        SubOp(Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), false), a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(MulOp.NAME)
+    public static class MulOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.mul";
+
+        public MulOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        MulOp(MulOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public MulOp transform(CopyContext cc, OpTransformer ot) {
+            return new MulOp(this, cc);
+        }
+
+        MulOp(Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), false), a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(DivOp.NAME)
+    public static class DivOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.div";
+
+        public DivOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        DivOp(DivOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public DivOp transform(CopyContext cc, OpTransformer ot) {
+            return new DivOp(this, cc);
+        }
+
+        DivOp(Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), true), a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(RemOp.NAME)
+    public static class RemOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.rem";
+
+        public RemOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        RemOp(RemOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public RemOp transform(CopyContext cc, OpTransformer ot) {
+            return new RemOp(this, cc);
+        }
+
+        RemOp(Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), true), a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(AndOp.NAME)
+    public static class AndOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.andi";
+
+        public AndOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        AndOp(AndOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AndOp transform(CopyContext cc, OpTransformer ot) {
+            return new AndOp(this, cc);
+        }
+
+        AndOp(Value a, Value b) {
+            super(NAME, a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(MaxOp.NAME)
+    public static class MaxOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.max";
+
+        public MaxOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        MaxOp(MaxOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public MaxOp transform(CopyContext cc, OpTransformer ot) {
+            return new MaxOp(this, cc);
+        }
+
+        MaxOp(Value a, Value b) {
+            super(NAME + maxMinSuffixFromType(a.type()) + nameSuffixFromType(a.type(), true),
+                    a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(MinOp.NAME)
+    public static class MinOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.min";
+
+        public MinOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        MinOp(MinOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public MinOp transform(CopyContext cc, OpTransformer ot) {
+            return new MinOp(this, cc);
+        }
+
+        MinOp(Value a, Value b) {
+            super(NAME + maxMinSuffixFromType(a.type()) + nameSuffixFromType(a.type(), true),
+                    a.type(), List.of(a, b));
+        }
+    }
+
+    @OpFactory.OpDeclaration(ExpOp.NAME)
+    public static class TruncOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.trunc";
+
+        public TruncOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        TruncOp(TruncOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public TruncOp transform(CopyContext cc, OpTransformer ot) {
+            return new TruncOp(this, cc);
+        }
+
+        TruncOp(TypeElement t, Value a) {
+            super(NAME + nameSuffixFromType(a.type(), false),
+                    t, List.of(a));
+        }
+    }
+
+    @OpFactory.OpDeclaration(ExpOp.NAME)
+    public static class ExpOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "math.exp";
+
+        public ExpOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ExpOp(ExpOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ExpOp transform(CopyContext cc, OpTransformer ot) {
+            return new ExpOp(this, cc);
+        }
+
+        ExpOp(Value a) {
+            super(NAME, a.type(), List.of(a));
+        }
+    }
+
+    @OpFactory.OpDeclaration(CompareOp.NAME)
+    public static class CompareOp extends ArithMathOp implements Op.Pure {
+        public static final String NAME = "arith.cmp";
+        public static final String ATTRIBUTE_CONSTANT_VALUE = NAME + ".compare";
+
+        public enum CompareKind {
+            slt
+        }
+
+        final CompareKind ck;
+
+        public static CompareOp create(ExternalizedOp def) {
+            CompareKind ck = def.extractAttributeValue(ATTRIBUTE_CONSTANT_VALUE, true,
+                    v -> switch (v) {
+                        case String s -> CompareKind.valueOf(s);
+                        case CompareKind k -> k;
+                        default -> throw new UnsupportedOperationException("Unsupported start value:" + v);
+                    });
+            return new CompareOp(def, ck);
+        }
+
+        CompareOp(ExternalizedOp def, CompareKind ck) {
+            super(def);
+
+            this.ck = ck;
+        }
+
+        CompareOp(CompareOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.ck = that.ck;
+        }
+
+        @Override
+        public CompareOp transform(CopyContext cc, OpTransformer ot) {
+            return new CompareOp(this, cc);
+        }
+
+        CompareOp(CompareKind ck, Value a, Value b) {
+            super(NAME + nameSuffixFromType(a.type(), false), a.type(), List.of(a, b));
+
+            this.ck = ck;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> attrs = new HashMap<>(super.attributes());
+            attrs.put("", ck);
+            return attrs;
+        }
+
+        public CompareKind kind() {
+            return ck;
+        }
+    }
+
+    static String maxMinSuffixFromType(TypeElement t) {
+        if (t instanceof TensorType tt) {
+            return maxMinSuffixFromType(tt.eType());
+        } else if (t instanceof PtrType pt) {
+            return maxMinSuffixFromType(pt.rType());
+        } else if (t.equals(JavaType.INT)) {
+            return "";
+        } else if (t.equals(JavaType.FLOAT)) {
+            return "imum";
+        } else {
+            throw new UnsupportedOperationException("Unsupported type: " + t);
+        }
+    }
+
+    static String nameSuffixFromType(TypeElement t, boolean signed) {
+        if (t instanceof TensorType tt) {
+            return nameSuffixFromType(tt.eType(), signed);
+        } else if (t instanceof PtrType pt) {
+            return nameSuffixFromType(pt.rType(), signed);
+        } else if (t.equals(JavaType.INT) || t.equals(JavaType.LONG)) {
+            return (signed ? "s" : "") + "i";
+        } else if (t.equals(JavaType.FLOAT) || t.equals(JavaType.DOUBLE) ||
+                Float16.FLOAT_16_TYPE.equals(t)) {
+            return "f";
+        } else {
+            throw new UnsupportedOperationException("Unsupported type: " + t);
+        }
+    }
+
+    public static final OpFactory FACTORY = def -> {
+        return switch (def.name()) {
+            case ConstantOp.NAME -> ConstantOp.create(def);
+            case ExpOp.NAME -> new ExpOp(def);
+            case AddOp.NAME + "i", AddOp.NAME + "f" -> new AddOp(def);
+            case SubOp.NAME + "i", SubOp.NAME + "f" -> new SubOp(def);
+            case MulOp.NAME + "i", MulOp.NAME + "f" -> new MulOp(def);
+            case DivOp.NAME + "si", DivOp.NAME + "f" -> new DivOp(def);
+            case RemOp.NAME + "si", RemOp.NAME + "f" -> new DivOp(def);
+            case AndOp.NAME -> new AndOp(def);
+            case MaxOp.NAME + "si", MaxOp.NAME + "imumf" -> new MaxOp(def);
+            case MinOp.NAME + "si", MinOp.NAME + "imumf" -> new MinOp(def);
+            case TruncOp.NAME + "i", TruncOp.NAME + "f" -> new TruncOp(def);
+            case CompareOp.NAME + "i", CompareOp.NAME + "f" -> CompareOp.create(def);
+            default -> null;
+        };
+    };
+
+    // Arith
+
+    public static ConstantOp constant(TypeElement type, Object value) {
+        return new ConstantOp(type, value);
+    }
+
+    public static MulOp mul(Value a, Value b) {
+        return new MulOp(a, b);
+    }
+
+    public static AddOp add(Value a, Value b) {
+        return new AddOp(a, b);
+    }
+
+    public static SubOp sub(Value a, Value b) {
+        return new SubOp(a, b);
+    }
+
+    public static DivOp div(Value a, Value b) {
+        return new DivOp(a, b);
+    }
+
+    public static RemOp rem(Value a, Value b) {
+        return new RemOp(a, b);
+    }
+
+    public static AndOp and(Value a, Value b) {
+        return new AndOp(a, b);
+    }
+
+    public static MaxOp maximum(Value a, Value b) {
+        return new MaxOp(a, b);
+    }
+
+    public static MinOp minimum(Value a, Value b) {
+        return new MinOp(a, b);
+    }
+
+    public static CompareOp cmp(CompareOp.CompareKind ck, Value a, Value b) {
+        return new CompareOp(ck, a, b);
+    }
+
+    public static TruncOp trunc(TypeElement type, Value a) {
+        return new TruncOp(type, a);
+    }
+
+    // Math
+
+    public static ExpOp exp(Value a) {
+        return new ExpOp(a);
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Constant.java b/cr-examples/triton/src/main/java/oracle/code/triton/Constant.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Constant.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Constant {
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/ConstantType.java b/cr-examples/triton/src/main/java/oracle/code/triton/ConstantType.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/ConstantType.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.Objects;
+
+public final class ConstantType extends TritonType {
+    static final String NAME = "constant";
+
+    final TypeElement cType;
+    final Object value;
+
+    public ConstantType(TypeElement cType, Object value) {
+        this.cType = cType;
+        this.value = value;
+    }
+
+    public TypeElement cType() {
+        return cType;
+    }
+
+    public Object value() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ConstantType that = (ConstantType) o;
+        return Objects.equals(cType, that.cType) && Objects.equals(value, that.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(cType, value);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME,
+                List.of(cType.externalize(),
+                        new ExternalizedTypeElement("c" + value, List.of())));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Float16.java b/cr-examples/triton/src/main/java/oracle/code/triton/Float16.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Float16.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+
+// Fake float 16 type
+public final class Float16 extends TritonNumber {
+    static final TypeElement FLOAT_16_TYPE = JavaType.type(Float16.class);
+
+    private Float16() {
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Functions.java b/cr-examples/triton/src/main/java/oracle/code/triton/Functions.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Functions.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+class Functions {
+    private Functions() {
+    }
+
+    @CodeReflection
+    static int sum(int a, int b) {
+        return a + b;
+    }
+
+    @CodeReflection
+    static int max(int a, int b) {
+        return Math.max(a, b);
+    }
+
+    @CodeReflection
+    static int cdiv(int x, int div) {
+        return (x + div - 1) / div;
+    }
+
+    static CoreOp.FuncOp getJavaCodeModel(String name) {
+        Optional<Method> om = Stream.of(Functions.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .filter(m -> m.getAnnotation(CodeReflection.class) != null)
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Ptr.java b/cr-examples/triton/src/main/java/oracle/code/triton/Ptr.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Ptr.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+public final class Ptr extends TritonNumber {
+    private Ptr() {
+    }
+
+    public PtrType type() {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/PtrType.java b/cr-examples/triton/src/main/java/oracle/code/triton/PtrType.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/PtrType.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.Objects;
+
+public final class PtrType extends TritonType {
+    static final String NAME = "ptr";
+    final TypeElement rType;
+
+    public PtrType(TypeElement rType) {
+        this.rType = rType;
+    }
+
+    public TypeElement rType() {
+        return rType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PtrType ptrType = (PtrType) o;
+        return Objects.equals(rType, ptrType.rType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(rType);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME, List.of(rType.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/SCFOps.java b/cr-examples/triton/src/main/java/oracle/code/triton/SCFOps.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/SCFOps.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.*;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.type.TupleType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class SCFOps {
+
+    @OpFactory.OpDeclaration(ForOp.NAME)
+    public static final class ForOp extends ExternalizableOp implements Op.Loop {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final List<Value> range;
+            final FunctionType loopType;
+
+            Builder(Body.Builder ancestorBody, List<Value> range, FunctionType loopType) {
+                this.ancestorBody = ancestorBody;
+                this.range = range;
+                this.loopType = loopType;
+            }
+
+            public ForOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, loopType);
+                c.accept(body.entryBlock());
+                return new ForOp(range, body);
+            }
+
+            public ForOp body(CopyContext cc, Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, loopType, cc);
+                c.accept(body.entryBlock());
+                return new ForOp(range, body);
+            }
+        }
+
+        public static final String NAME = "scf.for";
+
+        final Body body;
+
+        public ForOp(ExternalizedOp def) {
+            super(def);
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        ForOp(ForOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public ForOp transform(CopyContext cc, OpTransformer ot) {
+            return new ForOp(this, cc, ot);
+        }
+
+        ForOp(List<Value> range, Body.Builder bodyBuilder) {
+            super(NAME, range);
+
+            this.body = bodyBuilder.build(this);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return body.yieldType();
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public Body loopBody() {
+            return body;
+        }
+    }
+
+    @OpFactory.OpDeclaration(YieldOp.NAME)
+    public static class YieldOp extends ExternalizableOp implements Op.Terminating {
+        public static final String NAME = "scf.yield";
+
+        public YieldOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        YieldOp(YieldOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public YieldOp transform(CopyContext cc, OpTransformer ot) {
+            return new YieldOp(this, cc);
+        }
+
+        YieldOp(List<Value> values) {
+            super(NAME, values);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+
+        static TypeElement yieldType(List<Value> values) {
+            if (values.size() == 1) {
+                return values.get(0).type();
+            } else {
+                return TupleType.tupleTypeFromValues(values);
+            }
+        }
+    }
+
+    static public ForOp.Builder for_(Body.Builder ancestorBody,
+                                     Value start, Value end, Value step,
+                                     List<Value> iterValues) {
+        TypeElement yieldType = (iterValues.size() == 1)
+                ? iterValues.get(0).type()
+                : TupleType.tupleTypeFromValues(iterValues);
+
+        List<TypeElement> bodyParameterTypes = new ArrayList<>();
+        bodyParameterTypes.add(start.type());
+        bodyParameterTypes.addAll(iterValues.stream().map(Value::type).toList());
+        FunctionType bodyType = FunctionType.functionType(yieldType, bodyParameterTypes);
+
+        List<Value> operands = new ArrayList<>();
+        operands.addAll(List.of(start, end, step));
+        operands.addAll(iterValues);
+        return new ForOp.Builder(ancestorBody, operands, bodyType);
+    }
+
+
+    public static final OpFactory FACTORY = OpFactory.OP_FACTORY.get(SCFOps.class);
+
+    static public YieldOp yield_(Value... values) {
+        return yield_(List.of(values));
+    }
+
+    static public YieldOp yield_(List<Value> values) {
+        return new YieldOp(values);
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/SimpleCountedForLoopInfo.java b/cr-examples/triton/src/main/java/oracle/code/triton/SimpleCountedForLoopInfo.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/SimpleCountedForLoopInfo.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.analysis.Patterns;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.CoreOp.VarAccessOp.VarLoadOp;
+import java.lang.reflect.code.op.CoreOp.VarAccessOp.VarStoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.reflect.code.analysis.Patterns.*;
+
+// @@@ Very basic, limited, and partially correct
+public class SimpleCountedForLoopInfo {
+
+    final ExtendedOp.JavaForOp fop;
+
+    SimpleCountedForLoopInfo(ExtendedOp.JavaForOp fop) {
+        this.fop = fop;
+
+        if (fop.init().yieldType().equals(JavaType.VOID)) {
+            throw new IllegalArgumentException("Loop variable externally initialized");
+        }
+        if (fop.loopBody().entryBlock().parameters().size() > 1) {
+            throw new IllegalArgumentException("Two or more loop variables");
+        }
+    }
+
+    public List<Op> startExpression() {
+        /*
+        ()Var<int> -> {
+            %12 : int = constant @"0";
+            %13 : Var<int> = var %12 @"i";
+            yield %13;
+        }
+         */
+
+        Patterns.OpPattern p = opP(CoreOp.YieldOp.class,
+                opP(CoreOp.VarOp.class,
+                        opResultP()));
+
+        // match against yieldOp
+        Op yieldOp = fop.init().entryBlock().ops().getLast();
+        List<Value> matches = Patterns.match(null, yieldOp, p, (matchState, o) -> {
+            return matchState.matchedOperands();
+        });
+        if (matches == null) {
+            throw new IllegalArgumentException();
+        }
+        Op.Result initValue = (Op.Result) matches.get(0);
+
+        return traverseOperands(initValue.op());
+    }
+
+    public List<Op> endExpression() {
+        /*
+        (%14 : Var<int>)boolean -> {
+            %15 : int = var.load %14;
+            %16 : int = var.load %2;
+            %17 : boolean = lt %15 %16;
+            yield %17;
+        }
+         */
+
+        Patterns.OpPattern p = opP(CoreOp.YieldOp.class,
+                opP(CoreOp.LtOp.class,
+                        opP(VarLoadOp.class,
+                                blockParameterP()),
+                        opResultP()));
+
+        // match against yieldOp
+        Op yieldOp = fop.cond().entryBlock().ops().getLast();
+        List<Value> matches = Patterns.match(null, yieldOp, p, (matchState, o) -> {
+            return matchState.matchedOperands();
+        });
+        if (matches == null) {
+            throw new IllegalArgumentException();
+        }
+        Op.Result endValue = (Op.Result) matches.get(1);
+
+        return traverseOperands(endValue.op());
+    }
+
+    public List<Op> stepExpression() {
+        /*
+        (%18 : Var<int>)void -> {
+            %19 : int = var.load %18;
+            %20 : int = constant @"1";
+            %21 : int = add %19 %20;
+            var.store %18 %21;
+            yield;
+        }
+         */
+
+        Patterns.OpPattern p = opP(VarStoreOp.class,
+                blockParameterP(),
+                opP(CoreOp.AddOp.class,
+                        opP(VarLoadOp.class, blockParameterP()),
+                        opResultP()));
+
+        // Match against last store op
+        // @@@ Add Block.prevOp()
+        Op storeOp = fop.update().entryBlock().ops().get(fop.update().entryBlock().ops().size() - 2);
+        List<Value> matches = Patterns.match(null, storeOp, p, (matchState, r) -> {
+            return matchState.matchedOperands();
+        });
+        if (matches == null) {
+            throw new IllegalArgumentException();
+        }
+        Op.Result stepValue = (Op.Result) matches.get(2);
+
+        return traverseOperands(stepValue.op());
+    }
+
+    static List<Op> traverseOperands(Op op) {
+        List<Op> ops = new ArrayList<>();
+        traverseOperands(ops, op);
+        return ops;
+    }
+
+    // Hoist the expression
+    // @@@ should be pure and independent of the loop variable
+    static void traverseOperands(List<Op> ops, Op op) {
+        for (Value operand : op.operands()) {
+            if (operand.declaringBlock().parentBody() == op.ancestorBody()) {
+                if (operand instanceof Op.Result r) {
+                    traverseOperands(ops, r.op());
+                }
+            }
+        }
+
+        ops.add(op);
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Tensor.java b/cr-examples/triton/src/main/java/oracle/code/triton/Tensor.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Tensor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+public final class Tensor extends TritonNumber {
+    private Tensor() {
+    }
+
+    public TensorType type() {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TensorType.java b/cr-examples/triton/src/main/java/oracle/code/triton/TensorType.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TensorType.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public final class TensorType extends TritonType {
+    static final String NAME = "tensor";
+
+    final TypeElement eType;
+    final List<Integer> shape;
+    final int size;
+
+    public TensorType(TypeElement eType, List<Integer> shape) {
+        this.eType = eType;
+        this.shape = List.copyOf(shape);
+        int s = 1;
+        for (Integer i : shape) {
+            s *= i;
+        }
+        this.size = s;
+    }
+
+    public TypeElement eType() {
+        return eType;
+    }
+
+    public List<Integer> shape() {
+        return shape;
+    }
+
+    public int size() {
+        return size;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TensorType that = (TensorType) o;
+        return Objects.equals(eType, that.eType) && Objects.equals(shape, that.shape);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(eType, shape);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        List<ExternalizedTypeElement> args = new ArrayList<>();
+        for (int i : shape) {
+            args.add(new ExternalizedTypeElement("x" + i, List.of()));
+        }
+        args.add(eType.externalize());
+        return new ExternalizedTypeElement(NAME, args);
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/Triton.java b/cr-examples/triton/src/main/java/oracle/code/triton/Triton.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/Triton.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.Type;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+public class Triton {
+    private Triton() {
+    }
+
+    public static int programId(@Constant int axis) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor arange(@Constant int start, @Constant int end) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor load(Tensor ptr, Tensor mask) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static void store(Tensor ptr, Tensor value, Tensor mask) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor broadcast(Object o, TensorType type) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor expand(Tensor a, int axis) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor zeros(Class<?> eType, int... shape)  {
+        throw new UnsupportedOperationException();
+    }
+
+    public static TensorType joinShape(TensorType a, TensorType b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor dot(Tensor a, Tensor b) {
+        throw new UnsupportedOperationException();
+    }
+
+    // Arithmetic
+
+    public static Tensor add(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor sub(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor mul(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor div(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor mod(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor and(Number a, Number b) {
+        throw new UnsupportedOperationException();
+    }
+
+    public enum CompareKind {
+        Equal,
+        LessThan,
+        LessThanOrEqual,
+        GreaterThan,
+        GreaterThanOrEqual
+    }
+
+    public static Tensor compare(Number a, Number b, @Constant CompareKind ck) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor exp(Tensor a) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static int cdiv(Number x, Number div) {
+        throw new UnsupportedOperationException();
+    }
+
+    // Conversions
+
+    public static <T extends Number> T conv(Type t, T a) {
+        throw new UnsupportedOperationException();
+    }
+
+    // Reductions
+
+    public static Tensor max(Tensor a, @Constant int axis) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static Tensor sum(Tensor a, @Constant int axis) {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TritonNumber.java b/cr-examples/triton/src/main/java/oracle/code/triton/TritonNumber.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TritonNumber.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+abstract class TritonNumber extends Number {
+    TritonNumber() {
+    }
+
+    @Override
+    public int intValue() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long longValue() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public float floatValue() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public double doubleValue() {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TritonOps.java b/cr-examples/triton/src/main/java/oracle/code/triton/TritonOps.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TritonOps.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.*;
+import java.lang.reflect.code.type.*;
+import java.util.*;
+import java.util.function.Consumer;
+
+public class TritonOps {
+
+    static abstract class TritonOp extends ExternalizableOp {
+        final TypeElement resultType;
+
+        public TritonOp(ExternalizedOp def) {
+            super(def);
+
+            this.resultType = def.resultType();
+        }
+
+        TritonOp(TritonOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.resultType = that.resultType;
+        }
+
+        TritonOp(String name, TypeElement resultType, List<? extends Value> operands) {
+            super(name, operands);
+
+            this.resultType = resultType;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    @OpFactory.OpDeclaration(ModuleOp.NAME)
+    public static final class ModuleOp extends TritonOp implements Op.Isolated {
+        public static final String NAME = "module";
+
+        final Map<String, FuncOp> table;
+        final Body body;
+
+        public ModuleOp(ExternalizedOp def) {
+            super(def);
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+            this.table = createTable(body);
+        }
+
+        ModuleOp(ModuleOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.body = that.body.transform(cc, ot).build(this);
+            this.table = createTable(body);
+        }
+
+        static Map<String, FuncOp> createTable(Body body) {
+            Map<String, FuncOp> table = new HashMap<>();
+            for (var op : body.entryBlock().ops()) {
+                if (op instanceof FuncOp fop) {
+                    table.put(fop.funcName(), fop);
+                } else if (op instanceof CoreOp.UnreachableOp _) {
+                    // no operation
+                } else {
+                    throw new IllegalArgumentException("Bad operation in module: " + op);
+                }
+            }
+            return Collections.unmodifiableMap(table);
+        }
+
+        @Override
+        public ModuleOp transform(CopyContext cc, OpTransformer ot) {
+            return new ModuleOp(this, cc, ot);
+        }
+
+        public ModuleOp transform(OpTransformer ot) {
+            return new ModuleOp(this, CopyContext.create(), ot);
+        }
+
+        ModuleOp(List<FuncOp> functions) {
+            super(NAME, JavaType.VOID,
+                    List.of());
+
+            Body.Builder bodyC = Body.Builder.of(null, FunctionType.VOID);
+            Block.Builder entryBlock = bodyC.entryBlock();
+            Map<String, FuncOp> table = new HashMap<>();
+            for (FuncOp f : functions) {
+                entryBlock.op(f);
+                table.put(f.funcName(), f);
+            }
+            entryBlock.op(CoreOp.unreachable());
+            this.table = Collections.unmodifiableMap(table);
+            this.body = bodyC.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        public Map<String, FuncOp> functionTable() {
+            return table;
+        }
+    }
+
+    @OpFactory.OpDeclaration(FuncOp.NAME)
+    public static final class FuncOp extends TritonOp implements Op.Invokable, Op.Isolated, Op.Lowerable {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final String funcName;
+            final FunctionType funcType;
+
+            Builder(Body.Builder ancestorBody, String funcName, FunctionType funcType) {
+                this.ancestorBody = ancestorBody;
+                this.funcName = funcName;
+                this.funcType = funcType;
+            }
+
+            public FuncOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, funcType);
+                c.accept(body.entryBlock());
+                return new FuncOp(funcName, body);
+            }
+        }
+
+        public static final String NAME = "tt.func";
+        public static final String ATTRIBUTE_FUNC_NAME = NAME + ".name";
+
+        final String funcName;
+        final Body body;
+
+        public static FuncOp create(ExternalizedOp def) {
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Bad op " + def.name());
+            }
+
+            String funcName = def.extractAttributeValue(ATTRIBUTE_FUNC_NAME, true,
+                    v -> switch (v) {
+                        case String s -> s;
+                        default -> throw new UnsupportedOperationException("Unsupported func name value:" + v);
+                    });
+            return new FuncOp(def, funcName);
+        }
+
+        FuncOp(ExternalizedOp def, String funcName) {
+            super(def);
+
+            this.funcName = funcName;
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        FuncOp(FuncOp that, CopyContext cc, OpTransformer oa) {
+            this(that, that.funcName, cc, oa);
+        }
+
+        FuncOp(FuncOp that, String funcName, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.funcName = funcName;
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public FuncOp transform(CopyContext cc, OpTransformer ot) {
+            return new FuncOp(this, cc, ot);
+        }
+
+        public FuncOp transform(OpTransformer ot) {
+            return new FuncOp(this, CopyContext.create(), ot);
+        }
+
+        public FuncOp transform(String funcName, OpTransformer ot) {
+            return new FuncOp(this, funcName, CopyContext.create(), ot);
+        }
+
+        FuncOp(String funcName, Body.Builder bodyBuilder) {
+            super(NAME, JavaType.VOID,
+                    List.of());
+
+            this.funcName = funcName;
+            this.body = bodyBuilder.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", funcName);
+            return Collections.unmodifiableMap(m);
+        }
+
+        @Override
+        public FunctionType invokableType() {
+            return body.bodyType();
+        }
+
+        public String funcName() {
+            return funcName;
+        }
+
+        @Override
+        public Body body() {
+            return body;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer _ignore) {
+            // Isolate body with respect to ancestor transformations
+            // and copy directly without lowering descendant operations
+            b.op(this, OpTransformer.COPYING_TRANSFORMER);
+            return b;
+        }
+    }
+
+    @OpFactory.OpDeclaration(CallOp.NAME)
+    public static final class CallOp extends TritonOp {
+        public static final String NAME = "tt.call";
+        public static final String ATTRIBUTE_FUNC_NAME = NAME + ".name";
+
+        final String funcName;
+
+        public static CallOp create(ExternalizedOp def) {
+            String funcName = def.extractAttributeValue(ATTRIBUTE_FUNC_NAME, true,
+                    v -> switch (v) {
+                        case String s -> s;
+                        default -> throw new UnsupportedOperationException("Unsupported func name value:" + v);
+                    });
+
+            return new CallOp(def, funcName);
+        }
+
+        CallOp(ExternalizedOp def, String funcName) {
+            super(def);
+
+            this.funcName = funcName;
+        }
+
+        CallOp(CallOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.funcName = that.funcName;
+        }
+
+        @Override
+        public CallOp transform(CopyContext cc, OpTransformer ot) {
+            return new CallOp(this, cc);
+        }
+
+        CallOp(String funcName, TypeElement resultType, List<Value> args) {
+            super(NAME, resultType, args);
+
+            this.funcName = funcName;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", funcName);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public String funcName() {
+            return funcName;
+        }
+    }
+
+    @OpFactory.OpDeclaration(ReduceOp.NAME)
+    public static final class ReduceOp extends TritonOp {
+        // @@@ SSA transformation does not work with nested ops
+        // implements Op.Nested {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final int axis;
+            final Value v;
+            final FunctionType reduceType;
+
+            Builder(Body.Builder ancestorBody, int axis, Value v, FunctionType reduceType) {
+                this.ancestorBody = ancestorBody;
+                this.axis = axis;
+                this.v = v;
+                this.reduceType = reduceType;
+            }
+
+            public ReduceOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, reduceType);
+                c.accept(body.entryBlock());
+                return new ReduceOp(axis, v, body);
+            }
+        }
+
+        public static final String NAME = "tt.reduce";
+        public static final String ATTRIBUTE_AXIS = "axis";
+
+        final int axis;
+        final Body reducer;
+
+        public static ReduceOp create(ExternalizedOp def) {
+            int axis = def.extractAttributeValue(ATTRIBUTE_AXIS, true,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported axis value:" + v);
+                    });
+            return new ReduceOp(def, axis);
+        }
+
+        ReduceOp(ExternalizedOp def, int axis) {
+            super(def);
+
+            this.axis = axis;
+            this.reducer = def.bodyDefinitions().get(0).build(this);
+        }
+
+        ReduceOp(ReduceOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.axis = that.axis;
+            this.reducer = that.reducer.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public ReduceOp transform(CopyContext cc, OpTransformer ot) {
+            return new ReduceOp(this, cc, ot);
+        }
+
+        ReduceOp(int axis, Value tensor, Body.Builder reducerBuilder) {
+            super(NAME, reducerBuilder.bodyType().returnType(), List.of(tensor));
+
+            this.axis = axis;
+            this.reducer = reducerBuilder.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(reducer);
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put(ATTRIBUTE_AXIS, axis);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public int axis() {
+            return axis;
+        }
+
+        public Body reducer() {
+            return reducer;
+        }
+    }
+
+    @OpFactory.OpDeclaration(ReduceReturnOp.NAME)
+    public static class ReduceReturnOp extends TritonOp implements Op.Terminating {
+        public static final String NAME = "tt.reduce.return";
+
+        public ReduceReturnOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ReduceReturnOp(ReduceReturnOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ReduceReturnOp transform(CopyContext cc, OpTransformer ot) {
+            return new ReduceReturnOp(this, cc);
+        }
+
+        ReduceReturnOp(Value r) {
+            super(NAME, JavaType.VOID, List.of(r));
+        }
+    }
+
+    @OpFactory.OpDeclaration(GetProgramIdOp.NAME)
+    public static class GetProgramIdOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.get_program_id";
+        public static final String ATTRIBUTE_AXIS = "axis";
+
+        final int axis;
+
+        public static GetProgramIdOp create(ExternalizedOp def) {
+            int axis = def.extractAttributeValue(ATTRIBUTE_AXIS, true,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported axis value:" + v);
+                    });
+            return new GetProgramIdOp(def, axis);
+        }
+
+        GetProgramIdOp(ExternalizedOp def, int axis) {
+            super(def);
+
+            this.axis = axis;
+        }
+
+        GetProgramIdOp(GetProgramIdOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.axis = that.axis;
+        }
+
+        @Override
+        public GetProgramIdOp transform(CopyContext cc, OpTransformer ot) {
+            return new GetProgramIdOp(this, cc);
+        }
+
+        GetProgramIdOp(int axis) {
+            super(NAME, JavaType.INT, List.of());
+
+            this.axis = axis;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", axis);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public int axis() {
+            return axis;
+        }
+    }
+
+    @OpFactory.OpDeclaration(MakeRangeOp.NAME)
+    public static class MakeRangeOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.make_range";
+        public static final String ATTRIBUTE_START = "start";
+        public static final String ATTRIBUTE_END = "end";
+
+        final int start;
+        final int end;
+
+        public static MakeRangeOp create(ExternalizedOp def) {
+            int start = def.extractAttributeValue(ATTRIBUTE_START, false,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported start value:" + v);
+                    });
+            int end = def.extractAttributeValue(ATTRIBUTE_END, false,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported end value:" + v);
+                    });
+            return new MakeRangeOp(def, start, end);
+        }
+
+        MakeRangeOp(ExternalizedOp def, int start, int end) {
+            super(def);
+
+            this.start = start;
+            this.end = end;
+        }
+
+        MakeRangeOp(MakeRangeOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.start = that.start;
+            this.end = that.end;
+        }
+
+        @Override
+        public MakeRangeOp transform(CopyContext cc, OpTransformer ot) {
+            return new MakeRangeOp(this, cc);
+        }
+
+        MakeRangeOp(int start, int end) {
+            super(NAME, tensorType(start, end), List.of());
+
+            this.start = start;
+            this.end = end;
+        }
+
+        static TensorType tensorType(int start, int end) {
+            return new TensorType(JavaType.INT, List.of(end - start));
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put(ATTRIBUTE_START, start);
+            m.put(ATTRIBUTE_END, end);
+            return Collections.unmodifiableMap(m);
+        }
+    }
+
+    @OpFactory.OpDeclaration(ExpandOp.NAME)
+    public static class ExpandOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.expand_dims";
+        public static final String ATTRIBUTE_AXIS = "axis";
+
+        final int axis;
+
+        public static ExpandOp create(ExternalizedOp def) {
+            int axis = def.extractAttributeValue(ATTRIBUTE_AXIS, true,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported axis value:" + v);
+                    });
+            return new ExpandOp(def, axis);
+        }
+
+        ExpandOp(ExternalizedOp def, int axis) {
+            super(def);
+
+            this.axis = axis;
+        }
+
+        ExpandOp(ExpandOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.axis = that.axis;
+        }
+
+        @Override
+        public ExpandOp transform(CopyContext cc, OpTransformer ot) {
+            return new ExpandOp(this, cc);
+        }
+
+        ExpandOp(int axis, TypeElement tensorType, Value v) {
+            super(NAME, tensorType, List.of(v));
+
+            this.axis = axis;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", axis);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public int axis() {
+            return axis;
+        }
+    }
+
+    @OpFactory.OpDeclaration(SplatOp.NAME)
+    public static class SplatOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.splat";
+
+        public SplatOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        SplatOp(SplatOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public SplatOp transform(CopyContext cc, OpTransformer ot) {
+            return new SplatOp(this, cc);
+        }
+
+        SplatOp(TypeElement tensorType, Value v) {
+            super(NAME, tensorType, List.of(v));
+        }
+    }
+
+    @OpFactory.OpDeclaration(BroadcastOp.NAME)
+    public static class BroadcastOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.broadcast";
+
+        public BroadcastOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        BroadcastOp(BroadcastOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public BroadcastOp transform(CopyContext cc, OpTransformer ot) {
+            return new BroadcastOp(this, cc);
+        }
+
+        BroadcastOp(TypeElement tensorType, Value v) {
+            super(NAME, tensorType, List.of(v));
+        }
+    }
+
+    @OpFactory.OpDeclaration(AddPtrOp.NAME)
+    public static class AddPtrOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.addptr";
+
+        public AddPtrOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        AddPtrOp(AddPtrOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AddPtrOp transform(CopyContext cc, OpTransformer ot) {
+            return new AddPtrOp(this, cc);
+        }
+
+        AddPtrOp(Value ptr, Value offset) {
+            super(NAME, ptr.type(), List.of(ptr, offset));
+        }
+    }
+
+    @OpFactory.OpDeclaration(LoadOp.NAME)
+    public static class LoadOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.load";
+
+        public LoadOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        LoadOp(LoadOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LoadOp transform(CopyContext cc, OpTransformer ot) {
+            return new LoadOp(this, cc);
+        }
+
+        LoadOp(TypeElement tensorType, Value ptr, Value mask) {
+            super(NAME, tensorType, List.of(ptr, mask));
+        }
+    }
+
+    @OpFactory.OpDeclaration(StoreOp.NAME)
+    public static class StoreOp extends TritonOp {
+        public static final String NAME = "tt.store";
+
+        public StoreOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        StoreOp(StoreOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public StoreOp transform(CopyContext cc, OpTransformer ot) {
+            return new StoreOp(this, cc);
+        }
+
+        StoreOp(Value ptr, Value v, Value mask) {
+            super(NAME, JavaType.VOID, List.of(ptr, v, mask));
+        }
+    }
+
+    @OpFactory.OpDeclaration(ReturnOp.NAME)
+    public static class ReturnOp extends TritonOp implements Op.Terminating {
+        public static final String NAME = "tt.return";
+
+        public ReturnOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ReturnOp(ReturnOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ReturnOp transform(CopyContext cc, OpTransformer ot) {
+            return new ReturnOp(this, cc);
+        }
+
+        ReturnOp() {
+            super(NAME, JavaType.VOID, List.of());
+        }
+
+        ReturnOp(Value v) {
+            super(NAME, JavaType.VOID, List.of(v));
+        }
+    }
+
+    @OpFactory.OpDeclaration(DotOp.NAME)
+    public static class DotOp extends TritonOp implements Op.Pure {
+        public static final String NAME = "tt.dot";
+
+        public DotOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        DotOp(DotOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public DotOp transform(CopyContext cc, OpTransformer ot) {
+            return new DotOp(this, cc);
+        }
+
+        DotOp(TypeElement tensorType, Value a, Value b) {
+            super(NAME, tensorType, List.of(a, b));
+        }
+    }
+
+
+    public static ModuleOp module(FuncOp... functions) {
+        return module(List.of(functions));
+    }
+
+    public static ModuleOp module(List<FuncOp> functions) {
+        return new ModuleOp(List.copyOf(functions));
+    }
+
+    public static FuncOp.Builder func(String funcName, FunctionType funcType) {
+        return new FuncOp.Builder(null, funcName, funcType);
+    }
+
+    public static FuncOp func(String funcName, Body.Builder body) {
+        return new FuncOp(funcName, body);
+    }
+
+    public static CallOp call(FuncOp func, Value... args) {
+        return call(func, List.of(args));
+    }
+
+    public static CallOp call(FuncOp func, List<Value> args) {
+        return new CallOp(func.funcName(), func.invokableType().returnType(), args);
+    }
+
+    public static ReduceOp.Builder reduce(Body.Builder ancestorBody, int axis, Value tensor,
+                                          FunctionType reduceType) {
+        return new ReduceOp.Builder(ancestorBody, axis, tensor, reduceType);
+    }
+
+    public static ReduceOp reduce(int axis, Value tensor, Body.Builder reducerBuilder) {
+        return new ReduceOp(axis, tensor, reducerBuilder);
+    }
+
+    public static ReduceReturnOp reduceReturn(Value r) {
+        return new ReduceReturnOp(r);
+    }
+
+    public static GetProgramIdOp getProgramId(int axis) {
+        // @@@ 1 <= axis <= 3
+        return new GetProgramIdOp(axis);
+    }
+
+    public static MakeRangeOp makeRange(int start, int end) {
+        // @@@ 0 <= start < end
+        return new MakeRangeOp(start, end);
+    }
+
+    public static ExpandOp expand(int axis, TypeElement tensorType, Value v) {
+        return new ExpandOp(axis, tensorType, v);
+    }
+
+    // v is scalar
+    public static SplatOp splat(TypeElement tensorType, Value v) {
+        return new SplatOp(tensorType, v);
+    }
+
+    // v is tensor
+    public static BroadcastOp broadcast(TypeElement tensorType, Value v) {
+        return new BroadcastOp(tensorType, v);
+    }
+
+    public static AddPtrOp addptr(Value ptr, Value offset) {
+        return new AddPtrOp(ptr, offset);
+    }
+
+    public static LoadOp load(TypeElement tensorType, Value ptr, Value mask) {
+        return new LoadOp(tensorType, ptr, mask);
+    }
+
+    public static StoreOp store(Value ptr, Value v, Value mask) {
+        return new StoreOp(ptr, v, mask);
+    }
+
+    public static ReturnOp return_() {
+        return new ReturnOp();
+    }
+
+    public static ReturnOp return_(Value v) {
+        return new ReturnOp(v);
+    }
+
+    public static DotOp dot(TypeElement tensorType, Value a, Value b) {
+        return new DotOp(tensorType, a, b);
+    }
+
+
+    // Operation and type factories
+
+    public static final OpFactory FACTORY = OpFactory.OP_FACTORY.get(TritonOps.class);
+
+    static final TypeElementFactory TRITON_TYPE_FACTORY = new TypeElementFactory() {
+        @Override
+        public TypeElement constructType(TypeElement.ExternalizedTypeElement tree) {
+            return switch (tree.identifier()) {
+                case PtrType.NAME -> {
+                    if (tree.arguments().size() != 1) {
+                        throw new IllegalArgumentException();
+                    }
+
+                    TypeElement v = TRITON_JAVA_TYPE_FACTORY.constructType(tree.arguments().getFirst());
+                    if (v == null) {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+                    if (v instanceof JavaType || v instanceof TritonType) {
+                        yield new PtrType(v);
+                    } else {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+                }
+                case TensorType.NAME -> {
+                    if (tree.arguments().size() < 2) {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+
+                    List<Integer> shape = new ArrayList<>();
+                    for (int i = 0; i < tree.arguments().size() - 1; i++) {
+                        TypeElement.ExternalizedTypeElement a = tree.arguments().get(i);
+                        if (!a.identifier().startsWith("x")) {
+                            throw new IllegalArgumentException("Bad type: " + tree);
+                        }
+                        int d;
+                        try {
+                            d = Integer.parseInt(a.identifier().substring(1));
+                        } catch (NumberFormatException e) {
+                            throw new IllegalArgumentException("Bad type: " + tree, e);
+                        }
+                        shape.add(d);
+                    }
+
+                    TypeElement v = TRITON_JAVA_TYPE_FACTORY.constructType(tree.arguments().getLast());
+                    if (v == null) {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+                    if (v instanceof JavaType || v instanceof TritonType) {
+                        yield new TensorType(v, shape);
+                    } else {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+                }
+                default -> null;
+            };
+        }
+    };
+
+    // Triton types then Java types
+    static final TypeElementFactory TRITON_JAVA_TYPE_FACTORY =
+            TRITON_TYPE_FACTORY.andThen(CoreTypeFactory.JAVA_TYPE_FACTORY);
+
+    // Triton types then Java types, combined with code model types
+    public static final TypeElementFactory TYPE_FACTORY =
+            CoreTypeFactory.codeModelTypeFactory(TRITON_JAVA_TYPE_FACTORY);
+
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TritonTestOps.java b/cr-examples/triton/src/main/java/oracle/code/triton/TritonTestOps.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TritonTestOps.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.op.OpFactory;
+import java.lang.reflect.code.op.ExternalizableOp;
+import java.lang.reflect.code.type.JavaType;
+import java.util.List;
+
+public class TritonTestOps {
+
+    @OpFactory.OpDeclaration(ConsumeOp.NAME)
+    public static class ConsumeOp extends ExternalizableOp {
+        public static final String NAME = "tt.consume";
+
+        public ConsumeOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ConsumeOp(ConsumeOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ConsumeOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConsumeOp(this, cc);
+        }
+
+        ConsumeOp(List<Value> values) {
+            super(NAME, values);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+
+    public static final OpFactory FACTORY = OpFactory.OP_FACTORY.get(TritonTestOps.class);
+
+    public static ConsumeOp consume(Value... operands) {
+        return consume(List.of(operands));
+    }
+
+    public static ConsumeOp consume(List<Value> operands) {
+        return new ConsumeOp(operands);
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TritonTransformer.java b/cr-examples/triton/src/main/java/oracle/code/triton/TritonTransformer.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TritonTransformer.java
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.type.VarType;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+
+public final class TritonTransformer {
+    private TritonTransformer() {}
+
+    static final JavaType TYPE_Triton = JavaType.type(Triton.class);
+
+    static final JavaType TYPE_Triton_Test = JavaType.ofString("oracle.code.triton.TritonTest");
+
+    static final JavaType TYPE_Tensor = JavaType.type(Tensor.class);
+
+    static final JavaType TYPE_J_L_MATH = JavaType.type(Math.class);
+
+    public static <O extends Op & Op.Invokable>
+    TritonOps.ModuleOp tritonModule(O kernel,
+                                    TypeElement rType,
+                                    List<? extends TypeElement> argTypes) {
+        Map<String, TritonOps.FuncOp> fsymTable = new LinkedHashMap<>();
+        tritonFunction(kernel, rType, argTypes, fsymTable);
+        return TritonOps.module(fsymTable.values().stream().toList());
+    }
+
+    public static <O extends Op & Op.Invokable>
+    TritonOps.FuncOp tritonFunction(O javaKernel,
+                                    TypeElement rType,
+                                    List<? extends TypeElement> argTypes,
+                                    Map<String, TritonOps.FuncOp> fsymTable) {
+        String name = (javaKernel instanceof FuncOp f) ? f.funcName() : "kernel";
+        String signature = signature(name, rType, argTypes);
+        if (fsymTable.containsKey(signature)) {
+            return fsymTable.get(signature);
+        }
+
+        System.out.println(javaKernel.toText());
+
+        Map<Value, TypeElement> valueTypeMap = new HashMap<>();
+        Map<Op, Object> opData = new HashMap<>();
+        TritonTransformer.typeCheckKernel(javaKernel, argTypes, valueTypeMap, opData);
+        TritonTransformer.printTypeMap(javaKernel, valueTypeMap);
+
+        return TritonTransformer.transformToTritonFunction(javaKernel, signature,
+                rType, valueTypeMap, opData,
+                fsymTable);
+    }
+
+    static String signature(String name, TypeElement rType, List<? extends TypeElement> argTypes) {
+        StringBuilder sb = new StringBuilder(name);
+
+        for (TypeElement argType : argTypes) {
+            sb.append("_");
+            if (argType instanceof ConstantType ct) {
+                sb.append(ct.value());
+            } else {
+                sb.append(argType);
+            }
+        }
+        sb.append("_");
+        sb.append(rType);
+        return sb.toString();
+    }
+
+    public static <O extends Op & Op.Invokable> void typeCheckKernel(
+            O kernel, List<? extends TypeElement> argTypes,
+            Map<Value, TypeElement> valueTypeMap, Map<Op, Object> opData) {
+        kernel.traverse(null, CodeElement.opVisitor((o, op) -> {
+            switch (op) {
+                case Op.Invokable fop -> {
+                    List<Block.Parameter> parameters = fop.body().entryBlock().parameters();
+                    for (int i = 0; i < parameters.size(); i++) {
+                        valueTypeMap.put(parameters.get(i), argTypes.get(i));
+                    }
+                }
+                case VarOp _, VarAccessOp.VarLoadOp _ -> {
+                    Value init = op.operands().get(0);
+                    valueTypeMap.put(op.result(), valueTypeMap.get(init));
+                }
+                case VarAccessOp.VarStoreOp _ -> {
+                    Value var = op.operands().get(0);
+                    TypeElement varType = valueTypeMap.get(var);
+                    Value v = op.operands().get(1);
+                    TypeElement vType = valueTypeMap.get(v);
+                    if (!varType.equals(vType)) {
+                        throw new IllegalStateException("Storing to variable with different type: "
+                                + varType + " <- " + vType);
+                    }
+
+                    valueTypeMap.put(op.result(), valueTypeMap.get(var));
+                }
+                case ConstantOp cop -> {
+                    valueTypeMap.put(op.result(), new ConstantType(op.result().type(), cop.value()));
+                }
+                case ArithmeticOperation _ -> {
+                    TypeElement t = checkWithTypeInterpreter(op, op.opName(), valueTypeMap);
+                    valueTypeMap.put(op.result(), t);
+                }
+                case FieldAccessOp.FieldLoadOp flop -> {
+                    if (!flop.operands().isEmpty()) {
+                        throw new IllegalStateException("Unsupported field load: " + flop.fieldDescriptor());
+                    }
+
+                    Field f;
+                    try {
+                        f = flop.fieldDescriptor().resolveToMember(MethodHandles.lookup());
+                    } catch (ReflectiveOperationException e) {
+                        throw new IllegalStateException("Unsupported field load: " + flop.fieldDescriptor(), e);
+                    }
+                    Object value;
+                    try {
+                        value = f.get(null);
+                    } catch (IllegalAccessException e) {
+                        throw new IllegalStateException("Unsupported field load: " + f, e);
+                    }
+                    valueTypeMap.put(op.result(), new ConstantType(JavaType.type(f.getType()), value));
+                }
+                case InvokeOp iop when iop.invokeDescriptor().refType().equals(JavaType.J_L_INTEGER) -> {
+                    // Box
+                    if (iop.invokeDescriptor().name().equals("valueOf")) {
+                        Value a = op.operands().get(0);
+                        valueTypeMap.put(op.result(), valueTypeMap.get(a));
+                    } else {
+                        throw new UnsupportedOperationException("Unsupported invocation on Integer: " + iop.invokeDescriptor());
+                    }
+                }
+                case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_J_L_MATH) -> {
+                    String name = iop.invokeDescriptor().name();
+                    if (name.equals("max") || name.equals("min")) {
+                        Value a = op.operands().get(0);
+                        valueTypeMap.put(op.result(), valueTypeMap.get(a));
+                    } else {
+                        throw new UnsupportedOperationException("Unsupported invocation on Math: " + iop.invokeDescriptor());
+                    }
+                }
+                case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Tensor) -> {
+                    if (iop.invokeDescriptor().name().equals("type")) {
+                        Value a = op.operands().get(0);
+                        valueTypeMap.put(op.result(), valueTypeMap.get(a));
+                    } else {
+                        throw new UnsupportedOperationException("Unsupported invocation on Tensor: " + iop.invokeDescriptor());
+                    }
+                }
+                case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Triton) -> {
+                    TypeElement t = checkWithTypeInterpreter(op, iop.invokeDescriptor().name(), valueTypeMap);
+                    valueTypeMap.put(op.result(), t);
+                }
+                case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Triton_Test) -> {
+                    TypeElement t = checkWithTypeInterpreter(op, iop.invokeDescriptor().name(), valueTypeMap);
+                    valueTypeMap.put(op.result(), t);
+                }
+                case ExtendedOp.JavaForOp fop -> {
+                    SimpleCountedForLoopInfo li = new SimpleCountedForLoopInfo(fop);
+                    opData.put(fop, li);
+
+                    TypeElement type = fop.init().yieldType();
+                    if (type instanceof VarType vt && vt.valueType().equals(JavaType.INT)) {
+                        for (Body b : List.of(fop.cond(), fop.update(), fop.loopBody())) {
+                            valueTypeMap.put(b.entryBlock().parameters().get(0), JavaType.INT);
+                        }
+                    } else {
+                        throw new IllegalStateException();
+                    }
+                }
+                case TestOperation _ -> {
+                }
+                case ExtendedOp.JavaContinueOp _ -> {
+                }
+                case YieldOp _ -> {
+                }
+                case ReturnOp _ -> {
+                }
+                default -> throw new UnsupportedOperationException("Unsupported operation: " + op);
+            }
+
+            return null;
+        }));
+    }
+
+    static TypeElement checkWithTypeInterpreter(Op op, String name, Map<Value, TypeElement> valueTypeMap) {
+        // Obtain associated type-based method
+        MethodHandle mh;
+        try {
+            Optional<Method> om = Stream.of(TritonTypeInterpreter.class.getDeclaredMethods())
+                    .filter(m -> m.getName().equals(name))
+                    .findFirst();
+            mh = MethodHandles.lookup().unreflect(
+                    om.orElseThrow(() -> new NoSuchMethodException(name)));
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(name, e);
+        }
+
+        // Invoke with the values' types
+        List<TypeElement> operandTypes = op.operands().stream().map(valueTypeMap::get).toList();
+        try {
+            return (TypeElement) mh.invokeWithArguments(operandTypes.toArray(Object[]::new));
+        } catch (Throwable e) {
+            throw new IllegalStateException(mh.toString(), e);
+        }
+    }
+
+    // @@@ type check tensor shapes
+    static class TritonTypeInterpreter {
+        private TritonTypeInterpreter() {
+        }
+
+        //                 int programId(@Constant int axis) {
+        public static JavaType programId(ConstantType axis) {
+            assert axis.cType().equals(JavaType.INT);
+            int axisValue = (int) axis.value();
+            if (axisValue < 0 || axisValue > 3) {
+                throw new IllegalStateException();
+            }
+
+            return JavaType.INT;
+        }
+
+        //                Tensor arange(@Constant int start, @Constant int end)
+        public static TensorType arange(ConstantType start, ConstantType end) {
+            assert start.cType().equals(JavaType.INT);
+            assert end.cType().equals(JavaType.INT);
+
+            int startValue = (int) start.value();
+            int endValue = (int) end.value();
+
+            return new TensorType(JavaType.INT, List.of(endValue - startValue));
+        }
+
+        //                Tensor expand(Tensor a, int axis) {
+        public static TensorType expand(TensorType a, ConstantType axis) {
+            assert axis.cType().equals(JavaType.INT);
+            int axisValue = (int) axis.value();
+
+            List<Integer> s = new ArrayList<>(a.shape());
+            if (axisValue < s.size()) {
+                s.add(axisValue, 1);
+            } else {
+                for (int i = 0; i <= (axisValue - s.size()); i++) {
+                    s.add(1);
+                }
+            }
+            return new TensorType(a.eType(), s);
+        }
+
+        //                Tensor load(Tensor ptr, Tensor mask)
+        public static TensorType load(TensorType ptr, TensorType mask) {
+            checkTensorShape(ptr, mask);
+            if (ptr.eType() instanceof PtrType eptr) {
+                return new TensorType(eptr.rType(), ptr.shape());
+            }
+
+            throw new IllegalStateException();
+        }
+
+        //            void store(Tensor ptr, Tensor value, Tensor mask)
+        public static void store(TensorType ptr, TensorType value, TensorType mask) {
+            if (!(ptr.eType() instanceof PtrType)) {
+                throw new IllegalStateException();
+            }
+        }
+
+        //                Tensor zeros(TensorType type)
+        public static TensorType zeros(ConstantType eType, ConstantType... cShape) {
+            List<Integer> shape = Stream.of(cShape).map(s -> (int) s.value()).toList();
+            return new TensorType((TypeElement) eType.value(), shape);
+        }
+
+        //                Tensor broadcast(Object o, TensorType type)
+        public static TensorType broadcast(TypeElement o, TensorType type) {
+            if (o instanceof TensorType ot) {
+                // @@@
+                if (ot.shape().size() != type.shape().size()) {
+                    throw new IllegalStateException();
+                }
+                o = ot.eType();
+            } if (o instanceof ConstantType oc) {
+                o = oc.cType();
+            }
+            return new TensorType(o, type.shape());
+        }
+
+        public static TensorType joinShape(TensorType a, TensorType b) {
+            return checkTensorTypes(a, b);
+        }
+
+        //          Tensor add(Number a, Number b)
+        //             Ptr add(Ptr a, int offset)
+        public static TypeElement add(TypeElement a, TypeElement b) {
+            // @@@ Pass additional argument for checking ptr
+            return binary(a, b);
+        }
+
+        public static TypeElement sub(TypeElement a, TypeElement b) {
+            return binary(a, b);
+        }
+
+        public static TypeElement mul(TypeElement a, TypeElement b) {
+            return binary(a, b);
+        }
+
+        public static TypeElement div(TypeElement a, TypeElement b) {
+            return binary(a, b);
+        }
+
+        public static TypeElement mod(TypeElement a, TypeElement b) {
+            return binary(a, b);
+        }
+
+        public static TypeElement and(TypeElement a, TypeElement b) {
+            return binary(a, b);
+        }
+
+        public static TypeElement cdiv(TypeElement a, TypeElement b) {
+            a = reduceScalarType(a);
+            b = reduceScalarType(b);
+            if (!a.equals(JavaType.INT) && !b.equals(JavaType.INT)) {
+                throw new IllegalStateException();
+            }
+            return a;
+        }
+
+        //          Number conv(Type t, Number a) {
+        public static TypeElement conv(ConstantType eType, TypeElement a) {
+            return convTypes(eType, a);
+        }
+
+        public static TypeElement convTypes(ConstantType eType, TypeElement a) {
+            if (a instanceof TensorType tb) {
+                TypeElement e = convScalarTypes(eType, tb.eType());
+                return new TensorType(e, tb.shape());
+            } else {
+                return convScalarTypes(eType, a);
+            }
+        }
+
+        public static TypeElement convScalarTypes(ConstantType eType, TypeElement a) {
+            TypeElement t = (TypeElement) eType.value();
+            if (t.equals(Float16.FLOAT_16_TYPE) && a.equals(JavaType.FLOAT)) {
+                return Float16.FLOAT_16_TYPE;
+            } else if (t.equals(a)) {
+                return t;
+            } else {
+                // @@@ Conversions;
+                throw new IllegalStateException();
+            }
+        }
+
+        //          Tensor exp(Tensor a)
+        public static TypeElement exp(TypeElement a) {
+            return unary(a);
+        }
+
+        static TypeElement unary(TypeElement a) {
+            return a;
+        }
+
+        //                Tensor compare(Number a, Number b, @Constant CompareKind ck) {
+        public static TypeElement compare(TypeElement a, TypeElement b, ConstantType kind) {
+            assert kind.cType().equals(JavaType.type(Triton.CompareKind.class));
+
+            return binary(a, b);
+        }
+
+        //                Tensor dot(Tensor a, Tensor b)
+        public static TensorType dot(TensorType a, TensorType b) {
+            if (a.shape().size() != 2 || b.shape().size() != 2) {
+                throw new IllegalStateException();
+            }
+
+            if (!a.shape().get(1).equals(b.shape().get(0))) {
+                throw new IllegalStateException();
+            }
+
+            if (a.eType() != b.eType()) {
+                // @@@ Conversion, type checking
+                throw new IllegalStateException();
+            }
+
+            // Computed result is tensor of floats, regardless of inputs
+            return new TensorType(JavaType.FLOAT, List.of(a.shape().get(0), b.shape().get(1)));
+        }
+
+
+        //                Tensor max(Tensor a, @Constant int axis) {
+        public static TypeElement max(TensorType a, ConstantType axis) {
+            return reduce(a, axis);
+        }
+
+        //                Tensor sum(Tensor a, @Constant int axis) {
+        public static TypeElement sum(TensorType a, ConstantType axis) {
+            return reduce(a, axis);
+        }
+
+        static TypeElement reduce(TensorType a, ConstantType axis) {
+            assert axis.cType().equals(JavaType.INT);
+            int axisValue = (int) axis.value();
+            if (axisValue < 0 || axisValue > 3) {
+                throw new IllegalStateException();
+            }
+
+            List<Integer> reduceShape = new ArrayList<>();
+            for (int i = 0; i < a.shape().size(); i++) {
+                if (i != axisValue) {
+                    reduceShape.add(a.shape().get(i));
+                } else {
+                    reduceShape.add(1);
+                }
+            }
+
+            if (reduceShape.size() == 1 && reduceShape.getFirst() == 1) {
+                return a.eType();
+            } else {
+                return new TensorType(a.eType(), reduceShape);
+            }
+        }
+
+        // @@@ Test
+        public static void consume(TypeElement a) {
+        }
+
+
+        static TypeElement binary(TypeElement a, TypeElement b) {
+            if (a instanceof TensorType ta && b instanceof TensorType tb) {
+                return checkTensorTypes(ta, tb);
+            } else if (a instanceof TensorType ta) {
+                return new TensorType(checkScalarTypes(ta.eType(), b), ta.shape());
+            } else if (b instanceof TensorType tb) {
+                return new TensorType(checkScalarTypes(a, tb.eType()), tb.shape());
+            } else {
+                return checkScalarTypes(a, b);
+            }
+        }
+
+        static TensorType checkTensorTypes(TensorType a, TensorType b) {
+            List<Integer> s = checkTensorShape(a, b);
+            TypeElement e = checkScalarTypes(a.eType(), b.eType());
+            return new TensorType(e, s);
+        }
+
+        static List<Integer> checkTensorShape(TensorType a, TensorType b) {
+            if (a.shape().size() != b.shape().size()) {
+                // Shape mismatch
+                throw new IllegalStateException();
+            }
+
+            List<Integer> s = new ArrayList<>();
+            for (int i = 0; i < a.shape().size(); i++) {
+                int ad = a.shape().get(i);
+                int bd = b.shape().get(i);
+
+                // Expand dimensions
+                int d;
+                if (ad == bd) {
+                    d = ad;
+                } else {
+                    if (ad != 1 && bd == 1) {
+                        d = ad;
+                    } else if (ad == 1) {
+                        d = bd;
+                    } else {
+                        // Shape mismatch
+                        throw new IllegalStateException();
+                    }
+                }
+
+                s.add(d);
+            }
+
+            return s;
+        }
+
+        static TypeElement checkScalarTypes(TypeElement a, TypeElement b) {
+            // @@@ Optional ptr checking
+            if (a instanceof PtrType) {
+                if (!b.equals(JavaType.INT)) {
+                    throw new IllegalStateException();
+                }
+            } else if (b instanceof PtrType) {
+                // Pointer must be first argument
+                throw new IllegalStateException();
+            } else if (a instanceof ConstantType || b instanceof ConstantType) {
+                return checkScalarTypes(reduceScalarType(a), reduceScalarType(b));
+            } else if (!a.equals(b)) {
+                // @@@ Conversion
+                throw new IllegalStateException();
+            }
+            return a;
+        }
+
+        static TypeElement reduceScalarType(TypeElement a) {
+            return a instanceof ConstantType ct ? ct.cType() : a;
+        }
+    }
+
+    public static <O extends Op & Op.Invokable> TritonOps.FuncOp transformToTritonFunction(
+            O kernel,
+            String signature,
+            TypeElement rType,
+            Map<Value, TypeElement> valueTypeMap, Map<Op, Object> opData,
+            Map<String, TritonOps.FuncOp> fsymTable) {
+        TritonOps.FuncOp ttKernel = TritonOps.func(signature, functionType(rType))
+                .body(fblock -> {
+                    // Process kernel parameters
+                    List<Value> args = new ArrayList<>();
+                    for (Block.Parameter kp : kernel.body().entryBlock().parameters()) {
+                        TypeElement type = valueTypeMap.get(kp);
+                        if (type instanceof ConstantType ct) {
+                            // Constant
+                            Op.Result cr = fblock.op(ArithMathOps.constant(
+                                    ct.cType(), ct.value()));
+                            args.add(cr);
+                        } else {
+                            args.add(fblock.parameter(type));
+                        }
+                    }
+
+                    // Transform kernel body
+                    fblock.transformBody(kernel.body(), args, (kblock, op) -> {
+                        return transformToTritonOperation(kblock, op, valueTypeMap, opData, fsymTable);
+                    });
+                });
+
+        ttKernel = cleanup(ttKernel);
+        fsymTable.put(ttKernel.funcName(), ttKernel);
+        return ttKernel;
+    }
+
+    static Block.Builder transformToTritonOperation(Block.Builder kblock, Op op,
+                                                    Map<Value, TypeElement> valueTypeMap, Map<Op, Object> opData,
+                                                    Map<String, TritonOps.FuncOp> fsymTable) {
+        // @@@ Avoid constructing for each operation -- block builder passed as argument or a scoped value
+        TritonBuilderInterpreter tbi = new TritonBuilderInterpreter(fsymTable, kblock);
+        CopyContext cc = kblock.context();
+        switch (op) {
+            case VarOp varOp -> {
+                // @@@ Cannot copy op because the result type
+                //     is derived from init type
+                Value init = cc.getValue(op.operands().get(0));
+                Op.Result r = kblock.op(var(varOp.varName(), init));
+                cc.mapValue(op.result(), r);
+            }
+            case ConstantOp cop -> {
+                TypeElement t = valueTypeMap.get(cop.result());
+                if (t instanceof ConstantType ct) {
+                    Op.Result r = kblock.op(ArithMathOps.constant(
+                            ct.cType(), ct.value()));
+                    cc.mapValue(op.result(), r);
+                } else {
+                    kblock.op(op);
+                }
+            }
+            case ArithmeticOperation _ -> {
+                Value result = tbi.build(op, op.opName(), valueTypeMap);
+                if (result != null) {
+                    cc.mapValue(op.result(), result);
+                }
+            }
+            case InvokeOp iop when iop.invokeDescriptor().refType().equals(JavaType.J_L_INTEGER) -> {
+                // Replace box with its value
+                Value a = cc.getValue(op.operands().get(0));
+                cc.mapValue(op.result(), a);
+            }
+            case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_J_L_MATH) -> {
+                String name = iop.invokeDescriptor().name();
+                if (name.equals("max")) {
+                    Value a = cc.getValue(op.operands().get(0));
+                    Value b = cc.getValue(op.operands().get(1));
+
+                    Op.Result result = kblock.op(ArithMathOps.maximum(a, b));
+                    cc.mapValue(op.result(), result);
+                } else if (name.equals("min")) {
+                    Value a = cc.getValue(op.operands().get(0));
+                    Value b = cc.getValue(op.operands().get(1));
+
+                    Op.Result result = kblock.op(ArithMathOps.minimum(a, b));
+                    cc.mapValue(op.result(), result);
+                }
+            }
+            case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Tensor) -> {
+                if (iop.invokeDescriptor().name().equals("type")) {
+                    // Replace with constant operation to produce tensor type.
+                    // Result may be used, but transitively it will be removed due to no uses
+                    // contributing to the computation
+                    Value a = op.operands().get(0);
+                    TensorType aType = (TensorType) valueTypeMap.get(a);
+                    Op.Result result = kblock.op(CoreOp.constant(iop.resultType(), aType));
+                    cc.mapValue(op.result(), result);
+                    valueTypeMap.put(result, aType);
+                }
+                // Remove
+            }
+            case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Triton) -> {
+                Value result = tbi.build(op, iop.invokeDescriptor().name(), valueTypeMap);
+                if (result != null) {
+                    cc.mapValue(op.result(), result);
+                }
+            }
+            case InvokeOp iop when iop.invokeDescriptor().refType().equals(TYPE_Triton_Test) -> {
+                Value result = tbi.build(op, iop.invokeDescriptor().name(), valueTypeMap);
+                if (result != null) {
+                    cc.mapValue(op.result(), result);
+                }
+            }
+            case ExtendedOp.JavaForOp fop -> {
+                transformToSCFFor(cc, kblock, fop, valueTypeMap, opData, fsymTable);
+            }
+            case ReturnOp rop -> {
+                if (rop.operands().isEmpty()) {
+                    kblock.op(TritonOps.return_());
+                } else {
+                    kblock.op(TritonOps.return_(
+                            cc.getValue(rop.returnValue())));
+                }
+            }
+            default -> kblock.op(op);
+        }
+        return kblock;
+    }
+
+    static void transformToSCFFor(CopyContext cc, Block.Builder kblock, ExtendedOp.JavaForOp fop,
+                                  Map<Value, TypeElement> valueTypeMap, Map<Op, Object> opData,
+                                  Map<String, TritonOps.FuncOp> fsymTable) {
+        Body body = fop.loopBody();
+
+        // Hoist expressions for start, end, and step
+        SimpleCountedForLoopInfo li = (SimpleCountedForLoopInfo) opData.get(fop);
+        Value start = null;
+        for (Op o : li.startExpression()) {
+            transformToTritonOperation(kblock, o, valueTypeMap, opData, fsymTable);
+            start = cc.getValue(o.result());
+        }
+        Value end = null;
+        for (Op o : li.endExpression()) {
+            transformToTritonOperation(kblock, o, valueTypeMap, opData, fsymTable);
+            end = cc.getValue(o.result());
+        }
+        Value step = null;
+        for (Op o : li.stepExpression()) {
+            transformToTritonOperation(kblock, o, valueTypeMap, opData, fsymTable);
+            step = cc.getValue(o.result());
+        }
+
+        // Obtain captured vars
+        // true == stores
+        // false == loads only
+        Map<Boolean, Set<Value>> capturedVars = capturedVars(body);
+        Set<Value> capturedAndStoredVars = capturedVars.get(true);
+
+        // Get load values
+        // Loaded values are hoisted out of the loop body
+        Map<Value, Value> loadValues = new HashMap<>();
+        for (Value v : capturedVars.get(false)) {
+            Value load = kblock.op(varLoad(cc.getValue(v)));
+            valueTypeMap.put(load, valueTypeMap.get(v));
+            loadValues.put(v, load);
+        }
+
+        // Get iteration values -- represented by captured vars that are stored to in the loop
+        // The SCF for operation returns the iteration values of the last loop iteration, which
+        // are then to be stored to the iteration variables
+        List<Value> iterValues = new ArrayList<>();
+        for (Value v : capturedAndStoredVars) {
+            iterValues.add(kblock.op(varLoad(cc.getValue(v))));
+        }
+
+        // @@@ Build in java code model, then transform?
+        SCFOps.ForOp scffor = SCFOps.for_(kblock.parentBody(), start, end, step, iterValues)
+                // Ensure existing context is used
+                .body(CopyContext.create(cc), builder -> {
+                    // Create index var initialized from entry block parameter
+                    Value index = builder.parameters().get(0);
+                    valueTypeMap.put(index, JavaType.INT);
+                    Value varIndex = builder.op(var("index", index));
+                    valueTypeMap.put(varIndex, JavaType.INT);
+                    builder.context().mapValue(body.entryBlock().parameters().get(0), varIndex);
+
+                    // Create iter vars initialized from entry block parameters
+                    int pi = 1;
+                    for (Value v : capturedAndStoredVars) {
+                        TypeElement type = valueTypeMap.get(v);
+                        Value iter = builder.parameters().get(pi++);
+                        valueTypeMap.put(iter, type);
+                        Value varIter = builder.op(var(Integer.toString(pi), iter));
+                        valueTypeMap.put(varIter, type);
+                        builder.context().mapValue(v, varIter);
+                    }
+
+                    // Transform the Java for body into the SCF for body
+                    builder.transformBody(body, List.of(), (block, op) -> {
+                        // Yield iter values
+                        if (op instanceof ExtendedOp.JavaContinueOp) {
+                            // Replace with yield of loaded vars
+                            List<Value> yieldValues = new ArrayList<>();
+                            for (Value value : capturedAndStoredVars) {
+                                Value varIter = block.context().getValue(value);
+                                Value v = block.op(varLoad(varIter));
+                                yieldValues.add(v);
+                            }
+                            block.op(SCFOps.yield_(yieldValues));
+                        } else if (op instanceof VarAccessOp.VarLoadOp) {
+                            // Replace with value loaded immediately before loop
+                            Value v = op.operands().get(0);
+                            if (capturedVars.get(false).contains(v)) {
+                                block.context().mapValue(op.result(), loadValues.get(v));
+                            } else {
+                                block.op(op);
+                            }
+                        } else {
+                            block = transformToTritonOperation(block, op, valueTypeMap, opData, fsymTable);
+                        }
+                        return block;
+                    });
+                });
+        Op.Result forResult = kblock.op(scffor);
+
+        // Assign back result to iter vars
+        if (capturedAndStoredVars.size() == 1) {
+            for (Value v : capturedAndStoredVars) {
+                kblock.op(varStore(cc.getValue(v), forResult));
+            }
+        } else {
+            int i = 0;
+            for (Value v : capturedAndStoredVars) {
+                kblock.op(varStore(cc.getValue(v),
+                        kblock.op(tupleLoad(forResult, i++))));
+            }
+        }
+    }
+
+    static Map<Boolean, Set<Value>> capturedVars(Body body) {
+        Map<Boolean, Set<Value>> capturedValues = new HashMap<>();
+        capturedValues.put(false, new LinkedHashSet<>());
+        capturedValues.put(true, new LinkedHashSet<>());
+
+        capturedVars(capturedValues, new ArrayDeque<>(), body);
+        return capturedValues;
+    }
+
+    static void capturedVars(Map<Boolean, Set<Value>> capturedVars, Deque<Body> bodyStack, Body body) {
+        bodyStack.push(body);
+
+        for (Block b : body.blocks()) {
+            for (Op op : b.ops()) {
+                // @@@ Nested bodies
+                if (!op.bodies().isEmpty()) {
+                    throw new IllegalStateException();
+                }
+//                for (Body childBody : op.bodies()) {
+//                    capturedAndUpdatedVars(capturedValues, bodyStack, childBody);
+//                }
+
+                if (op instanceof VarAccessOp) {
+                    Value v = op.operands().get(0);
+                    if (!bodyStack.contains(v.declaringBlock().parentBody())) {
+                        if (op instanceof VarAccessOp.VarStoreOp) {
+                            capturedVars.get(true).add(v);
+                            capturedVars.get(false).remove(v);
+                        } else if (!capturedVars.get(true).contains(v)) {
+                            capturedVars.get(false).add(v);
+                        }
+                    }
+                }
+            }
+        }
+
+        bodyStack.pop();
+    }
+
+    public static final ScopedValue<Boolean> SV_SSA = ScopedValue.newInstance();
+
+    static TritonOps.FuncOp cleanup(TritonOps.FuncOp f) {
+        // Remove var ops
+        boolean doSSA = SV_SSA.isBound() ? SV_SSA.get() : true;
+        if (doSSA) {
+            f = SSA.transform(f);
+        }
+        // Remove unused ops
+        f = f.transform((fblock, op) -> {
+            if (op instanceof Op.Pure && op.result().uses().isEmpty()) {
+                return fblock;
+            } else if (op instanceof VarAccessOp.VarLoadOp && op.result().uses().isEmpty()) {
+                return fblock;
+            }
+
+            fblock.op(op);
+            return fblock;
+        });
+        return f;
+    }
+
+    static class TritonBuilderInterpreter {
+        final Map<String, TritonOps.FuncOp> fsymTable;
+        final Block.Builder block;
+
+        TritonBuilderInterpreter(Map<String, TritonOps.FuncOp> fsymTable, Block.Builder block) {
+            this.fsymTable = fsymTable;
+            this.block = block;
+        }
+
+        Value build(Op op, String name, Map<Value, TypeElement> valueTypeMap) {
+            // Obtain associated type-based method
+            MethodHandle mh;
+            try {
+                Optional<Method> om = Stream.of(TritonBuilderInterpreter.class.getDeclaredMethods())
+                        .filter(m -> m.getName().equals(name))
+                        .findFirst();
+                mh = MethodHandles.lookup().unreflect(
+                        om.orElseThrow(() -> new NoSuchMethodException(name)));
+            } catch (ReflectiveOperationException e) {
+                throw new IllegalStateException(e);
+            }
+
+            List<Object> iArgs = new ArrayList<>();
+            iArgs.add(this);
+            iArgs.add(valueTypeMap.get(op.result()));
+            iArgs.add(op.result());
+            for (Value o : op.operands()) {
+                iArgs.add(valueTypeMap.get(o));
+                iArgs.add(o);
+            }
+            try {
+                return (Value) mh.invokeWithArguments(iArgs.toArray(Object[]::new));
+            } catch (Throwable e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+
+        public Value programId(TypeElement rType, Op.Result r,
+                               ConstantType axisType, Value axis) {
+            return block.op(TritonOps.getProgramId(
+                    (int) axisType.value()));
+        }
+
+        public Value arange(TensorType rType, Op.Result r,
+                            ConstantType startType, Value start,
+                            ConstantType endType, Value end) {
+            return block.op(TritonOps.makeRange(
+                    (int) startType.value(),
+                    (int) endType.value()));
+        }
+
+        public Value expand(TensorType rType, Op.Result r,
+                            TensorType aType, Value a,
+                            ConstantType axisType, Value axis) {
+            return block.op(TritonOps.expand(
+                    (int) axisType.value(),
+                    rType,
+                    block.context().getValue(a)));
+        }
+
+        public Value zeros(TensorType rType, Op.Result r,
+                           ConstantType aType, Value a,
+                           Object... constantsAndValues) {
+            Object zero;
+            try {
+                JavaType zeroType = (JavaType) aType.value();
+                zero = MethodHandles.zero((Class<?>) zeroType.resolve(MethodHandles.lookup())).invoke();
+            } catch (Throwable e) {
+                throw new RuntimeException(e);
+            }
+            return block.op(ArithMathOps.constant(rType, zero));
+        }
+
+        public Value load(TensorType rType, Op.Result r,
+                          TensorType ptrType, Value ptr,
+                          TensorType maskType, Value mask) {
+            broadcastConversionRight(ptrType, maskType, mask);
+            return block.op(TritonOps.load(
+                    rType,
+                    block.context().getValue(ptr),
+                    block.context().getValue(mask)));
+        }
+
+        public Value store(TensorType rType, Op.Result r,
+                           TensorType ptrType, Value ptr,
+                           TensorType valueType, Value value,
+                           TensorType maskType, Value mask) {
+            broadcastConversionRight(ptrType, valueType, value);
+            broadcastConversionRight(ptrType, maskType, mask);
+            return block.op(TritonOps.store(
+                    block.context().getValue(ptr),
+                    block.context().getValue(value),
+                    block.context().getValue(mask)));
+        }
+
+        public Value broadcast(TensorType rType, Op.Result r,
+                               TypeElement oType, Value o,
+                               TensorType tensorTypeType, Value tensorType) {
+            // @@@ tt.splat with scalar operand, tt.broadcast with tensor operand
+            if (oType instanceof TensorType) {
+                return block.op(TritonOps.broadcast(
+                        rType,
+                        block.context().getValue(o)));
+            } else {
+                return block.op(TritonOps.splat(
+                        rType,
+                        block.context().getValue(o)));
+            }
+        }
+
+        public Value joinShape(TensorType rType, Op.Result r,
+                               TensorType aType, Value a,
+                               TensorType bType, Value b) {
+            // Replace with constant operation to produce tensor type.
+            // Result may be used, but transitively it will be removed due to no uses
+            // contributing to the computation
+            return block.op(CoreOp.constant(JavaType.type(TensorType.class), r.type()));
+        }
+
+
+        public Value add(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            if (rType instanceof PtrType ||
+                    rType instanceof TensorType t && t.eType() instanceof PtrType) {
+                return block.op(TritonOps.addptr(a, b));
+            } else {
+                return block.op(ArithMathOps.add(a, b));
+            }
+        }
+
+        public Value sub(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.sub(a, b));
+        }
+
+        public Value mul(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.mul(a, b));
+        }
+
+        public Value div(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.div(a, b));
+        }
+
+        public Value mod(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.rem(a, b));
+        }
+
+        public Value and(TypeElement rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.and(a, b));
+        }
+
+        public Value dot(TensorType rType, Op.Result r,
+                         TypeElement aType, Value a,
+                         TypeElement bType, Value b) {
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(TritonOps.dot(rType, a, b));
+        }
+
+        public Value cdiv(TypeElement rType, Op.Result r,
+                          TypeElement aType, Value a,
+                          TypeElement bType, Value b) {
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            TritonOps.FuncOp cdiv = tritonFunction(Functions.getJavaCodeModel("cdiv"),
+                    rType, List.of(aType, bType),
+                    fsymTable);
+            // @@@ Generalize
+            List<Value> args = new ArrayList<>();
+            if (!(aType instanceof ConstantType)) {
+                args.add(a);
+            }
+            if (!(bType instanceof ConstantType)) {
+                args.add(b);
+            }
+            return block.op(TritonOps.call(cdiv, args));
+        }
+
+        public Value conv(TypeElement rType, Op.Result r,
+                          ConstantType tType, Value t,
+                          TypeElement aType, Value a) {
+            a = block.context().getValue(a);
+
+            TypeElement rScalarType;
+            TypeElement aScalarType;
+            if (rType instanceof TensorType rTensorType && aType instanceof TensorType aTensorType) {
+                rScalarType = rTensorType.eType();
+                aScalarType = aTensorType.eType();
+            } else {
+                rScalarType = rType;
+                aScalarType = aType;
+            }
+
+            if (rScalarType.equals(Float16.FLOAT_16_TYPE) && aScalarType.equals(JavaType.FLOAT)) {
+                return block.op(ArithMathOps.trunc(rType, a));
+            } else if (rType.equals(aType)) {
+                return a;
+            } else {
+                throw new IllegalStateException();
+            }
+        }
+
+        public Value exp(TritonType rType, Op.Result r,
+                         TritonType aType, Value a) {
+            return block.op(ArithMathOps.exp(
+                    block.context().getValue(a)));
+        }
+
+        public Value compare(TensorType rType, Op.Result r,
+                             TypeElement aType, Value a,
+                             TypeElement bType, Value b,
+                             ConstantType compareType, Value compare) {
+            Triton.CompareKind ck = (Triton.CompareKind) compareType.value();
+
+            ArithMathOps.CompareOp.CompareKind ack = switch (ck) {
+                case LessThan -> ArithMathOps.CompareOp.CompareKind.slt;
+                default -> throw new UnsupportedOperationException("Unsupported comparison: " + ck);
+            };
+
+            broadcastConversion(rType, aType, a, bType, b);
+            a = block.context().getValue(a);
+            b = block.context().getValue(b);
+
+            return block.op(ArithMathOps.cmp(ack, a, b));
+        }
+
+
+        public Value max(TypeElement rType, Op.Result r,
+                         TensorType xType, Value x,
+                         ConstantType axisType, Value axis) {
+            TritonOps.FuncOp f = tritonFunction(Functions.getJavaCodeModel("max"),
+                    rType, List.of(rType, rType), fsymTable);
+            return reduce(rType, r, xType, x, axisType, axis, f);
+        }
+
+        public Value sum(TypeElement rType, Op.Result r,
+                         TensorType xType, Value x,
+                         ConstantType axisType, Value axis) {
+            TritonOps.FuncOp f = tritonFunction(Functions.getJavaCodeModel("sum"),
+                    rType, List.of(rType, rType), fsymTable);
+            return reduce(rType, r, xType, x, axisType, axis, f);
+        }
+
+        Value reduce(TypeElement rType, Op.Result r,
+                     TensorType xType, Value x,
+                     ConstantType axisType, Value axis,
+                     TritonOps.FuncOp f) {
+            int axisConstant = (int) axisType.value();
+
+            String signature = "reduce_" + f.funcName() + "_" + axisConstant;
+            TritonOps.FuncOp rf = fsymTable.computeIfAbsent(signature,
+                    s -> reduce(rType, xType, axisConstant, s, f));
+
+            return block.op(TritonOps.call(rf, block.context().getValue(x)));
+        }
+
+        static TritonOps.FuncOp reduce(TypeElement elementType,
+                                       TensorType tensorType,
+                                       int axisConstant,
+                                       String name, TritonOps.FuncOp scalarFunc) {
+            return TritonOps.func(name,
+                            functionType(elementType, tensorType))
+                    .body(fblock -> {
+                        TritonOps.ReduceOp reduceOp = TritonOps.reduce(fblock.parentBody(),
+                                        axisConstant, fblock.parameters().get(0),
+                                        functionType(elementType, elementType, elementType))
+                                .body(rblock -> {
+                                    Block.Parameter a = rblock.parameters().get(0);
+                                    Block.Parameter b = rblock.parameters().get(1);
+                                    Op.Result _r = rblock.op(TritonOps.call(scalarFunc, a, b));
+                                    rblock.op(TritonOps.reduceReturn(_r));
+                                });
+
+                        Op.Result opr = fblock.op(reduceOp);
+                        fblock.op(TritonOps.return_(opr));
+                    });
+        }
+
+        // @@@ Test
+        public Value consume(TypeElement rType, Op.Result r,
+                             TypeElement aType, Value a) {
+            return block.op(TritonTestOps.consume(block.context().getValue(a)));
+        }
+
+        void broadcastConversion(TypeElement rType,
+                                 TypeElement aType, Value a,
+                                 TypeElement bType, Value b) {
+            Value ma = block.context().getValue(a);
+            Value mb = block.context().getValue(b);
+            if (aType instanceof TensorType at && bType instanceof TensorType bTensorType) {
+                TensorType rTensorType = (TensorType) rType;
+                if (!at.shape().equals(rTensorType.shape())) {
+                    ma = block.op(TritonOps.broadcast(rTensorType, ma));
+                }
+                if (!bTensorType.shape().equals(rTensorType.shape())) {
+                    if (rTensorType.eType() instanceof PtrType) {
+                        bTensorType = new TensorType(bType, rTensorType.shape());
+                        mb = block.op(TritonOps.broadcast(bTensorType, mb));
+                    } else {
+                        mb = block.op(TritonOps.broadcast(rTensorType, mb));
+                    }
+                }
+            } else if (aType instanceof TensorType) {
+                TensorType rTensorType = (TensorType) rType;
+                if (rTensorType.eType() instanceof PtrType) {
+                    TensorType bTensorType = new TensorType(bType, rTensorType.shape());
+                    mb = block.op(TritonOps.splat(bTensorType, mb));
+                } else {
+                    mb = block.op(TritonOps.splat(rTensorType, mb));
+                }
+            } else if (bType instanceof TensorType) {
+                TensorType rTensorType = (TensorType) rType;
+                ma = block.op(TritonOps.splat(rTensorType, ma));
+            }
+            block.context().mapValue(a, ma);
+            block.context().mapValue(b, mb);
+        }
+
+        void broadcastConversionRight(TypeElement aType,
+                                      TypeElement bType, Value b) {
+            Value mb = block.context().getValue(b);
+            if (aType instanceof TensorType aTensorType && bType instanceof TensorType bTensorType) {
+                if (!bTensorType.shape().equals(aTensorType.shape())) {
+                    if (aTensorType.eType() instanceof PtrType) {
+                        bTensorType = new TensorType(bTensorType.eType(), aTensorType.shape());
+                        mb = block.op(TritonOps.broadcast(bTensorType, mb));
+                    } else {
+                        mb = block.op(TritonOps.broadcast(aTensorType, mb));
+                    }
+                }
+            } else if (aType instanceof TensorType rTensorType) {
+                if (rTensorType.eType() instanceof PtrType) {
+                    TensorType bTensorType = new TensorType(bType, rTensorType.shape());
+                    mb = block.op(TritonOps.splat(bTensorType, mb));
+                } else {
+                    mb = block.op(TritonOps.splat(rTensorType, mb));
+                }
+            }
+            block.context().mapValue(b, mb);
+        }
+    }
+
+    public static <O extends Op & Op.Invokable> void printTypeMap(
+            O kernel, Map<Value, TypeElement> valueTypeMap) {
+        AtomicInteger valueId = new AtomicInteger();
+        Map<Value, Integer> valueIdMap = new LinkedHashMap<>();
+        kernel.traverse(null, (o, codeElement) -> {
+            switch (codeElement) {
+                case FuncOp _ -> {
+                    // Ignore
+                }
+                case Op op when !op.result().type().equals(JavaType.VOID) -> {
+                    valueIdMap.put(op.result(), valueId.getAndIncrement());
+                }
+                case Block block -> {
+                    for (Block.Parameter parameter : block.parameters()) {
+                        valueIdMap.put(parameter, valueId.getAndIncrement());
+                    }
+                }
+                default -> {
+                }
+            }
+            return null;
+        });
+
+        valueIdMap.forEach((value, id) -> {
+            TypeElement type = valueTypeMap.get(value);
+            if (type != null) {
+                System.out.println("%" + id + " : " + value.type() + " -> " + type);
+            }
+        });
+    }
+}
diff a/cr-examples/triton/src/main/java/oracle/code/triton/TritonType.java b/cr-examples/triton/src/main/java/oracle/code/triton/TritonType.java
--- /dev/null
+++ b/cr-examples/triton/src/main/java/oracle/code/triton/TritonType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.reflect.code.TypeElement;
+
+public abstract sealed class TritonType implements TypeElement
+        permits ConstantType, PtrType, TensorType {
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestAddKernel.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestAddKernel.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestAddKernel.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import oracle.code.triton.TritonTestExtension.TritonTestData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestAddKernel {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"add_kernel_ptr<float>_ptr<float>_ptr<float>_int_64_void" (%0 : ptr<float>, %1 : ptr<float>, %2 : ptr<float>, %3 : int)void -> {
+                    %4 : int = arith.constant @"64";
+                    %5 : int = tt.get_program_id @"0";
+                    %6 : int = arith.muli %5 %4;
+                    %7 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %8 : tensor<x64, int> = tt.splat %6;
+                    %9 : tensor<x64, int> = arith.addi %8 %7;
+                    %10 : tensor<x64, int> = tt.splat %3;
+                    %11 : tensor<x64, int> = arith.cmpi %9 %10 @"slt";
+                    %12 : tensor<x64, ptr<float>> = tt.splat %0;
+                    %13 : tensor<x64, ptr<float>> = tt.addptr %12 %9;
+                    %14 : tensor<x64, float> = tt.load %13 %11;
+                    %15 : tensor<x64, ptr<float>> = tt.splat %1;
+                    %16 : tensor<x64, ptr<float>> = tt.addptr %15 %9;
+                    %17 : tensor<x64, float> = tt.load %16 %11;
+                    %18 : tensor<x64, float> = arith.addf %14 %17;
+                    %19 : tensor<x64, ptr<float>> = tt.splat %2;
+                    %20 : tensor<x64, ptr<float>> = tt.addptr %19 %9;
+                    tt.store %20 %18 %11;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void add_kernel(Ptr x_ptr,  // *Pointer* to first input vector.
+                           Ptr y_ptr,  // *Pointer* to second input vector.
+                           Ptr output_ptr,  // *Pointer* to output vector.
+                           int n_elements,  // Size of the vector.
+                           @Constant int BLOCK_SIZE)  // Number of elements each program should process.
+    // NOTE: @Constant so it can be used as a shape value
+    {
+        // There are multiple 'programs' processing different data. We identify which program
+        // we are here:
+        var pid = Triton.programId(0); // We use a 1D launch grid so axis is 0.
+        // This program will process inputs that are offset from the initial data.
+        // For instance, if you had a vector of length 256 and block_size of 64, the programs
+        // would each access the elements [0:64, 64:128, 128:192, 192:256].
+        // Note that offsets is a list of pointers:
+        var block_start = pid * BLOCK_SIZE;
+        var range = Triton.arange(0, BLOCK_SIZE);
+        var offsets = Triton.add(Triton.broadcast(block_start, range.type()), range);
+        // Create a mask to guard memory operations against out-of-bounds accesses.
+        var mask = Triton.compare(offsets, Triton.broadcast(n_elements, offsets.type()), Triton.CompareKind.LessThan);
+        // Load x and y from DRAM, masking out any extra elements in case the input is not a
+        // multiple of the block size.
+        var x = Triton.load(Triton.add(Triton.broadcast(x_ptr, offsets.type()), offsets), mask);
+        var y = Triton.load(Triton.add(Triton.broadcast(y_ptr, offsets.type()), offsets), mask);
+        var output = Triton.add(x, y);
+        // Write x + y back to DRAM.
+        Triton.store(Triton.add(Triton.broadcast(output_ptr, offsets.type()), offsets), output, mask);
+    }
+
+    @TritonTestExtension.Kernel("add_kernel")
+    @Test
+    public void test(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"add_kernel2_ptr<float>_ptr<float>_ptr<float>_int_64_void" (%0 : ptr<float>, %1 : ptr<float>, %2 : ptr<float>, %3 : int)void -> {
+                    %4 : int = arith.constant @"64";
+                    %5 : int = tt.get_program_id @"0";
+                    %6 : int = arith.muli %5 %4;
+                    %7 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %8 : tensor<x64, int> = tt.splat %6;
+                    %9 : tensor<x64, int> = arith.addi %8 %7;
+                    %10 : tensor<x64, int> = tt.splat %3;
+                    %11 : tensor<x64, int> = arith.cmpi %9 %10 @"slt";
+                    %12 : tensor<x64, ptr<float>> = tt.splat %0;
+                    %13 : tensor<x64, ptr<float>> = tt.addptr %12 %9;
+                    %14 : tensor<x64, float> = tt.load %13 %11;
+                    %15 : tensor<x64, ptr<float>> = tt.splat %1;
+                    %16 : tensor<x64, ptr<float>> = tt.addptr %15 %9;
+                    %17 : tensor<x64, float> = tt.load %16 %11;
+                    %18 : tensor<x64, float> = arith.addf %14 %17;
+                    %19 : tensor<x64, ptr<float>> = tt.splat %2;
+                    %20 : tensor<x64, ptr<float>> = tt.addptr %19 %9;
+                    tt.store %20 %18 %11;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void add_kernel2(Ptr x_ptr,  // *Pointer* to first input vector.
+                            Ptr y_ptr,  // *Pointer* to second input vector.
+                            Ptr output_ptr,  // *Pointer* to output vector.
+                            int n_elements,  // Size of the vector.
+                            @Constant int BLOCK_SIZE)  // Number of elements each program should process.
+    // NOTE: @Constant so it can be used as a shape value
+    {
+        // There are multiple 'programs' processing different data. We identify which program
+        // we are here:
+        var pid = Triton.programId(0); // We use a 1D launch grid so axis is 0.
+        // This program will process inputs that are offset from the initial data.
+        // For instance, if you had a vector of length 256 and block_size of 64, the programs
+        // would each access the elements [0:64, 64:128, 128:192, 192:256].
+        // Note that offsets is a list of pointers:
+        var block_start = pid * BLOCK_SIZE;
+        var range = Triton.arange(0, BLOCK_SIZE);
+        var offsets = Triton.add(block_start, range);
+        // Create a mask to guard memory operations against out-of-bounds accesses.
+        var mask = Triton.compare(offsets, n_elements, Triton.CompareKind.LessThan);
+        // Load x and y from DRAM, masking out any extra elements in case the input is not a
+        // multiple of the block size.
+        var x = Triton.load(Triton.add(x_ptr, offsets), mask);
+        var y = Triton.load(Triton.add(y_ptr, offsets), mask);
+        var output = Triton.add(x, y);
+        // Write x + y back to DRAM.
+        Triton.store(Triton.add(output_ptr, offsets), output, mask);
+    }
+
+    @TritonTestExtension.Kernel("add_kernel2")
+    @Test
+    public void test2(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+}
+
+/*
+@triton.jit
+def add_kernel(x_ptr,  # *Pointer* to first input vector.
+               y_ptr,  # *Pointer* to second input vector.
+               output_ptr,  # *Pointer* to output vector.
+               n_elements,  # Size of the vector.
+               BLOCK_SIZE: tl.constexpr,  # Number of elements each program should process.
+               # NOTE: `constexpr` so it can be used as a shape value.
+               ):
+    # There are multiple 'programs' processing different data. We identify which program
+    # we are here:
+    pid = tl.program_id(axis=0)  # We use a 1D launch grid so axis is 0.
+    # This program will process inputs that are offset from the initial data.
+    # For instance, if you had a vector of length 256 and block_size of 64, the programs
+    # would each access the elements [0:64, 64:128, 128:192, 192:256].
+    # Note that offsets is a list of pointers:
+    block_start = pid * BLOCK_SIZE
+    offsets = block_start + tl.arange(0, BLOCK_SIZE)
+    # Create a mask to guard memory operations against out-of-bounds accesses.
+    mask = offsets < n_elements
+    # Load x and y from DRAM, masking out any extra elements in case the input is not a
+    # multiple of the block size.
+    x = tl.load(x_ptr + offsets, mask=mask)
+    y = tl.load(y_ptr + offsets, mask=mask)
+    output = x + y
+    # Write x + y back to DRAM.
+    tl.store(output_ptr + offsets, output, mask=mask)
+*/
+
+/*
+module {
+  tt.func public @add_kernel_0123(%arg0: !tt.ptr<f32, 1> , %arg1: !tt.ptr<f32, 1> , %arg2: !tt.ptr<f32, 1> , %arg3: i32 ) attributes {noinline = false} {
+    %0 = tt.get_program_id x : i32
+    %c64_i32 = arith.constant 64 : i32
+    %1 = arith.muli %0, %c64_i32 : i32
+    %2 = tt.make_range {end = 64 : i32, start = 0 : i32} : tensor<64xi32>
+    %3 = tt.splat %1 : (i32) -> tensor<64xi32>
+    %4 = arith.addi %3, %2 : tensor<64xi32>
+    %5 = tt.splat %arg3 : (i32) -> tensor<64xi32>
+    %6 = arith.cmpi slt, %4, %5 : tensor<64xi32>
+    %7 = tt.splat %arg0 : (!tt.ptr<f32, 1>) -> tensor<64x!tt.ptr<f32, 1>>
+    %8 = tt.addptr %7, %4 : tensor<64x!tt.ptr<f32, 1>>, tensor<64xi32>
+    %9 = tt.load %8, %6 {cache = 1 : i32, evict = 1 : i32, isVolatile = false} : tensor<64xf32>
+    %10 = tt.splat %arg1 : (!tt.ptr<f32, 1>) -> tensor<64x!tt.ptr<f32, 1>>
+    %11 = tt.addptr %10, %4 : tensor<64x!tt.ptr<f32, 1>>, tensor<64xi32>
+    %12 = tt.load %11, %6 {cache = 1 : i32, evict = 1 : i32, isVolatile = false} : tensor<64xf32>
+    %13 = arith.addf %9, %12 : tensor<64xf32>
+    %14 = tt.splat %arg2 : (!tt.ptr<f32, 1>) -> tensor<64x!tt.ptr<f32, 1>>
+    %15 = tt.addptr %14, %4 : tensor<64x!tt.ptr<f32, 1>>, tensor<64xi32>
+    tt.store %15, %13, %6 {cache = 1 : i32, evict = 1 : i32} : tensor<64xf32>
+    tt.return
+  }
+}
+*/
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestBroadcast.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestBroadcast.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestBroadcast.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import oracle.code.triton.TritonTestExtension.TritonTestData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.*;
+import static oracle.code.triton.TritonTest.consume;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestBroadcast {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"test1_ptr<int>_int_64_void" (%0 : ptr<int>, %1 : int)void -> {
+                    %2 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %3 : tensor<x64, ptr<int>> = tt.splat %0;
+                    %4 : tensor<x64, ptr<int>> = tt.addptr %3 %2;
+                    tt.consume %4;
+                    %5 : tensor<x64, int> = tt.splat %1;
+                    %6 : tensor<x64, int> = arith.addi %5 %2;
+                    tt.consume %6;
+                    %7 : tensor<x64, int> = tt.splat %1;
+                    %8 : tensor<x64, int> = arith.addi %2 %7;
+                    tt.consume %8;
+                    %9 : tensor<x64, int> = tt.splat %1;
+                    %10 : tensor<x64, int> = arith.addi %9 %2;
+                    tt.consume %10;
+                    %11 : tensor<x64, int> = tt.splat %1;
+                    %12 : tensor<x64, int> = arith.addi %2 %11;
+                    tt.consume %12;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void test1(Ptr ptr, int a, @Constant int s) {
+        var t = arange(0, s);
+        consume(add(ptr, t));
+        consume(add(a, t));
+        consume(add(t, a));
+        consume(add(broadcast(a, t.type()), t));
+        consume(add(t, broadcast(a, t.type())));
+    }
+
+    @Test
+    public void test1(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.INT),
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"test2_int_64_32_void" (%1 : int)void -> {
+                    %2 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %3 : tensor<x1, x64, int> = tt.expand_dims %2 @"0";
+                    %4 : tensor<x32, int> = tt.make_range @start="0" @end="32";
+                    %5 : tensor<x32, x1, int> = tt.expand_dims %4 @"1";
+                    %6 : tensor<x1, x64, int> = tt.splat %1;
+                    %7 : tensor<x1, x64, int> = arith.addi %3 %6;
+                    tt.consume %7;
+                    %8 : tensor<x32, x64, int> = tt.broadcast %3;
+                    %9 : tensor<x32, x64, int> = tt.broadcast %5;
+                    %10 : tensor<x32, x64, int> = arith.addi %8 %9;
+                    tt.consume %10;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void test2(int a, @Constant int M, @Constant int N) {
+        var m = arange(0, M);
+        var me = expand(m, 0);
+
+        var n = arange(0, N);
+        var ne = expand(n, 1);
+
+        var t4 = add(me, a);
+        consume(t4);
+
+        var t3 = add(me, ne);
+        consume(t3);
+    }
+
+    @Test
+    public void test2(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 64),
+                new ConstantType(JavaType.INT, 32)
+        );
+
+        t.test(argTypes);
+    }
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestCdiv.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestCdiv.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestCdiv.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import oracle.code.triton.TritonTestExtension.TritonTestData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.*;
+import static oracle.code.triton.TritonTest.consume;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestCdiv {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"cdiv_int_int_int" (%0 : int, %1 : int)int -> {
+                    %2 : int = arith.addi %0 %1;
+                    %3 : int = arith.constant @"1";
+                    %4 : int = arith.subi %2 %3;
+                    %5 : int = arith.divsi %4 %1;
+                    tt.return %5;
+                };
+                tt.func @"testScalar_int_int_void" (%6 : int, %7 : int)void -> {
+                    %8 : int = tt.call %6 %7 @"cdiv_int_int_int";
+                    tt.consume %8;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void testScalar(int a, int b) {
+        var r1 = cdiv(a, b);
+        consume(r1);
+    }
+
+    @Test
+    public void testScalar(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                JavaType.INT,
+                JavaType.INT);
+
+        t.test(argTypes);
+    }
+
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"cdiv_int_10_int" (%0 : int)int -> {
+                    %1 : int = arith.constant @"10";
+                    %2 : int = arith.addi %0 %1;
+                    %3 : int = arith.constant @"1";
+                    %4 : int = arith.subi %2 %3;
+                    %5 : int = arith.divsi %4 %1;
+                    tt.return %5;
+                };
+                tt.func @"testConstant_int_10_void" (%6 : int)void -> {
+                    %7 : int = tt.call %6 @"cdiv_int_10_int";
+                    tt.consume %7;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void testConstant(int a, int b) {
+        var r1 = cdiv(a, b);
+        consume(r1);
+    }
+
+    @Test
+    public void testConstant(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 10));
+
+        t.test(argTypes);
+    }
+
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"cdiv_int_int_int" (%0 : int, %1 : int)int -> {
+                    %2 : int = arith.addi %0 %1;
+                    %3 : int = arith.constant @"1";
+                    %4 : int = arith.subi %2 %3;
+                    %5 : int = arith.divsi %4 %1;
+                    tt.return %5;
+                };
+                tt.func @"cdiv_int_10_int" (%6 : int)int -> {
+                    %7 : int = arith.constant @"10";
+                    %8 : int = arith.addi %6 %7;
+                    %9 : int = arith.constant @"1";
+                    %10 : int = arith.subi %8 %9;
+                    %11 : int = arith.divsi %10 %7;
+                    tt.return %11;
+                };
+                tt.func @"cdiv_10_int_int" (%12 : int)int -> {
+                    %13 : int = arith.constant @"10";
+                    %14 : int = arith.addi %13 %12;
+                    %15 : int = arith.constant @"1";
+                    %16 : int = arith.subi %14 %15;
+                    %17 : int = arith.divsi %16 %12;
+                    tt.return %17;
+                };
+                tt.func @"testCalls_int_int_10_void" (%18 : int, %19 : int)void -> {
+                    %20 : int = tt.call %18 %19 @"cdiv_int_int_int";
+                    tt.consume %20;
+                    %21 : int = tt.call %19 %18 @"cdiv_int_int_int";
+                    tt.consume %21;
+                    %22 : int = tt.call %18 @"cdiv_int_10_int";
+                    tt.consume %22;
+                    %23 : int = tt.call %18 @"cdiv_10_int_int";
+                    tt.consume %23;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void testCalls(int a, int b, int c) {
+        consume(cdiv(a, b));
+        consume(cdiv(b, a));
+        consume(cdiv(a, c));
+        consume(cdiv(c, a));
+    }
+
+    @Test
+    public void testCalls(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                JavaType.INT,
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 10));
+
+        t.test(argTypes);
+    }
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestCountedLoop.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestCountedLoop.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestCountedLoop.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import oracle.code.triton.TritonTestExtension.TritonTestData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.*;
+import static oracle.code.triton.TritonTest.consume;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestCountedLoop {
+
+    @TritonCodeModel(value = """
+            module ()void -> {
+                tt.func @"test1_int_64_void" (%0 : int)void -> {
+                    %1 : int = arith.constant @"64";
+                    %2 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %3 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %4 : int = arith.constant @"0";
+                    %5 : int = arith.constant @"1";
+                    %6 : Tuple<tensor<x64, int>, tensor<x64,int>> = scf.for %4 %0 %5 %2 %3 (%7 : int, %8 : tensor<x64, int>, %9 : tensor<x64, int>)Tuple<tensor<x64, int>, tensor<x64, int>> -> {
+                        %10 : tensor<x64, int> = tt.splat %7;
+                        %11 : tensor<x64, int> = arith.addi %8 %10;
+                        %12 : tensor<x64, int> = tt.splat %1;
+                        %13 : tensor<x64, int> = arith.addi %9 %12;
+                        scf.yield %11 %13;
+                    };
+                    %14 : tensor<x64, int> = tuple.load %6 @"0";
+                    %15 : tensor<x64, int> = tuple.load %6 @"1";
+                    tt.consume %14;
+                    tt.consume %15;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void test1(int n, @Constant int s) {
+        var a = arange(0, s);
+        var b = arange(0, s);
+        for (int i = 0; i < n; i++) {
+            a = Triton.add(a, i);
+            b = Triton.add(b, s);
+        }
+        consume(a);
+        consume(b);
+    }
+
+    @Test
+    public void test1(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                JavaType.INT,
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestMatrix.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestMatrix.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestMatrix.java
@@ -0,0 +1,832 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.*;
+import static oracle.code.triton.Triton.CompareKind.*;
+import static oracle.code.triton.Triton.compare;
+import static oracle.code.triton.Triton.load;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestMatrix {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"cdiv_int_32_int" (%0 : int)int -> {
+                    %1 : int = arith.constant @"32";
+                    %2 : int = arith.addi %0 %1;
+                    %3 : int = arith.constant @"1";
+                    %4 : int = arith.subi %2 %3;
+                    %5 : int = arith.divsi %4 %1;
+                    tt.return %5;
+                };
+                tt.func @"cdiv_int_64_int" (%6 : int)int -> {
+                    %7 : int = arith.constant @"64";
+                    %8 : int = arith.addi %6 %7;
+                    %9 : int = arith.constant @"1";
+                    %10 : int = arith.subi %8 %9;
+                    %11 : int = arith.divsi %10 %7;
+                    tt.return %11;
+                };
+                tt.func @"matmul_kernel_broadcast_ptr<float>_ptr<float>_ptr<float>_int_int_int_int_int_int_int_int_int_32_64_32_8_false_void" (%12 : ptr<float>, %13 : ptr<float>, %14 : ptr<float>, %15 : int, %16 : int, %17 : int, %18 : int, %19 : int, %20 : int, %21 : int, %22 : int, %23 : int)void -> {
+                    %24 : int = arith.constant @"32";
+                    %25 : int = arith.constant @"64";
+                    %26 : int = arith.constant @"32";
+                    %27 : int = arith.constant @"8";
+                    %28 : int = tt.get_program_id @"0";
+                    %29 : int = tt.call %15 @"cdiv_int_32_int";
+                    %30 : int = tt.call %16 @"cdiv_int_64_int";
+                    %31 : int = arith.muli %27 %30;
+                    %32 : int = arith.divsi %28 %31;
+                    %33 : int = arith.muli %32 %27;
+                    %34 : int = arith.subi %29 %33;
+                    %35 : int = arith.minsi %34 %27;
+                    %36 : int = arith.remsi %28 %35;
+                    %37 : int = arith.addi %33 %36;
+                    %38 : int = arith.remsi %28 %31;
+                    %39 : int = arith.divsi %38 %35;
+                    %40 : tensor<x32, int> = tt.make_range @start="0" @end="32";
+                    %41 : int = arith.muli %37 %24;
+                    %42 : tensor<x32, int> = tt.splat %41;
+                    %43 : tensor<x32, int> = arith.addi %42 %40;
+                    %44 : tensor<x32, int> = tt.splat %15;
+                    %45 : tensor<x32, int> = arith.remsi %43 %44;
+                    %46 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %47 : int = arith.muli %39 %25;
+                    %48 : tensor<x64, int> = tt.splat %47;
+                    %49 : tensor<x64, int> = arith.addi %48 %46;
+                    %50 : tensor<x64, int> = tt.splat %16;
+                    %51 : tensor<x64, int> = arith.remsi %49 %50;
+                    %52 : tensor<x32, int> = tt.make_range @start="0" @end="32";
+                    %53 : tensor<x32, x1, int> = tt.expand_dims %45 @"1";
+                    %54 : tensor<x32, x1, int> = tt.splat %18;
+                    %55 : tensor<x32, x1, int> = arith.muli %53 %54;
+                    %56 : tensor<x1, x32, int> = tt.expand_dims %52 @"0";
+                    %57 : tensor<x1, x32, int> = tt.splat %19;
+                    %58 : tensor<x1, x32, int> = arith.muli %56 %57;
+                    %59 : tensor<x32, x32, ptr<float>> = tt.splat %12;
+                    %60 : tensor<x32, x32, int> = tt.broadcast %55;
+                    %61 : tensor<x32, x32, int> = tt.broadcast %58;
+                    %62 : tensor<x32, x32, int> = arith.addi %60 %61;
+                    %63 : tensor<x32, x32, ptr<float>> = tt.addptr %59 %62;
+                    %64 : tensor<x32, x1, int> = tt.expand_dims %52 @"1";
+                    %65 : tensor<x32, x1, int> = tt.splat %20;
+                    %66 : tensor<x32, x1, int> = arith.muli %64 %65;
+                    %67 : tensor<x1, x64, int> = tt.expand_dims %51 @"0";
+                    %68 : tensor<x1, x64, int> = tt.splat %21;
+                    %69 : tensor<x1, x64, int> = arith.muli %67 %68;
+                    %70 : tensor<x32, x64, ptr<float>> = tt.splat %13;
+                    %71 : tensor<x32, x64, int> = tt.broadcast %66;
+                    %72 : tensor<x32, x64, int> = tt.broadcast %69;
+                    %73 : tensor<x32, x64, int> = arith.addi %71 %72;
+                    %74 : tensor<x32, x64, ptr<float>> = tt.addptr %70 %73;
+                    %75 : tensor<x32, x64, float> = arith.constant @"0.0";
+                    %76 : int = arith.constant @"0";
+                    %77 : int = tt.call %17 @"cdiv_int_32_int";
+                    %78 : int = arith.constant @"1";
+                    %79 : Tuple<tensor<x32, x64, float>, tensor<x32, x32, ptr<float>>, tensor<x32, x64, ptr<float>>> = scf.for %76 %77 %78 %75 %63 %74 (%80 : int, %81 : tensor<x32, x64, float>, %82 : tensor<x32, x32, ptr<float>>, %83 : tensor<x32, x64, ptr<float>>)Tuple<tensor<x32, x64, float>, tensor<x32, x32, ptr<float>>, tensor<x32, x64, ptr<float>>> -> {
+                        %84 : tensor<x1, x32, int> = tt.expand_dims %52 @"0";
+                        %85 : int = arith.muli %80 %26;
+                        %86 : int = arith.subi %17 %85;
+                        %87 : tensor<x1, x32, int> = tt.splat %86;
+                        %88 : tensor<x1, x32, int> = arith.cmpi %84 %87 @"slt";
+                        %89 : tensor<x32, x32, int> = tt.broadcast %88;
+                        %90 : tensor<x32, x32, float> = tt.load %82 %89;
+                        %91 : tensor<x32, x1, int> = tt.expand_dims %52 @"1";
+                        %92 : int = arith.muli %80 %26;
+                        %93 : int = arith.subi %17 %92;
+                        %94 : tensor<x32, x1, int> = tt.splat %93;
+                        %95 : tensor<x32, x1, int> = arith.cmpi %91 %94 @"slt";
+                        %96 : tensor<x32, x64, int> = tt.broadcast %95;
+                        %97 : tensor<x32, x64, float> = tt.load %83 %96;
+                        %98 : tensor<x32, x64, float> = tt.dot %90 %97;
+                        %99 : tensor<x32, x64, float> = arith.addf %81 %98;
+                        %100 : int = arith.muli %26 %19;
+                        %101 : tensor<x32, x32, int> = tt.splat %100;
+                        %102 : tensor<x32, x32, ptr<float>> = tt.addptr %82 %101;
+                        %103 : int = arith.muli %26 %20;
+                        %104 : tensor<x32, x64, int> = tt.splat %103;
+                        %105 : tensor<x32, x64, ptr<float>> = tt.addptr %83 %104;
+                        scf.yield %99 %102 %105;
+                    };
+                    %106 : tensor<x32, x64, float> = tuple.load %79 @"0";
+                    %107 : tensor<x32, x32, ptr<float>> = tuple.load %79 @"1";
+                    %108 : tensor<x32, x64, ptr<float>> = tuple.load %79 @"2";
+                    %109 : int = arith.muli %37 %24;
+                    %110 : tensor<x32, int> = tt.splat %109;
+                    %111 : tensor<x32, int> = arith.addi %110 %40;
+                    %112 : int = arith.muli %39 %25;
+                    %113 : tensor<x64, int> = tt.splat %112;
+                    %114 : tensor<x64, int> = arith.addi %113 %46;
+                    %115 : tensor<x32, x1, int> = tt.expand_dims %111 @"1";
+                    %116 : tensor<x32, x1, int> = tt.splat %22;
+                    %117 : tensor<x32, x1, int> = arith.muli %115 %116;
+                    %118 : tensor<x1, x64, int> = tt.expand_dims %114 @"0";
+                    %119 : tensor<x1, x64, int> = tt.splat %23;
+                    %120 : tensor<x1, x64, int> = arith.muli %118 %119;
+                    %121 : tensor<x32, x64, ptr<float>> = tt.splat %14;
+                    %122 : tensor<x32, x64, int> = tt.broadcast %117;
+                    %123 : tensor<x32, x64, int> = tt.broadcast %120;
+                    %124 : tensor<x32, x64, int> = arith.addi %122 %123;
+                    %125 : tensor<x32, x64, ptr<float>> = tt.addptr %121 %124;
+                    %126 : tensor<x32, x1, int> = tt.expand_dims %111 @"1";
+                    %127 : tensor<x32, x1, int> = tt.splat %15;
+                    %128 : tensor<x32, x1, int> = arith.cmpi %126 %127 @"slt";
+                    %129 : tensor<x1, x64, int> = tt.expand_dims %114 @"0";
+                    %130 : tensor<x1, x64, int> = tt.splat %16;
+                    %131 : tensor<x1, x64, int> = arith.cmpi %129 %130 @"slt";
+                    %132 : tensor<x32, x64, int> = tt.broadcast %128;
+                    %133 : tensor<x32, x64, int> = tt.broadcast %131;
+                    %134 : tensor<x32, x64, int> = arith.andi %132 %133;
+                    tt.store %125 %106 %134;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void matmul_kernel_broadcast(
+            // Pointers to matrices
+            Ptr a_ptr, Ptr b_ptr, Ptr c_ptr,
+            // Matrix dimensions
+            int M, int N, int K,
+            // The stride variables represent how much to increase the ptr by when moving by 1
+            // element in a particular dimension. E.g. `stride_am` is how much to increase `a_ptr`
+            // by to get the element one row down (A has M rows).
+            int stride_am, int stride_ak,
+            int stride_bk, int stride_bn,
+            int stride_cm, int stride_cn,
+            // Meta-parameters
+            @Constant int BLOCK_SIZE_M, @Constant int BLOCK_SIZE_N, @Constant int BLOCK_SIZE_K,
+            @Constant int GROUP_SIZE_M,
+            @Constant boolean ACTIVATION) {
+
+        // """Kernel for computing the matmul C = A x B.
+        // A has shape (M, K), B has shape (K, N) and C has shape (M, N)
+        // """
+        // -----------------------------------------------------------
+        // Map program ids `pid` to the block of C it should compute.
+        // This is done in a grouped ordering to promote L2 data reuse.
+        // See above `L2 Cache Optimizations` section for details.
+        var pid = programId(0);
+
+        var num_pid_m = cdiv(M, BLOCK_SIZE_M);
+        var num_pid_n = cdiv(N, BLOCK_SIZE_N);
+        var num_pid_in_group = GROUP_SIZE_M * num_pid_n;
+        var group_id = pid / num_pid_in_group;
+        var first_pid_m = group_id * GROUP_SIZE_M;
+        var group_size_m = Math.min(num_pid_m - first_pid_m, GROUP_SIZE_M);
+        var pid_m = first_pid_m + (pid % group_size_m);
+        var pid_n = (pid % num_pid_in_group) / group_size_m;
+
+        // ----------------------------------------------------------
+        // Create pointers for the first blocks of A and B.
+        // We will advance this pointer as we move in the K direction
+        // and accumulate
+        // `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers
+        // `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers
+        // See above `Pointer Arithmetics` section for details
+        var offs_m = arange(0, BLOCK_SIZE_M);
+        var offs_am = mod(
+                add(broadcast(pid_m * BLOCK_SIZE_M, offs_m.type()), offs_m),
+                broadcast(M, offs_m.type()));
+        var offs_n = arange(0, BLOCK_SIZE_N);
+        var offs_bn = mod(
+                add(broadcast(pid_n * BLOCK_SIZE_N, offs_n.type()), offs_n),
+                broadcast(N, offs_n.type()));
+        var offs_k = arange(0, BLOCK_SIZE_K);
+
+        var offs_am_e = expand(offs_am, 1);
+        offs_am_e = mul(offs_am_e, broadcast(stride_am, offs_am_e.type()));
+        var offs_k_e_0 = expand(offs_k, 0);
+        offs_k_e_0 = mul(offs_k_e_0, broadcast(stride_ak, offs_k_e_0.type()));
+        TensorType a_ptrs_t = joinShape(offs_am_e.type(), offs_k_e_0.type());
+        var a_ptrs = add(broadcast(a_ptr, a_ptrs_t),
+                add(broadcast(offs_am_e, a_ptrs_t), broadcast(offs_k_e_0, a_ptrs_t)));
+
+        var offs_k_e_1 = expand(offs_k, 1);
+        offs_k_e_1 = mul(offs_k_e_1, broadcast(stride_bk, offs_k_e_1.type()));
+        var offs_bn_e = expand(offs_bn, 0);
+        offs_bn_e = mul(offs_bn_e, broadcast(stride_bn, offs_bn_e.type()));
+        TensorType b_ptrs_t = joinShape(offs_k_e_1.type(), offs_bn_e.type());
+        var b_ptrs = add(broadcast(b_ptr, b_ptrs_t),
+                add(broadcast(offs_k_e_1, b_ptrs_t), broadcast(offs_bn_e, b_ptrs_t)));
+
+        // -----------------------------------------------------------
+        // Iterate to compute a block of the C matrix.
+        // We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block
+        // of fp32 values for higher accuracy.
+        // `accumulator` will be converted back to fp16 after the loop.
+        var accumulator = zeros(float.class, BLOCK_SIZE_M, BLOCK_SIZE_N);
+        for (int k = 0; k < cdiv(K, BLOCK_SIZE_K); k++) {
+            // Load the next block of A and B, generate a mask by checking the K dimension.
+            // If it is out of bounds, set it to 0.
+            var offs_k_m_0 = expand(offs_k, 0);
+            offs_k_m_0 = compare(offs_k_m_0,
+                    broadcast(K - k * BLOCK_SIZE_K, offs_k_m_0.type()),
+                    LessThan);
+            var a = load(a_ptrs, broadcast(offs_k_m_0, a_ptrs.type()));
+            var offs_k_m_1 = expand(offs_k, 1);
+            offs_k_m_1 = compare(offs_k_m_1,
+                    broadcast(K - k * BLOCK_SIZE_K, offs_k_m_1.type()),
+                    LessThan);
+            var b = load(b_ptrs, broadcast(offs_k_m_1, b_ptrs.type()));
+            // We accumulate along the K dimension.
+            accumulator = add(accumulator, dot(a, b));
+            // Advance the ptrs to the next K block.
+            a_ptrs = add(a_ptrs, broadcast(BLOCK_SIZE_K * stride_ak, a_ptrs.type()));
+            b_ptrs = add(b_ptrs, broadcast(BLOCK_SIZE_K * stride_bk, b_ptrs.type()));
+        }
+
+        // You can fuse arbitrary activation functions here
+        // while the accumulator is still in FP32!
+//        if (ACTIVATION) {
+//            // ...
+//        }
+        // c = Triton.to(activation, tl.float16)
+        var c = accumulator;
+
+        // -----------------------------------------------------------
+        // Write back the block of the output matrix C with masks.
+        var offs_cm = add(broadcast(pid_m * BLOCK_SIZE_M, offs_m.type()), offs_m);
+        var offs_cn = add(broadcast(pid_n * BLOCK_SIZE_N, offs_n.type()), offs_n);
+
+        var offs_cm_e = expand(offs_cm, 1);
+        offs_cm_e = mul(offs_cm_e, broadcast(stride_cm, offs_cm_e.type()));
+        var offs_cn_e = expand(offs_cn, 0);
+        offs_cn_e = mul(offs_cn_e, broadcast(stride_cn, offs_cn_e.type()));
+        TensorType c_ptrs_t = joinShape(offs_cm_e.type(), offs_cn_e.type());
+        var c_ptrs = add(broadcast(c_ptr, c_ptrs_t),
+                add(broadcast(offs_cm_e, c_ptrs_t), broadcast(offs_cn_e, c_ptrs_t)));
+
+        offs_cm_e = expand(offs_cm, 1);
+        var c_mask_l = compare(offs_cm_e, broadcast(M, offs_cm_e.type()), LessThan);
+        offs_cn_e = expand(offs_cn, 0);
+        var c_mask_r = compare(offs_cn_e, broadcast(N, offs_cn_e.type()), LessThan);
+        var c_mask = and(broadcast(c_mask_l, c_ptrs_t), broadcast(c_mask_r, c_ptrs_t));
+
+        store(c_ptrs, c, c_mask);
+    }
+
+    @TritonTestExtension.Kernel("matmul_kernel_broadcast")
+    @Test
+    public void testWithBroadcast(TritonTestExtension.TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                JavaType.INT, JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                new ConstantType(JavaType.INT, 32), new ConstantType(JavaType.INT, 64), new ConstantType(JavaType.INT, 32),
+                new ConstantType(JavaType.INT, 8),
+                new ConstantType(JavaType.INT, false));
+
+        t.test(argTypes);
+    }
+
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"cdiv_int_32_int" (%0 : int)int -> {
+                    %1 : int = arith.constant @"32";
+                    %2 : int = arith.addi %0 %1;
+                    %3 : int = arith.constant @"1";
+                    %4 : int = arith.subi %2 %3;
+                    %5 : int = arith.divsi %4 %1;
+                    tt.return %5;
+                };
+                tt.func @"cdiv_int_64_int" (%6 : int)int -> {
+                    %7 : int = arith.constant @"64";
+                    %8 : int = arith.addi %6 %7;
+                    %9 : int = arith.constant @"1";
+                    %10 : int = arith.subi %8 %9;
+                    %11 : int = arith.divsi %10 %7;
+                    tt.return %11;
+                };
+                tt.func @"matmul_kernel_ptr<oracle.code.triton.Float16>_ptr<oracle.code.triton.Float16>_ptr<oracle.code.triton.Float16>_int_int_int_int_int_int_int_int_int_32_64_32_8_false_void" (%12 : ptr<oracle.code.triton.Float16>, %13 : ptr<oracle.code.triton.Float16>, %14 : ptr<oracle.code.triton.Float16>, %15 : int, %16 : int, %17 : int, %18 : int, %19 : int, %20 : int, %21 : int, %22 : int, %23 : int)void -> {
+                    %24 : int = arith.constant @"32";
+                    %25 : int = arith.constant @"64";
+                    %26 : int = arith.constant @"32";
+                    %27 : int = arith.constant @"8";
+                    %28 : int = tt.get_program_id @"0";
+                    %29 : int = tt.call %15 @"cdiv_int_32_int";
+                    %30 : int = tt.call %16 @"cdiv_int_64_int";
+                    %31 : int = arith.muli %27 %30;
+                    %32 : int = arith.divsi %28 %31;
+                    %33 : int = arith.muli %32 %27;
+                    %34 : int = arith.subi %29 %33;
+                    %35 : int = arith.minsi %34 %27;
+                    %36 : int = arith.remsi %28 %35;
+                    %37 : int = arith.addi %33 %36;
+                    %38 : int = arith.remsi %28 %31;
+                    %39 : int = arith.divsi %38 %35;
+                    %40 : tensor<x32, int> = tt.make_range @start="0" @end="32";
+                    %41 : int = arith.muli %37 %24;
+                    %42 : tensor<x32, int> = tt.splat %41;
+                    %43 : tensor<x32, int> = arith.addi %42 %40;
+                    %44 : tensor<x32, int> = tt.splat %15;
+                    %45 : tensor<x32, int> = arith.remsi %43 %44;
+                    %46 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %47 : int = arith.muli %39 %25;
+                    %48 : tensor<x64, int> = tt.splat %47;
+                    %49 : tensor<x64, int> = arith.addi %48 %46;
+                    %50 : tensor<x64, int> = tt.splat %16;
+                    %51 : tensor<x64, int> = arith.remsi %49 %50;
+                    %52 : tensor<x32, int> = tt.make_range @start="0" @end="32";
+                    %53 : tensor<x32, x1, int> = tt.expand_dims %45 @"1";
+                    %54 : tensor<x32, x1, int> = tt.splat %18;
+                    %55 : tensor<x32, x1, int> = arith.muli %53 %54;
+                    %56 : tensor<x1, x32, int> = tt.expand_dims %52 @"0";
+                    %57 : tensor<x1, x32, int> = tt.splat %19;
+                    %58 : tensor<x1, x32, int> = arith.muli %56 %57;
+                    %59 : tensor<x32, x32, int> = tt.broadcast %55;
+                    %60 : tensor<x32, x32, int> = tt.broadcast %58;
+                    %61 : tensor<x32, x32, int> = arith.addi %59 %60;
+                    %62 : tensor<x32, x32, ptr<oracle.code.triton.Float16>> = tt.splat %12;
+                    %63 : tensor<x32, x32, ptr<oracle.code.triton.Float16>> = tt.addptr %62 %61;
+                    %64 : tensor<x32, x1, int> = tt.expand_dims %52 @"1";
+                    %65 : tensor<x32, x1, int> = tt.splat %20;
+                    %66 : tensor<x32, x1, int> = arith.muli %64 %65;
+                    %67 : tensor<x1, x64, int> = tt.expand_dims %51 @"0";
+                    %68 : tensor<x1, x64, int> = tt.splat %21;
+                    %69 : tensor<x1, x64, int> = arith.muli %67 %68;
+                    %70 : tensor<x32, x64, int> = tt.broadcast %66;
+                    %71 : tensor<x32, x64, int> = tt.broadcast %69;
+                    %72 : tensor<x32, x64, int> = arith.addi %70 %71;
+                    %73 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tt.splat %13;
+                    %74 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tt.addptr %73 %72;
+                    %75 : tensor<x32, x64, float> = arith.constant @"0.0";
+                    %76 : int = arith.constant @"0";
+                    %77 : int = tt.call %17 @"cdiv_int_32_int";
+                    %78 : int = arith.constant @"1";
+                    %79 : Tuple<tensor<x32, x64, float>, tensor<x32, x32, ptr<oracle.code.triton.Float16>>, tensor<x32, x64, ptr<oracle.code.triton.Float16>>> = scf.for %76 %77 %78 %75 %63 %74 (%80 : int, %81 : tensor<x32, x64, float>, %82 : tensor<x32, x32, ptr<oracle.code.triton.Float16>>, %83 : tensor<x32, x64, ptr<oracle.code.triton.Float16>>)Tuple<tensor<x32, x64, float>, tensor<x32, x32, ptr<oracle.code.triton.Float16>>, tensor<x32, x64, ptr<oracle.code.triton.Float16>>> -> {
+                        %84 : tensor<x1, x32, int> = tt.expand_dims %52 @"0";
+                        %85 : int = arith.muli %80 %26;
+                        %86 : int = arith.subi %17 %85;
+                        %87 : tensor<x1, x32, int> = tt.splat %86;
+                        %88 : tensor<x1, x32, int> = arith.cmpi %84 %87 @"slt";
+                        %89 : tensor<x32, x32, int> = tt.broadcast %88;
+                        %90 : tensor<x32, x32, oracle.code.triton.Float16> = tt.load %82 %89;
+                        %91 : tensor<x32, x1, int> = tt.expand_dims %52 @"1";
+                        %92 : int = arith.muli %80 %26;
+                        %93 : int = arith.subi %17 %92;
+                        %94 : tensor<x32, x1, int> = tt.splat %93;
+                        %95 : tensor<x32, x1, int> = arith.cmpi %91 %94 @"slt";
+                        %96 : tensor<x32, x64, int> = tt.broadcast %95;
+                        %97 : tensor<x32, x64, oracle.code.triton.Float16> = tt.load %83 %96;
+                        %98 : tensor<x32, x64, float> = tt.dot %90 %97;
+                        %99 : tensor<x32, x64, float> = arith.addf %81 %98;
+                        %100 : int = arith.muli %26 %19;
+                        %101 : tensor<x32, x32, int> = tt.splat %100;
+                        %102 : tensor<x32, x32, ptr<oracle.code.triton.Float16>> = tt.addptr %82 %101;
+                        %103 : int = arith.muli %26 %20;
+                        %104 : tensor<x32, x64, int> = tt.splat %103;
+                        %105 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tt.addptr %83 %104;
+                        scf.yield %99 %102 %105;
+                    };
+                    %106 : tensor<x32, x64, float> = tuple.load %79 @"0";
+                    %107 : tensor<x32, x32, ptr<oracle.code.triton.Float16>> = tuple.load %79 @"1";
+                    %108 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tuple.load %79 @"2";
+                    %109 : tensor<x32, x64, oracle.code.triton.Float16> = arith.truncf %106;
+                    %110 : int = arith.muli %37 %24;
+                    %111 : tensor<x32, int> = tt.splat %110;
+                    %112 : tensor<x32, int> = arith.addi %111 %40;
+                    %113 : int = arith.muli %39 %25;
+                    %114 : tensor<x64, int> = tt.splat %113;
+                    %115 : tensor<x64, int> = arith.addi %114 %46;
+                    %116 : tensor<x32, x1, int> = tt.expand_dims %112 @"1";
+                    %117 : tensor<x32, x1, int> = tt.splat %22;
+                    %118 : tensor<x32, x1, int> = arith.muli %117 %116;
+                    %119 : tensor<x1, x64, int> = tt.expand_dims %115 @"0";
+                    %120 : tensor<x1, x64, int> = tt.splat %23;
+                    %121 : tensor<x1, x64, int> = arith.muli %120 %119;
+                    %122 : tensor<x32, x64, int> = tt.broadcast %118;
+                    %123 : tensor<x32, x64, int> = tt.broadcast %121;
+                    %124 : tensor<x32, x64, int> = arith.addi %122 %123;
+                    %125 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tt.splat %14;
+                    %126 : tensor<x32, x64, ptr<oracle.code.triton.Float16>> = tt.addptr %125 %124;
+                    %127 : tensor<x32, x1, int> = tt.expand_dims %112 @"1";
+                    %128 : tensor<x32, x1, int> = tt.splat %15;
+                    %129 : tensor<x32, x1, int> = arith.cmpi %127 %128 @"slt";
+                    %130 : tensor<x1, x64, int> = tt.expand_dims %115 @"0";
+                    %131 : tensor<x1, x64, int> = tt.splat %16;
+                    %132 : tensor<x1, x64, int> = arith.cmpi %130 %131 @"slt";
+                    %133 : tensor<x32, x64, int> = tt.broadcast %129;
+                    %134 : tensor<x32, x64, int> = tt.broadcast %132;
+                    %135 : tensor<x32, x64, int> = arith.andi %133 %134;
+                    tt.store %126 %109 %135;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void matmul_kernel(
+            // Pointers to matrices
+            Ptr a_ptr, Ptr b_ptr, Ptr c_ptr,
+            // Matrix dimensions
+            int M, int N, int K,
+            // The stride variables represent how much to increase the ptr by when moving by 1
+            // element in a particular dimension. E.g. `stride_am` is how much to increase `a_ptr`
+            // by to get the element one row down (A has M rows).
+            int stride_am, int stride_ak,
+            int stride_bk, int stride_bn,
+            int stride_cm, int stride_cn,
+            // Meta-parameters
+            @Constant int BLOCK_SIZE_M, @Constant int BLOCK_SIZE_N, @Constant int BLOCK_SIZE_K,
+            @Constant int GROUP_SIZE_M,
+            @Constant boolean ACTIVATION) {
+
+        // """Kernel for computing the matmul C = A x B.
+        // A has shape (M, K), B has shape (K, N) and C has shape (M, N)
+        // """
+        // -----------------------------------------------------------
+        // Map program ids `pid` to the block of C it should compute.
+        // This is done in a grouped ordering to promote L2 data reuse.
+        // See above `L2 Cache Optimizations` section for details.
+        var pid = programId(0);
+        var num_pid_m = cdiv(M, BLOCK_SIZE_M);
+        var num_pid_n = cdiv(N, BLOCK_SIZE_N);
+        var num_pid_in_group = GROUP_SIZE_M * num_pid_n;
+        var group_id = pid / num_pid_in_group;
+        var first_pid_m = group_id * GROUP_SIZE_M;
+        var group_size_m = Math.min(num_pid_m - first_pid_m, GROUP_SIZE_M);
+        var pid_m = first_pid_m + (pid % group_size_m);
+        var pid_n = (pid % num_pid_in_group) / group_size_m;
+
+        // ----------------------------------------------------------
+        // Create pointers for the first blocks of A and B.
+        // We will advance this pointer as we move in the K direction
+        // and accumulate
+        // `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers
+        // `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers
+        // See above `Pointer Arithmetics` section for details
+        var offs_m = arange(0, BLOCK_SIZE_M);
+        var offs_am = mod(add(pid_m * BLOCK_SIZE_M, offs_m), M);
+        var offs_n = arange(0, BLOCK_SIZE_N);
+        var offs_bn = mod(add(pid_n * BLOCK_SIZE_N, offs_n), N);
+        var offs_k = arange(0, BLOCK_SIZE_K);
+        var a_ptrs = add(a_ptr, add(
+                mul(expand(offs_am, 1), stride_am),
+                mul(expand(offs_k, 0), stride_ak)));
+        var b_ptrs = add(b_ptr, add(
+                        mul(expand(offs_k, 1), stride_bk),
+                        mul(expand(offs_bn, 0), stride_bn)));
+
+        // -----------------------------------------------------------
+        // Iterate to compute a block of the C matrix.
+        // We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block
+        // of fp32 values for higher accuracy.
+        // `accumulator` will be converted back to fp16 after the loop.
+        var accumulator = zeros(float.class, BLOCK_SIZE_M, BLOCK_SIZE_N);
+        for (int k = 0; k < cdiv(K, BLOCK_SIZE_K); k++) {
+            // Load the next block of A and B, generate a mask by checking the K dimension.
+            // If it is out of bounds, set it to 0.
+            var a = load(a_ptrs,
+                    compare(expand(offs_k, 0), K - k * BLOCK_SIZE_K, LessThan));
+            var b = load(b_ptrs,
+                    compare(expand(offs_k, 1), K - k * BLOCK_SIZE_K, LessThan));
+            // We accumulate along the K dimension.
+            accumulator = add(accumulator, dot(a, b));
+            // Advance the ptrs to the next K block.
+            a_ptrs = add(a_ptrs, BLOCK_SIZE_K * stride_ak);
+            b_ptrs = add(b_ptrs, BLOCK_SIZE_K * stride_bk);
+        }
+
+        // You can fuse arbitrary activation functions here
+        // while the accumulator is still in FP32!
+//        if (ACTIVATION) {
+//            // ...
+//        }
+        var c = Triton.conv(Float16.class, accumulator);
+
+        // -----------------------------------------------------------
+        // Write back the block of the output matrix C with masks.
+        var offs_cm = add(pid_m * BLOCK_SIZE_M, offs_m);
+        var offs_cn = add(pid_n * BLOCK_SIZE_N, offs_n);
+        var c_ptrs = add(c_ptr, add(
+                        mul(stride_cm, expand(offs_cm, 1)),
+                        mul(stride_cn, expand(offs_cn, 0))));
+        var c_mask = and(
+                compare(expand(offs_cm, 1), M, LessThan),
+                compare(expand(offs_cn, 0), N, LessThan));
+        store(c_ptrs, c, c_mask);
+    }
+
+    @TritonTestExtension.Kernel("matmul_kernel")
+    @Test
+    public void test(TritonTestExtension.TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(Float16.FLOAT_16_TYPE),
+                new PtrType(Float16.FLOAT_16_TYPE),
+                new PtrType(Float16.FLOAT_16_TYPE),
+                JavaType.INT, JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                JavaType.INT, JavaType.INT,
+                new ConstantType(JavaType.INT, 32), new ConstantType(JavaType.INT, 64), new ConstantType(JavaType.INT, 32),
+                new ConstantType(JavaType.INT, 8),
+                new ConstantType(JavaType.INT, false));
+
+        t.test(argTypes);
+    }
+
+}
+
+/*
+@triton.jit
+def matmul_kernel(
+        # Pointers to matrices
+        a_ptr, b_ptr, c_ptr,
+        # Matrix dimensions
+        M, N, K,
+        # The stride variables represent how much to increase the ptr by when moving by 1
+        # element in a particular dimension. E.g. `stride_am` is how much to increase `a_ptr`
+        # by to get the element one row down (A has M rows).
+        stride_am, stride_ak,  #
+        stride_bk, stride_bn,  #
+        stride_cm, stride_cn,
+        # Meta-parameters
+        BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,  #
+        GROUP_SIZE_M: tl.constexpr,  #
+        ACTIVATION: tl.constexpr  #
+):
+    """Kernel for computing the matmul C = A x B.
+    A has shape (M, K), B has shape (K, N) and C has shape (M, N)
+    """
+    # -----------------------------------------------------------
+    # Map program ids `pid` to the block of C it should compute.
+    # This is done in a grouped ordering to promote L2 data reuse.
+    # See above `L2 Cache Optimizations` section for details.
+    pid = tl.program_id(axis=0)
+    num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
+    num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
+    num_pid_in_group = GROUP_SIZE_M * num_pid_n
+    group_id = pid // num_pid_in_group
+    first_pid_m = group_id * GROUP_SIZE_M
+    group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
+    pid_m = first_pid_m + (pid % group_size_m)
+    pid_n = (pid % num_pid_in_group) // group_size_m
+
+    # ----------------------------------------------------------
+    # Create pointers for the first blocks of A and B.
+    # We will advance this pointer as we move in the K direction
+    # and accumulate
+    # `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers
+    # `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers
+    # See above `Pointer Arithmetics` section for details
+    offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
+    offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
+    offs_k = tl.arange(0, BLOCK_SIZE_K)
+    a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
+    b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
+
+    # -----------------------------------------------------------
+    # Iterate to compute a block of the C matrix.
+    # We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block
+    # of fp32 values for higher accuracy.
+    # `accumulator` will be converted back to fp16 after the loop.
+    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
+    for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
+        # Load the next block of A and B, generate a mask by checking the K dimension.
+        # If it is out of bounds, set it to 0.
+        a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
+        b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
+        # We accumulate along the K dimension.
+        accumulator += tl.dot(a, b)
+        # Advance the ptrs to the next K block.
+        a_ptrs += BLOCK_SIZE_K * stride_ak
+        b_ptrs += BLOCK_SIZE_K * stride_bk
+    # You can fuse arbitrary activation functions here
+    # while the accumulator is still in FP32!
+    if ACTIVATION == "leaky_relu":
+        accumulator = leaky_relu(accumulator)
+    c = accumulator.to(tl.float16)
+
+    # -----------------------------------------------------------
+    # Write back the block of the output matrix C with masks.
+    offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
+    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
+    c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
+    c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
+    tl.store(c_ptrs, c, mask=c_mask)
+
+
+# We can fuse `leaky_relu` by providing it as an `ACTIVATION` meta-parameter in `_matmul`.
+@triton.jit
+def leaky_relu(x):
+    x = x + 1
+    return tl.where(x >= 0, x, 0.01 * x)
+*/
+
+/*
+
+ triton/python/triton/tools/compile.py \
+    --kernel-name matmul_kernel \
+    --signature "*fp16,*fp16,*fp16,i32,i32,i32,i32,i32,i32,i32,i32,i32,32,64,32,8,0" \
+    --grid=1024,1024,1024 \
+    03-matrix-multiplication.py
+
+BLOCK_SIZE_M = 32
+BLOCK_SIZE_N = 64
+BLOCK_SIZE_K = 32
+GROUP_SIZE_M = 8
+ACTIVATION = 0
+
+module {
+  tt.func public @matmul_kernel_01234567891011(
+            %arg0: !tt.ptr<f16, 1>, %arg1: !tt.ptr<f16, 1>, %arg2: !tt.ptr<f16, 1> ,
+            %arg3: i32, %arg4: i32, %arg5: i32 ,
+            %arg6: i32, %arg7: i32, %arg8: i32 ,
+            %%arg9: i32, %arg10: i32, %arg11: i32 ) attributes {noinline = false} {
+    %0 = tt.get_program_id x : i32
+    %1 = tt.call @cdiv__i32__1cconstexpr_32_(%arg3) : (i32) -> i32
+    %2 = tt.call @cdiv__i32__1cconstexpr_64_(%arg4) : (i32) -> i32
+    %c8_i32 = arith.constant 8 : i32
+    %3 = arith.muli %2, %c8_i32 : i32
+    %4 = arith.divsi %0, %3 : i32
+    %c8_i32_0 = arith.constant 8 : i32
+    %5 = arith.muli %4, %c8_i32_0 : i32
+    %6 = arith.subi %1, %5 : i32
+    %7 = tt.call @minimum__i32__1cconstexpr_8_(%6) : (i32) -> i32
+    %8 = arith.remsi %0, %7 : i32
+    %9 = arith.addi %5, %8 : i32
+    %10 = arith.remsi %0, %3 : i32
+    %11 = arith.divsi %10, %7 : i32
+    %c32_i32 = arith.constant 32 : i32
+    %12 = arith.muli %9, %c32_i32 : i32
+    %13 = tt.make_range {end = 32 : i32, start = 0 : i32} : tensor<32xi32>
+    %14 = tt.splat %12 : (i32) -> tensor<32xi32>
+    %15 = arith.addi %14, %13 : tensor<32xi32>
+    %16 = tt.splat %arg3 : (i32) -> tensor<32xi32>
+    %17 = arith.remsi %15, %16 : tensor<32xi32>
+    %c64_i32 = arith.constant 64 : i32
+    %18 = arith.muli %11, %c64_i32 : i32
+    %19 = tt.make_range {end = 64 : i32, start = 0 : i32} : tensor<64xi32>
+    %20 = tt.splat %18 : (i32) -> tensor<64xi32>
+    %21 = arith.addi %20, %19 : tensor<64xi32>
+    %22 = tt.splat %arg4 : (i32) -> tensor<64xi32>
+    %23 = arith.remsi %21, %22 : tensor<64xi32>
+    %24 = tt.make_range {end = 32 : i32, start = 0 : i32} : tensor<32xi32>
+    %25 = tt.expand_dims %17 {axis = 1 : i32} : (tensor<32xi32>) -> tensor<32x1xi32>
+    %26 = tt.splat %arg6 : (i32) -> tensor<32x1xi32>
+    %27 = arith.muli %25, %26 : tensor<32x1xi32>
+    %28 = tt.expand_dims %24 {axis = 0 : i32} : (tensor<32xi32>) -> tensor<1x32xi32>
+    %29 = tt.splat %arg7 : (i32) -> tensor<1x32xi32>
+    %30 = arith.muli %28, %29 : tensor<1x32xi32>
+    %31 = tt.broadcast %27 : (tensor<32x1xi32>) -> tensor<32x32xi32>
+    %32 = tt.broadcast %30 : (tensor<1x32xi32>) -> tensor<32x32xi32>
+    %33 = arith.addi %31, %32 : tensor<32x32xi32>
+    %34 = tt.splat %arg0 : (!tt.ptr<f16, 1>) -> tensor<32x32x!tt.ptr<f16, 1>>
+    %35 = tt.addptr %34, %33 : tensor<32x32x!tt.ptr<f16, 1>>, tensor<32x32xi32>
+    %36 = tt.expand_dims %24 {axis = 1 : i32} : (tensor<32xi32>) -> tensor<32x1xi32>
+    %37 = tt.splat %arg8 : (i32) -> tensor<32x1xi32>
+    %38 = arith.muli %36, %37 : tensor<32x1xi32>
+    %39 = tt.expand_dims %23 {axis = 0 : i32} : (tensor<64xi32>) -> tensor<1x64xi32>
+    %40 = tt.splat %arg9 : (i32) -> tensor<1x64xi32>
+    %41 = arith.muli %39, %40 : tensor<1x64xi32>
+    %42 = tt.broadcast %38 : (tensor<32x1xi32>) -> tensor<32x64xi32>
+    %43 = tt.broadcast %41 : (tensor<1x64xi32>) -> tensor<32x64xi32>
+    %44 = arith.addi %42, %43 : tensor<32x64xi32>
+    %45 = tt.splat %arg1 : (!tt.ptr<f16, 1>) -> tensor<32x64x!tt.ptr<f16, 1>>
+    %46 = tt.addptr %45, %44 : tensor<32x64x!tt.ptr<f16, 1>>, tensor<32x64xi32>
+    %47 = tt.call @"zeros____0cconstexpr_(constexpr_32_, constexpr_64_)__1cconstexpr_fp32_"() : () -> tensor<32x64xf32>
+    %48 = tt.call @cdiv__i32__1cconstexpr_32_(%arg5) : (i32) -> i32
+    %c0_i32 = arith.constant 0 : i32
+    %c1_i32 = arith.constant 1 : i32
+    %49 = arith.bitcast %c0_i32 : i32 to i32
+    %50 = arith.bitcast %48 : i32 to i32
+    %51 = arith.bitcast %c1_i32 : i32 to i32
+    %52 = llvm.mlir.undef : i32
+    %53:3 = scf.for %arg12 = %49 to %50 step %51 iter_args(%arg13 = %47, %arg14 = %35, %arg15 = %46) -> (tensor<32x64xf32>, tensor<32x32x!tt.ptr<f16, 1>>, tensor<32x64x!tt.ptr<f16, 1>>)  : i32 {
+      %83 = tt.expand_dims %24 {axis = 0 : i32} : (tensor<32xi32>) -> tensor<1x32xi32>
+      %c32_i32_3 = arith.constant 32 : i32
+      %84 = arith.muli %arg12, %c32_i32_3 : i32
+      %85 = arith.subi %arg5, %84 : i32
+      %86 = tt.splat %85 : (i32) -> tensor<1x32xi32>
+      %87 = arith.cmpi slt, %83, %86 : tensor<1x32xi32>
+      %cst = arith.constant 0.000000e+00 : f32
+      %88 = tt.broadcast %87 : (tensor<1x32xi1>) -> tensor<32x32xi1>
+      %cst_4 = arith.constant dense<0.000000e+00> : tensor<32x32xf32>
+      %89 = arith.truncf %cst_4 : tensor<32x32xf32> to tensor<32x32xf16>
+      %90 = tt.load %arg14, %88, %89 {cache = 1 : i32, evict = 1 : i32, isVolatile = false} : tensor<32x32xf16>
+      %91 = tt.expand_dims %24 {axis = 1 : i32} : (tensor<32xi32>) -> tensor<32x1xi32>
+      %c32_i32_5 = arith.constant 32 : i32
+      %92 = arith.muli %arg12, %c32_i32_5 : i32
+      %93 = arith.subi %arg5, %92 : i32
+      %94 = tt.splat %93 : (i32) -> tensor<32x1xi32>
+      %95 = arith.cmpi slt, %91, %94 : tensor<32x1xi32>
+      %cst_6 = arith.constant 0.000000e+00 : f32
+      %96 = tt.broadcast %95 : (tensor<32x1xi1>) -> tensor<32x64xi1>
+      %cst_7 = arith.constant dense<0.000000e+00> : tensor<32x64xf32>
+      %97 = arith.truncf %cst_7 : tensor<32x64xf32> to tensor<32x64xf16>
+      %98 = tt.load %arg15, %96, %97 {cache = 1 : i32, evict = 1 : i32, isVolatile = false} : tensor<32x64xf16>
+      %cst_8 = arith.constant 0.000000e+00 : f32
+      %cst_9 = arith.constant dense<0.000000e+00> : tensor<32x64xf32>
+      %99 = tt.dot %90, %98, %cst_9 {allowTF32 = true, maxNumImpreciseAcc = 0 : i32} : tensor<32x32xf16> * tensor<32x64xf16> -> tensor<32x64xf32>
+      %100 = arith.addf %arg13, %99 : tensor<32x64xf32>
+      %c32_i32_10 = arith.constant 32 : i32
+      %101 = arith.muli %arg7, %c32_i32_10 : i32
+      %102 = tt.splat %101 : (i32) -> tensor<32x32xi32>
+      %103 = tt.addptr %arg14, %102 : tensor<32x32x!tt.ptr<f16, 1>>, tensor<32x32xi32>
+      %c32_i32_11 = arith.constant 32 : i32
+      %104 = arith.muli %arg8, %c32_i32_11 : i32
+      %105 = tt.splat %104 : (i32) -> tensor<32x64xi32>
+      %106 = tt.addptr %arg15, %105 : tensor<32x64x!tt.ptr<f16, 1>>, tensor<32x64xi32>
+      scf.yield %100, %103, %106 : tensor<32x64xf32>, tensor<32x32x!tt.ptr<f16, 1>>, tensor<32x64x!tt.ptr<f16, 1>>
+    }
+    %54 = arith.truncf %53#0 : tensor<32x64xf32> to tensor<32x64xf16>
+    %c32_i32_1 = arith.constant 32 : i32
+    %55 = arith.muli %9, %c32_i32_1 : i32
+    %56 = tt.make_range {end = 32 : i32, start = 0 : i32} : tensor<32xi32>
+    %57 = tt.splat %55 : (i32) -> tensor<32xi32>
+    %58 = arith.addi %57, %56 : tensor<32xi32>
+    %c64_i32_2 = arith.constant 64 : i32
+    %59 = arith.muli %11, %c64_i32_2 : i32
+    %60 = tt.make_range {end = 64 : i32, start = 0 : i32} : tensor<64xi32>
+    %61 = tt.splat %59 : (i32) -> tensor<64xi32>
+    %62 = arith.addi %61, %60 : tensor<64xi32>
+    %63 = tt.expand_dims %58 {axis = 1 : i32} : (tensor<32xi32>) -> tensor<32x1xi32>
+    %64 = tt.splat %arg10 : (i32) -> tensor<32x1xi32>
+    %65 = arith.muli %64, %63 : tensor<32x1xi32>
+    %66 = tt.splat %arg2 : (!tt.ptr<f16, 1>) -> tensor<32x1x!tt.ptr<f16, 1>>
+    %67 = tt.addptr %66, %65 : tensor<32x1x!tt.ptr<f16, 1>>, tensor<32x1xi32>
+    %68 = tt.expand_dims %62 {axis = 0 : i32} : (tensor<64xi32>) -> tensor<1x64xi32>
+    %69 = tt.splat %arg11 : (i32) -> tensor<1x64xi32>
+    %70 = arith.muli %69, %68 : tensor<1x64xi32>
+    %71 = tt.broadcast %67 : (tensor<32x1x!tt.ptr<f16, 1>>) -> tensor<32x64x!tt.ptr<f16, 1>>
+    %72 = tt.broadcast %70 : (tensor<1x64xi32>) -> tensor<32x64xi32>
+    %73 = tt.addptr %71, %72 : tensor<32x64x!tt.ptr<f16, 1>>, tensor<32x64xi32>
+    %74 = tt.expand_dims %58 {axis = 1 : i32} : (tensor<32xi32>) -> tensor<32x1xi32>
+    %75 = tt.splat %arg3 : (i32) -> tensor<32x1xi32>
+    %76 = arith.cmpi slt, %74, %75 : tensor<32x1xi32>
+    %77 = tt.expand_dims %62 {axis = 0 : i32} : (tensor<64xi32>) -> tensor<1x64xi32>
+    %78 = tt.splat %arg4 : (i32) -> tensor<1x64xi32>
+    %79 = arith.cmpi slt, %77, %78 : tensor<1x64xi32>
+    %80 = tt.broadcast %76 : (tensor<32x1xi1>) -> tensor<32x64xi1>
+    %81 = tt.broadcast %79 : (tensor<1x64xi1>) -> tensor<32x64xi1>
+    %82 = arith.andi %80, %81 : tensor<32x64xi1>
+    tt.store %73, %54, %82 {cache = 1 : i32, evict = 1 : i32} : tensor<32x64xf16>
+    tt.return
+  }
+  tt.func private @cdiv__i32__1cconstexpr_32_(%arg0: i32 ) -> i32 attributes {noinline = false} {
+    %c32_i32 = arith.constant 32 : i32
+    %0 = arith.addi %arg0, %c32_i32 : i32
+    %c1_i32 = arith.constant 1 : i32
+    %1 = arith.subi %0, %c1_i32 : i32
+    %c32_i32_0 = arith.constant 32 : i32
+    %2 = arith.divsi %1, %c32_i32_0 : i32
+    tt.return %2 : i32
+  }
+  tt.func private @cdiv__i32__1cconstexpr_64_(%arg0: i32 ) -> i32 attributes {noinline = false} {
+    %c64_i32 = arith.constant 64 : i32
+    %0 = arith.addi %arg0, %c64_i32 : i32
+    %c1_i32 = arith.constant 1 : i32
+    %1 = arith.subi %0, %c1_i32 : i32
+    %c64_i32_0 = arith.constant 64 : i32
+    %2 = arith.divsi %1, %c64_i32_0 : i32
+    tt.return %2 : i32
+  }
+  tt.func private @minimum__i32__1cconstexpr_8_(%arg0: i32 ) -> i32 attributes {noinline = false} {
+    %c8_i32 = arith.constant 8 : i32
+    %0 = arith.minsi %arg0, %c8_i32 : i32
+    tt.return %0 : i32
+  }
+  tt.func private @"zeros____0cconstexpr_(constexpr_32_, constexpr_64_)__1cconstexpr_fp32_"() -> tensor<32x64xf32> attributes {noinline = false} {
+    %cst = arith.constant 0.000000e+00 : f32
+    %cst_0 = arith.constant dense<0.000000e+00> : tensor<32x64xf32>
+    tt.return %cst_0 : tensor<32x64xf32>
+  }
+}
+ */
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestSoftMax.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestSoftMax.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestSoftMax.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import oracle.code.triton.TritonTestExtension.Kernel;
+import oracle.code.triton.TritonTestExtension.TritonTestData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestSoftMax {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"max_float_float_float" (%0 : float, %1 : float)float -> {
+                    %2 : float = arith.maximumf %0 %1;
+                    tt.return %2;
+                };
+                tt.func @"reduce_max_float_float_float_0" (%3 : tensor<x64, float>)float -> {
+                    %4 : float = tt.reduce %3 @axis="0" (%5 : float, %6 : float)float -> {
+                        %7 : float = tt.call %5 %6 @"max_float_float_float";
+                        tt.reduce.return %7;
+                    };
+                    tt.return %4;
+                };
+                tt.func @"sum_float_float_float" (%8 : float, %9 : float)float -> {
+                    %10 : float = arith.addf %8 %9;
+                    tt.return %10;
+                };
+                tt.func @"reduce_sum_float_float_float_0" (%11 : tensor<x64, float>)float -> {
+                    %12 : float = tt.reduce %11 @axis="0" (%13 : float, %14 : float)float -> {
+                        %15 : float = tt.call %13 %14 @"sum_float_float_float";
+                        tt.reduce.return %15;
+                    };
+                    tt.return %12;
+                };
+                tt.func @"softmax_kernel_ptr<float>_ptr<float>_1_1_10_64_void" (%16 : ptr<float>, %17 : ptr<float>)void -> {
+                    %18 : int = arith.constant @"1";
+                    %19 : int = arith.constant @"1";
+                    %20 : int = arith.constant @"10";
+                    %21 : int = tt.get_program_id @"0";
+                    %22 : int = arith.muli %21 %18;
+                    %23 : ptr<float> = tt.addptr %17 %22;
+                    %24 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %25 : tensor<x64, ptr<float>> = tt.splat %23;
+                    %26 : tensor<x64, ptr<float>> = tt.addptr %25 %24;
+                    %27 : tensor<x64, int> = tt.splat %20;
+                    %28 : tensor<x64, int> = arith.cmpi %24 %27 @"slt";
+                    %29 : tensor<x64, float> = tt.load %26 %28;
+                    %30 : float = tt.call %29 @"reduce_max_float_float_float_0";
+                    %31 : tensor<x64, float> = tt.splat %30;
+                    %32 : tensor<x64, float> = arith.subf %29 %31;
+                    %33 : tensor<x64, float> = math.exp %32;
+                    %34 : float = tt.call %33 @"reduce_sum_float_float_float_0";
+                    %35 : tensor<x64, float> = tt.splat %34;
+                    %36 : tensor<x64, float> = arith.divf %33 %35;
+                    %37 : int = arith.muli %21 %19;
+                    %38 : ptr<float> = tt.addptr %16 %37;
+                    %39 : tensor<x64, ptr<float>> = tt.splat %38;
+                    %40 : tensor<x64, ptr<float>> = tt.addptr %39 %24;
+                    tt.store %40 %36 %28;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void softmax_kernel(Ptr output_ptr,
+                               Ptr input_ptr,
+                               int input_row_stride,
+                               int output_row_stride,
+                               int n_cols,
+                               @Constant int BLOCK_SIZE) {
+        // The rows of the softmax are independent, so we parallelize across those
+        var row_idx = Triton.programId(0);
+        var row_start_ptr = Triton.add(input_ptr, row_idx * input_row_stride);
+        // The block size is the next power of two greater than n_cols, so we can fit each
+        // row in a single block
+        var col_offsets = Triton.arange(0, BLOCK_SIZE);
+        var input_ptrs = Triton.add(Triton.broadcast(row_start_ptr, col_offsets.type()), col_offsets);
+        // Load the row into SRAM, using a mask since BLOCK_SIZE may be > than n_cols
+        var mask = Triton.compare(col_offsets,
+                Triton.broadcast(n_cols, col_offsets.type()),
+                Triton.CompareKind.LessThan);
+        var row = Triton.load(input_ptrs, mask);
+        // Subtract maximum for numerical stability
+        var row_minus_max = Triton.sub(row, Triton.broadcast(Triton.max(row, 0), row.type()));
+        // Note that exponentiation in Triton is fast but approximate (i.e., think __expf in CUDA)
+        var numerator = Triton.exp(row_minus_max);
+        var denominator = Triton.sum(numerator, 0);
+        var softmax_output = Triton.div(numerator, Triton.broadcast(denominator, numerator.type()));
+        // Write back output to DRAM
+        var output_row_start_ptr = Triton.add(output_ptr, row_idx * output_row_stride);
+        var output_ptrs = Triton.add(Triton.broadcast(output_row_start_ptr, col_offsets.type()), col_offsets);
+        Triton.store(output_ptrs, softmax_output, mask);
+    }
+
+    @Kernel("softmax_kernel")
+    @Test
+    public void test(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                new ConstantType(JavaType.INT, 1),
+                new ConstantType(JavaType.INT, 1),
+                new ConstantType(JavaType.INT, 10),
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"max_float_float_float" (%0 : float, %1 : float)float -> {
+                    %2 : float = arith.maximumf %0 %1;
+                    tt.return %2;
+                };
+                tt.func @"reduce_max_float_float_float_0" (%3 : tensor<x64, float>)float -> {
+                    %4 : float = tt.reduce %3 @axis="0" (%5 : float, %6 : float)float -> {
+                        %7 : float = tt.call %5 %6 @"max_float_float_float";
+                        tt.reduce.return %7;
+                    };
+                    tt.return %4;
+                };
+                tt.func @"sum_float_float_float" (%8 : float, %9 : float)float -> {
+                    %10 : float = arith.addf %8 %9;
+                    tt.return %10;
+                };
+                tt.func @"reduce_sum_float_float_float_0" (%11 : tensor<x64, float>)float -> {
+                    %12 : float = tt.reduce %11 @axis="0" (%13 : float, %14 : float)float -> {
+                        %15 : float = tt.call %13 %14 @"sum_float_float_float";
+                        tt.reduce.return %15;
+                    };
+                    tt.return %12;
+                };
+                tt.func @"softmax_kernel2_ptr<float>_ptr<float>_1_1_10_64_void" (%16 : ptr<float>, %17 : ptr<float>)void -> {
+                    %18 : int = arith.constant @"1";
+                    %19 : int = arith.constant @"1";
+                    %20 : int = arith.constant @"10";
+                    %21 : int = tt.get_program_id @"0";
+                    %22 : int = arith.muli %21 %18;
+                    %23 : ptr<float> = tt.addptr %17 %22;
+                    %24 : tensor<x64, int> = tt.make_range @start="0" @end="64";
+                    %25 : tensor<x64, ptr<float>> = tt.splat %23;
+                    %26 : tensor<x64, ptr<float>> = tt.addptr %25 %24;
+                    %27 : tensor<x64, int> = tt.splat %20;
+                    %28 : tensor<x64, int> = arith.cmpi %24 %27 @"slt";
+                    %29 : tensor<x64, float> = tt.load %26 %28;
+                    %30 : float = tt.call %29 @"reduce_max_float_float_float_0";
+                    %31 : tensor<x64, float> = tt.splat %30;
+                    %32 : tensor<x64, float> = arith.subf %29 %31;
+                    %33 : tensor<x64, float> = math.exp %32;
+                    %34 : float = tt.call %33 @"reduce_sum_float_float_float_0";
+                    %35 : tensor<x64, float> = tt.splat %34;
+                    %36 : tensor<x64, float> = arith.divf %33 %35;
+                    %37 : int = arith.muli %21 %19;
+                    %38 : ptr<float> = tt.addptr %16 %37;
+                    %39 : tensor<x64, ptr<float>> = tt.splat %38;
+                    %40 : tensor<x64, ptr<float>> = tt.addptr %39 %24;
+                    tt.store %40 %36 %28;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void softmax_kernel2(Ptr output_ptr,
+                                Ptr input_ptr,
+                                int input_row_stride,
+                                int output_row_stride,
+                                int n_cols,
+                                @Constant int BLOCK_SIZE) {
+        // The rows of the softmax are independent, so we parallelize across those
+        var row_idx = Triton.programId(0);
+        var row_start_ptr = Triton.add(input_ptr, row_idx * input_row_stride);
+        // The block size is the next power of two greater than n_cols, so we can fit each
+        // row in a single block
+        var col_offsets = Triton.arange(0, BLOCK_SIZE);
+        var input_ptrs = Triton.add(row_start_ptr, col_offsets);
+        // Load the row into SRAM, using a mask since BLOCK_SIZE may be > than n_cols
+        var mask = Triton.compare(col_offsets, n_cols, Triton.CompareKind.LessThan);
+        var row = Triton.load(input_ptrs, mask);
+        // Subtract maximum for numerical stability
+        var row_minus_max = Triton.sub(row, Triton.max(row, 0));
+        // Note that exponentiation in Triton is fast but approximate (i.e., think __expf in CUDA)
+        var numerator = Triton.exp(row_minus_max);
+        var denominator = Triton.sum(numerator, 0);
+        var softmax_output = Triton.div(numerator, denominator);
+        // Write back output to DRAM
+        var output_row_start_ptr = Triton.add(output_ptr, row_idx * output_row_stride);
+        var output_ptrs = Triton.add(output_row_start_ptr, col_offsets);
+        Triton.store(output_ptrs, softmax_output, mask);
+    }
+
+    @Kernel("softmax_kernel2")
+    @Test
+    public void test2(TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new PtrType(JavaType.FLOAT),
+                new PtrType(JavaType.FLOAT),
+                new ConstantType(JavaType.INT, 1),
+                new ConstantType(JavaType.INT, 1),
+                new ConstantType(JavaType.INT, 10),
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+}
+
+/*
+def softmax_kernel(output_ptr, input_ptr, input_row_stride, output_row_stride, n_cols, BLOCK_SIZE: tl.constexpr):
+    # The rows of the softmax are independent, so we parallelize across those
+    row_idx = tl.program_id(0)
+    # The stride represents how much we need to increase the pointer to advance 1 row
+    row_start_ptr = input_ptr + row_idx * input_row_stride
+    # The block size is the next power of two greater than n_cols, so we can fit each
+    # row in a single block
+    col_offsets = tl.arange(0, BLOCK_SIZE)
+    input_ptrs = row_start_ptr + col_offsets
+    # Load the row into SRAM, using a mask since BLOCK_SIZE may be > than n_cols
+    row = tl.load(input_ptrs, mask=col_offsets < n_cols, other=-float('inf'))
+    # Subtract maximum for numerical stability
+    row_minus_max = row - tl.max(row, axis=0)
+    # Note that exponentiation in Triton is fast but approximate (i.e., think __expf in CUDA)
+    numerator = tl.exp(row_minus_max)
+    denominator = tl.sum(numerator, axis=0)
+    softmax_output = numerator / denominator
+    # Write back output to DRAM
+    output_row_start_ptr = output_ptr + row_idx * output_row_stride
+    output_ptrs = output_row_start_ptr + col_offsets
+    tl.store(output_ptrs, softmax_output, mask=col_offsets < n_cols)
+*/
+
+/*
+input_row_stride = 1
+output_row_stride = 1
+n_cols=10
+BLOCK_SIZE=64
+
+module {
+  tt.func public @softmax_kernel_01(%arg0: !tt.ptr<f32, 1> , %arg1: !tt.ptr<f32, 1> ) attributes {noinline = false} {
+    %0 = tt.get_program_id x : i32
+    %c1_i32 = arith.constant 1 : i32
+    %1 = arith.muli %0, %c1_i32 : i32
+    %2 = tt.addptr %arg1, %1 : !tt.ptr<f32, 1>, i32
+    %3 = tt.make_range {end = 64 : i32, start = 0 : i32} : tensor<64xi32>
+    %4 = tt.splat %2 : (!tt.ptr<f32, 1>) -> tensor<64x!tt.ptr<f32, 1>>
+    %5 = tt.addptr %4, %3 : tensor<64x!tt.ptr<f32, 1>>, tensor<64xi32>
+    %c10_i32 = arith.constant 10 : i32
+    %cst = arith.constant dense<10> : tensor<64xi32>
+    %6 = arith.cmpi slt, %3, %cst : tensor<64xi32>
+    %cst_0 = arith.constant 0xFF800000 : f32
+    %cst_1 = arith.constant dense<0xFF800000> : tensor<64xf32>
+    %7 = tt.load %5, %6, %cst_1 {cache = 1 : i32, evict = 1 : i32, isVolatile = false} : tensor<64xf32>
+    %8 = tt.call @max__fp32S64S__1cconstexpr_0__2cconstexpr_False__3cconstexpr_True_(%7) : (tensor<64xf32>) -> f32
+    %9 = tt.splat %8 : (f32) -> tensor<64xf32>
+    %10 = arith.subf %7, %9 : tensor<64xf32>
+    %11 = math.exp %10 : tensor<64xf32>
+    %12 = tt.call @sum__fp32S64S__1cconstexpr_0_(%11) : (tensor<64xf32>) -> f32
+    %13 = tt.splat %12 : (f32) -> tensor<64xf32>
+    %14 = arith.divf %11, %13 : tensor<64xf32>
+    %c1_i32_2 = arith.constant 1 : i32
+    %15 = arith.muli %0, %c1_i32_2 : i32
+    %16 = tt.addptr %arg0, %15 : !tt.ptr<f32, 1>, i32
+    %17 = tt.splat %16 : (!tt.ptr<f32, 1>) -> tensor<64x!tt.ptr<f32, 1>>
+    %18 = tt.addptr %17, %3 : tensor<64x!tt.ptr<f32, 1>>, tensor<64xi32>
+    %c10_i32_3 = arith.constant 10 : i32
+    %cst_4 = arith.constant dense<10> : tensor<64xi32>
+    %19 = arith.cmpi slt, %3, %cst_4 : tensor<64xi32>
+    tt.store %18, %14, %19 {cache = 1 : i32, evict = 1 : i32} : tensor<64xf32>
+    tt.return
+  }
+  tt.func private @max__fp32S64S__1cconstexpr_0__2cconstexpr_False__3cconstexpr_True_(%arg0: tensor<64xf32> ) -> f32 attributes {noinline = false} {
+    %0 = "tt.reduce"(%arg0) <{axis = 0 : i32}> ({
+    ^bb0(%arg1: f32 , %arg2: f32 ):
+      %1 = tt.call @maximum__fp32_fp32__(%arg1, %arg2) : (f32, f32) -> f32
+      tt.reduce.return %1 : f32
+    }) : (tensor<64xf32>) -> f32
+    tt.return %0 : f32
+  }
+  tt.func private @maximum__fp32_fp32__(%arg0: f32 , %arg1: f32 ) -> f32 attributes {noinline = false} {
+    %0 = arith.maximumf %arg0, %arg1 : f32
+    tt.return %0 : f32
+  }
+  tt.func private @sum__fp32S64S__1cconstexpr_0_(%arg0: tensor<64xf32> ) -> f32 attributes {noinline = false} {
+    %0 = "tt.reduce"(%arg0) <{axis = 0 : i32}> ({
+    ^bb0(%arg1: f32 , %arg2: f32 ):
+      %1 = tt.call @_sum_combine__fp32_fp32__(%arg1, %arg2) : (f32, f32) -> f32
+      tt.reduce.return %1 : f32
+    }) : (tensor<64xf32>) -> f32
+    tt.return %0 : f32
+  }
+  tt.func private @_sum_combine__fp32_fp32__(%arg0: f32 , %arg1: f32 ) -> f32 attributes {noinline = false} {
+    %0 = arith.addf %arg0, %arg1 : f32
+    tt.return %0 : f32
+  }
+}
+*/
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestVariables.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestVariables.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestVariables.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.zeros;
+import static oracle.code.triton.TritonTest.consume;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestVariables {
+
+    @CodeReflection
+    static void test1(@Constant int M) {
+        var m = Triton.arange(0, M);
+        var e = Triton.expand(m, 0);
+        consume(e);
+        // Store to variable fails since tensor is of different shape
+        e = Triton.expand(m, 1);
+        consume(e);
+    }
+
+    @Test
+    public void test1(TritonTestExtension.TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new ConstantType(JavaType.INT, 32));
+
+        Assertions.assertThrows(IllegalStateException.class, () -> {
+            t.test(argTypes);
+        });
+    }
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TestZeros.java b/cr-examples/triton/src/test/java/oracle/code/triton/TestZeros.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TestZeros.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+import static oracle.code.triton.Triton.*;
+import static oracle.code.triton.TritonTest.consume;
+
+@ExtendWith(TritonTestExtension.class)
+public class TestZeros {
+
+    @TritonCodeModel("""
+            module ()void -> {
+                tt.func @"test1_32_64_void" ()void -> {
+                    %0 : tensor<x32, x64, float> = arith.constant @"0.0";
+                    tt.consume %0;
+                    tt.return;
+                };
+                unreachable;
+            };
+            """)
+    @CodeReflection
+    static void test1(@Constant int M, @Constant int N) {
+        var t = zeros(float.class, M, N);
+        consume(t);
+    }
+
+    @Test
+    public void test1(TritonTestExtension.TritonTestData t) {
+        List<TypeElement> argTypes = List.of(
+                new ConstantType(JavaType.INT, 32),
+                new ConstantType(JavaType.INT, 64));
+
+        t.test(argTypes);
+    }
+
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TritonCodeModel.java b/cr-examples/triton/src/test/java/oracle/code/triton/TritonCodeModel.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TritonCodeModel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TritonCodeModel {
+    String value();
+
+    boolean SSA() default true;
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TritonTest.java b/cr-examples/triton/src/test/java/oracle/code/triton/TritonTest.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TritonTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+public class TritonTest {
+    public static void consume(Object o) {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/cr-examples/triton/src/test/java/oracle/code/triton/TritonTestExtension.java b/cr-examples/triton/src/test/java/oracle/code/triton/TritonTestExtension.java
--- /dev/null
+++ b/cr-examples/triton/src/test/java/oracle/code/triton/TritonTestExtension.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package oracle.code.triton;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TritonTestExtension implements ParameterResolver {
+
+    @Target({ElementType.METHOD, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Kernel {
+        String value();
+    }
+
+    @Override
+    public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
+        return pc.getParameter().getType() == TritonTestData.class;
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext pc, ExtensionContext ec) {
+        Kernel k = ec.getRequiredTestMethod().getAnnotation(Kernel.class);
+        String kernelName = (k != null)
+            ? k.value()
+            : ec.getRequiredTestMethod().getName();
+
+        return new TritonTestData(ec.getRequiredTestClass(), kernelName);
+    }
+
+    public static class TritonTestData {
+        final Class<?> testClass;
+        final String javaKernelName;
+
+        public TritonTestData(Class<?> testClass, String javaKernelName) {
+            this.testClass = testClass;
+            this.javaKernelName = javaKernelName;
+        }
+
+        public void test(List<? extends TypeElement> argTypes) {
+            Optional<Method> om = Stream.of(testClass.getDeclaredMethods())
+                    .filter(m -> m.getName().equals(javaKernelName))
+                    .filter(m -> m.getAnnotation(CodeReflection.class) != null)
+                    .findFirst();
+            Method m = om.get();
+            TritonCodeModel tcm = m.getAnnotation(TritonCodeModel.class);
+            boolean doSSA = tcm != null ? tcm.SSA() : true;
+            test(m.getCodeModel().get(), argTypes, expectedTritonKernel(tcm), doSSA);
+        }
+
+        public TritonOps.ModuleOp expectedTritonKernel(TritonCodeModel tcm) {
+            if (tcm == null || tcm.value().isEmpty()) {
+                return null;
+            }
+
+            return (TritonOps.ModuleOp) OpParser.fromString(
+                    TritonOps.FACTORY.andThen(ArithMathOps.FACTORY)
+                            .andThen(TritonTestOps.FACTORY)
+                            .andThen(SCFOps.FACTORY)
+                            .andThen(CoreOp.FACTORY),
+                    TritonOps.TYPE_FACTORY,
+                    tcm.value()).get(0);
+        }
+
+        void test(CoreOp.FuncOp javaKernel,
+                  List<? extends TypeElement> argTypes,
+                  TritonOps.ModuleOp expectedTritonKernel,
+                  boolean doSSA) {
+            TritonOps.ModuleOp actualTritonKernel = ScopedValue.getWhere(TritonTransformer.SV_SSA, doSSA,() -> {
+                return TritonTransformer.tritonModule(javaKernel, JavaType.VOID, argTypes);
+            });
+
+            Assertions.assertEquals(
+                    expectedTritonKernel == null ? "NO @TritonCodeModel" : expectedTritonKernel.toText(),
+                    actualTritonKernel.toText());
+        }
+    }
+
+}
diff a/cr-util/copy-to-compiler.sh b/cr-util/copy-to-compiler.sh
--- /dev/null
+++ b/cr-util/copy-to-compiler.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# copy selected cr packages into jdk.compiler
+
+base=java/lang/reflect/code
+packages="
+  $base \
+  $base/writer \
+  $base/op \
+  $base/type \
+  $base/type/impl \
+  "
+
+removeclasses=""
+
+java_base_dir=$1
+jdk_compiler_dir=$2
+
+for p in $packages; do
+  mkdir -p $jdk_compiler_dir/jdk/internal/$p
+  cp -r $java_base_dir/$p/*.java $jdk_compiler_dir/jdk/internal/$p/.
+done
+
+for f in $removeclasses; do
+  rm $jdk_compiler_dir/jdk/internal/$f
+done
+
+find $jdk_compiler_dir/jdk/internal/$base -name "*.java" -print \
+  | xargs sed -i'.bck' \
+  -e 's/java\.lang\.reflect\.code/jdk\.internal\.java\.lang\.reflect\.code/g' \
+  -e 's/^\/\*__\(.*\)__\*\/.*$/\1/'
+
+find $jdk_compiler_dir/jdk/internal/$base -name "*.bck" -exec rm {} \;
+
diff a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf
--- a/make/conf/module-loader-map.conf
+++ b/make/conf/module-loader-map.conf
@@ -73,10 +73,11 @@
     java.sql \
     java.sql.rowset \
     java.transaction.xa \
     java.xml.crypto \
     jdk.accessibility \
+    jdk.code.tools \
     jdk.charsets \
     jdk.crypto.cryptoki \
     jdk.dynalink \
     jdk.httpserver \
     jdk.jsobject \
diff a/make/langtools/build.xml b/make/langtools/build.xml
--- a/make/langtools/build.xml
+++ b/make/langtools/build.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
+  ~ Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved.
   ~ DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   ~
   ~ This code is free software; you can redistribute it and/or modify it
   ~ under the terms of the GNU General Public License version 2 only, as
   ~ published by the Free Software Foundation.  Oracle designates this
@@ -193,11 +193,19 @@
         <pcompile destdir="${build.gensrc}/${module.name}" includes="**/*.properties">
             <src path="./share/classes"/>
         </pcompile>
     </target>
 
-    <target name="generate-sources"  depends="-prepare-build,-def-pparse,-def-pcompile">
+    <target name="copy-cr-sources">
+        <exec executable="/bin/bash">
+            <arg value="cr-util/copy-to-compiler.sh"/>
+            <arg value="src/java.base/share/classes"/>
+            <arg value="${build.gensrc}/jdk.compiler"/>
+        </exec>
+    </target>
+
+    <target name="generate-sources"  depends="copy-cr-sources,-prepare-build,-def-pparse,-def-pcompile">
         <subant inheritall="true" target="generate-sources-internal" genericantfile="${make.dir}/build.xml">
               <dirset refid="src.module.dirset"/>
         </subant>
     </target>
 
diff a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk
--- a/make/modules/java.base/Java.gmk
+++ b/make/modules/java.base/Java.gmk
@@ -26,14 +26,16 @@
 # The base module should be built with all warnings enabled. When a
 # new warning is added to javac, it can be temporarily added to the
 # disabled warnings list.
 #
 # DISABLED_WARNINGS_java +=
+DISABLED_WARNINGS_java += preview
 
-DOCLINT += -Xdoclint:all/protected \
-    '-Xdoclint/package:java.*,javax.*'
-JAVAC_FLAGS += -XDstringConcat=inline
+# DOCLINT += -Xdoclint:all/protected \
+#     '-Xdoclint/package:java.*,javax.*'
+JAVAC_FLAGS += -XDstringConcat=inline \
+    --enable-preview
 COPY += .icu .dat .spp .nrm content-types.properties \
     hijrah-config-Hijrah-umalqura_islamic-umalqura.properties
 CLEAN += intrinsic.properties
 
 EXCLUDE_FILES += \
diff a/make/modules/jdk.compiler/Gensrc.gmk b/make/modules/jdk.compiler/Gensrc.gmk
--- a/make/modules/jdk.compiler/Gensrc.gmk
+++ b/make/modules/jdk.compiler/Gensrc.gmk
@@ -1,7 +1,7 @@
 #
-# Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License version 2 only, as
 # published by the Free Software Foundation.  Oracle designates this
@@ -70,5 +70,28 @@
     OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/gensrc/$(MODULE), \
     COMMAND := $(TOOL_PARSEPROPERTIES_CMD) $(PARSEPROPERTIES_ARGS), \
 ))
 
 TARGETS += $(PARSEPROPERTIES)
+
+################################################################################
+#
+# Copy stubs of code reflection API in langtools
+
+GENSTUBS_DEST_ROOT := $(SUPPORT_OUTPUTDIR)/gensrc/$(MODULE)
+
+GENSTUBS_SRC := $(TOPDIR)/src/java.base/share/classes/java/lang/reflect/code
+
+GENSTUBS_DEST := $(GENSTUBS_DEST_ROOT)/jdk/internal/java/lang/reflect/code
+
+GENSTUBS_SCRIPT := $(TOPDIR)/cr-util/copy-to-compiler.sh
+
+GENSTUBS_CMD := $(BASH) $(GENSTUBS_SCRIPT) $(TOPDIR)/src/java.base/share/classes $(GENSTUBS_DEST_ROOT)
+
+$(eval $(call SetupExecute, GENSTUBS, \
+    WARN := Generating compiler stubs for code reflection, \
+    DEPS := $(call FindFiles, $(GENSTUBS_SRC), *.java), \
+    OUTPUT_DIR := $(GENSTUBS_DEST), \
+    COMMAND := $(GENSTUBS_CMD), \
+))
+
+TARGETS += $(GENSTUBS)
diff a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java
--- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -67,10 +67,17 @@
     final Class<?> implClass;                 // Class for referencing the implementation method "class CC"
     final MethodType dynamicMethodType;       // Dynamically checked method type "(Integer)Object"
     final boolean isSerializable;             // Should the returned instance be serializable
     final Class<?>[] altInterfaces;           // Additional interfaces to be implemented
     final MethodType[] altMethods;            // Signatures of additional methods to bridge
+    final MethodHandle quotableOpField;       // A getter method handle that is used to retrieve the
+                                              // string representation of the quotable lambda's associated
+                                              // intermediate representation (can be null).
+    final MethodHandleInfo quotableOpFieldInfo;  // Info about the quotable getter method handle (can be null).
+
+    final MethodType quotableOpType;          // The type of the quotable lambda's associated
+                                              // intermediate representation (can be null).
 
 
     /**
      * Meta-factory constructor.
      *
@@ -103,10 +110,13 @@
      *                       types must extend {@code Serializable}.
      * @param altInterfaces Additional interfaces which the lambda object
      *                      should implement.
      * @param altMethods Method types for additional signatures to be
      *                   implemented by invoking the implementation method
+     * @param reflectiveField a {@linkplain MethodHandles.Lookup#findGetter(Class, String, Class) getter}
+     *                   method handle that is used to retrieve the string representation of the
+     *                   quotable lambda's associated intermediate representation.
      * @throws LambdaConversionException If any of the meta-factory protocol
      *         invariants are violated
      * @throws SecurityException If a security manager is present, and it
      *         <a href="MethodHandles.Lookup.html#secmgr">denies access</a>
      *         from {@code caller} to the package of {@code implementation}.
@@ -117,11 +127,12 @@
                                         MethodType interfaceMethodType,
                                         MethodHandle implementation,
                                         MethodType dynamicMethodType,
                                         boolean isSerializable,
                                         Class<?>[] altInterfaces,
-                                        MethodType[] altMethods)
+                                        MethodType[] altMethods,
+                                        MethodHandle reflectiveField)
             throws LambdaConversionException {
         if (!caller.hasFullPrivilegeAccess()) {
             throw new LambdaConversionException(String.format(
                     "Invalid caller: %s",
                     caller.lookupClass().getName()));
@@ -178,10 +189,23 @@
 
         this.dynamicMethodType = dynamicMethodType;
         this.isSerializable = isSerializable;
         this.altInterfaces = altInterfaces;
         this.altMethods = altMethods;
+        this.quotableOpField = reflectiveField;
+        if (reflectiveField != null) {
+            // infer the method type associated with the intermediate representation of the
+            // quotable lambda. Since {@code factoryType} contains all the captured args
+            // we need to subtract the captured args that are required to invoke the lambda's
+            // bytecode. The type of {@code implementation} is useful here, as it corresponds to
+            // the signature of the emitted javac lambda implementation. From there, we need to
+            // drop all the dynamic arguments, which are obtained from {@code interfaceMethodType}.
+            this.quotableOpType = factoryType.dropParameterTypes(0,
+                    implementation.type().parameterCount() - interfaceMethodType.parameterCount());
+        } else {
+            quotableOpType = null;
+        }
 
         if (interfaceMethodName.isEmpty() ||
                 interfaceMethodName.indexOf('.') >= 0 ||
                 interfaceMethodName.indexOf(';') >= 0 ||
                 interfaceMethodName.indexOf('[') >= 0 ||
@@ -204,10 +228,24 @@
                 throw new LambdaConversionException(String.format(
                         "%s is not an interface",
                         c.getName()));
             }
         }
+
+        if (reflectiveField != null) {
+            try {
+                quotableOpFieldInfo = caller.revealDirect(reflectiveField); // may throw SecurityException
+            } catch (IllegalArgumentException e) {
+                throw new LambdaConversionException(implementation + " is not direct or cannot be cracked");
+            }
+            if (quotableOpFieldInfo.getReferenceKind() != REF_getField &&
+                    quotableOpFieldInfo.getReferenceKind() != REF_getStatic) {
+                throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", quotableOpFieldInfo));
+            }
+        } else {
+            quotableOpFieldInfo = null;
+        }
     }
 
     /**
      * Build the CallSite.
      *
@@ -223,11 +261,11 @@
      * @throws LambdaConversionException if there are improper conversions
      */
     void validateMetafactoryArgs() throws LambdaConversionException {
         // Check arity: captured + SAM == impl
         final int implArity = implMethodType.parameterCount();
-        final int capturedArity = factoryType.parameterCount();
+        final int capturedArity = factoryType.parameterCount() - reflectiveCaptureCount();
         final int samArity = interfaceMethodType.parameterCount();
         final int dynamicArity = dynamicMethodType.parameterCount();
         if (implArity != capturedArity + samArity) {
             throw new LambdaConversionException(
                     String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters",
@@ -314,10 +352,14 @@
         for (MethodType bridgeMT : altMethods) {
             checkDescriptor(bridgeMT);
         }
     }
 
+    int reflectiveCaptureCount() {
+        return quotableOpType == null ? 0 : quotableOpType.parameterCount();
+    }
+
     /** Validate that the given descriptor's types are compatible with {@code dynamicMethodType} **/
     private void checkDescriptor(MethodType descriptor) throws LambdaConversionException {
         for (int i = 0; i < dynamicMethodType.parameterCount(); i++) {
             Class<?> dynamicParamType = dynamicMethodType.parameterType(i);
             Class<?> descriptorParamType = descriptor.parameterType(i);
diff a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
--- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
@@ -32,13 +32,17 @@
 import sun.invoke.util.VerifyAccess;
 import sun.security.action.GetBooleanAction;
 
 import java.io.Serializable;
 import java.lang.constant.ConstantDescs;
+import java.lang.reflect.code.op.CoreOp.FuncOp;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Modifier;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.*;
 
 import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION;
 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
 import static java.lang.invoke.MethodType.methodType;
@@ -60,14 +64,16 @@
     private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
     private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException";
     private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;";
     private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V";
     private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V";
+    private static final String DESCR_METHOD_QUOTED = "()Ljava/lang/reflect/code/Quoted;";
 
     private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
     private static final String NAME_METHOD_READ_OBJECT = "readObject";
     private static final String NAME_METHOD_WRITE_OBJECT = "writeObject";
+    private static final String NAME_METHOD_QUOTED = "quoted";
 
     private static final String DESCR_CLASS = "Ljava/lang/Class;";
     private static final String DESCR_STRING = "Ljava/lang/String;";
     private static final String DESCR_OBJECT = "Ljava/lang/Object;";
     private static final String DESCR_CTOR_SERIALIZED_LAMBDA
@@ -85,10 +91,21 @@
     private static final boolean disableEagerInitialization;
 
     // condy to load implMethod from class data
     private static final ConstantDynamic implMethodCondy;
 
+    // condy to load reflective field from class data
+    private static final ConstantDynamic reflectiveFieldCondy;
+
+    private static final ConstantDynamic makeQuotedMethodCondy;
+
+    private static final MethodHandle HANDLE_MAKE_QUOTED;
+
+    private static final String quotedInstanceFieldName = "quoted";
+    private static final String quotedInstanceFieldDesc = Quoted.class.descriptorString();
+
+
     static {
         // To dump the lambda proxy classes, set this system property:
         //    -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
         // or -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true
         final String dumpProxyClassesKey = "jdk.invoke.LambdaMetafactory.dumpProxyClassFiles";
@@ -96,14 +113,24 @@
 
         final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization";
         disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey);
 
         // condy to load implMethod from class data
-        MethodType classDataMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class);
-        Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
-                                         classDataMType.descriptorString(), false);
-        implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm);
+        MethodType classDataAtMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class, int.class);
+        Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classDataAt",
+                                         classDataAtMType.descriptorString(), false);
+        implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm, 0);
+        reflectiveFieldCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm, 1);
+        makeQuotedMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm, 2);
+
+        try {
+            HANDLE_MAKE_QUOTED = MethodHandles.lookup().findStatic(
+                    InnerClassLambdaMetafactory.class, "makeQuoted",
+                    MethodType.methodType(Quoted.class, String.class, Object[].class));
+        } catch (Throwable ex) {
+            throw new AssertionError(ex);
+        }
     }
 
     // See context values in AbstractValidatingLambdaMetafactory
     private final String implMethodClassName;        // Name of type containing implementation "CC"
     private final String implMethodName;             // Name of implementation method "impl"
@@ -147,10 +174,13 @@
      *                       types must extend {@code Serializable}.
      * @param altInterfaces Additional interfaces which the lambda object
      *                      should implement.
      * @param altMethods Method types for additional signatures to be
      *                   implemented by invoking the implementation method
+     * @param reflectiveField a {@linkplain MethodHandles.Lookup#findGetter(Class, String, Class) getter}
+     *                   method handle that is used to retrieve the string representation of the
+     *                   quotable lambda's associated intermediate representation.
      * @throws LambdaConversionException If any of the meta-factory protocol
      *         invariants are violated
      * @throws SecurityException If a security manager is present, and it
      *         <a href="MethodHandles.Lookup.html#secmgr">denies access</a>
      *         from {@code caller} to the package of {@code implementation}.
@@ -161,15 +191,16 @@
                                        MethodType interfaceMethodType,
                                        MethodHandle implementation,
                                        MethodType dynamicMethodType,
                                        boolean isSerializable,
                                        Class<?>[] altInterfaces,
-                                       MethodType[] altMethods)
+                                       MethodType[] altMethods,
+                                       MethodHandle reflectiveField)
             throws LambdaConversionException {
         super(caller, factoryType, interfaceMethodName, interfaceMethodType,
               implementation, dynamicMethodType,
-              isSerializable, altInterfaces, altMethods);
+              isSerializable, altInterfaces, altMethods, reflectiveField);
         implMethodClassName = implClass.getName().replace('.', '/');
         implMethodName = implInfo.getName();
         implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
         constructorType = factoryType.changeReturnType(Void.TYPE);
         lambdaClassName = lambdaClassName(targetClass);
@@ -327,10 +358,18 @@
                                             argDescs[i],
                                             null, null);
             fv.visitEnd();
         }
 
+        // if quotable, generate the field that will hold the value of quoted
+        if (quotableOpField != null) {
+            cw.visitField(ACC_PRIVATE + ACC_FINAL,
+                          quotedInstanceFieldName,
+                          quotedInstanceFieldDesc,
+                          null, null);
+        }
+
         generateConstructor();
 
         if (factoryType.parameterCount() == 0 && disableEagerInitialization) {
             generateClassInitializer();
         }
@@ -352,18 +391,29 @@
         if (isSerializable)
             generateSerializationFriendlyMethods();
         else if (accidentallySerializable)
             generateSerializationHostileMethods();
 
+        if (quotableOpField != null) {
+            generateQuotableMethod();
+        }
+
         cw.visitEnd();
 
         // Define the generated class in this VM.
 
         final byte[] classBytes = cw.toByteArray();
         try {
             // this class is linked at the indy callsite; so define a hidden nestmate
-            var classdata = useImplMethodHandle? implementation : null;
+            List<?> classdata;
+            if (useImplMethodHandle || quotableOpField != null) {
+                classdata = quotableOpField == null ?
+                        List.of(implementation) :
+                        List.of(implementation, quotableOpField, HANDLE_MAKE_QUOTED);
+            } else {
+                classdata = null;
+            }
             return caller.makeHiddenClassDefiner(lambdaClassName, classBytes, Set.of(NESTMATE, STRONG), lambdaProxyClassFileDumper)
                          .defineClass(!disableEagerInitialization, classdata);
 
         } catch (Throwable t) {
             throw new InternalError(t);
@@ -413,16 +463,62 @@
             Class<?> argType = factoryType.parameterType(i);
             ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1);
             lvIndex += getParameterSize(argType);
             ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]);
         }
+
+        if (quotableOpField != null) {
+            generateQuotedFieldInitializer(ctor);
+        }
+
         ctor.visitInsn(RETURN);
         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
         ctor.visitMaxs(-1, -1);
         ctor.visitEnd();
     }
 
+    private void generateQuotedFieldInitializer(MethodVisitor ctor) {
+        ctor.visitCode();
+
+        // push the receiver on the stack for operand of put field instruction
+        ctor.visitVarInsn(ALOAD, 0);
+
+        ctor.visitLdcInsn(makeQuotedMethodCondy);
+
+        // load op string from field
+
+        ctor.visitLdcInsn(reflectiveFieldCondy);
+        MethodType mtype = quotableOpFieldInfo.getMethodType();
+        if (quotableOpFieldInfo.getReferenceKind() != MethodHandleInfo.REF_getStatic) {
+            mtype = mtype.insertParameterTypes(0, implClass);
+        }
+        ctor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle",
+                "invokeExact", mtype.descriptorString(), false);
+
+        // load captured args in array
+
+        ctor.visitLdcInsn(quotableOpType.parameterCount());
+        ctor.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT);
+        int capturedArity = factoryType.parameterCount() - reflectiveCaptureCount();
+        // initialize quoted captures
+        TypeConvertingMethodAdapter tcmv = new TypeConvertingMethodAdapter(ctor);
+        for (int i = 0; i < reflectiveCaptureCount(); i++) {
+            ctor.visitInsn(DUP);
+            ctor.visitIntInsn(BIPUSH, i); // is it possible that i can be greater than Byte.MAX_VALUE ?
+            ctor.visitVarInsn(ALOAD, 0);
+            ctor.visitFieldInsn(GETFIELD, lambdaClassName, argNames[capturedArity + i], argDescs[capturedArity + i]);
+            tcmv.boxIfTypePrimitive(Type.getType(argDescs[capturedArity + i]));
+            ctor.visitInsn(AASTORE);
+        }
+
+        // now create a Quoted from String and captured args Object[]
+
+        ctor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle",
+                "invokeExact", HANDLE_MAKE_QUOTED.type().toMethodDescriptorString(), false);
+        ctor.visitFieldInsn(PUTFIELD, lambdaClassName, quotedInstanceFieldName, quotedInstanceFieldDesc);
+    }
+
     /**
      * Generate a writeReplace method that supports serialization
      */
     private void generateSerializationFriendlyMethods() {
         TypeConvertingMethodAdapter mv
@@ -459,10 +555,27 @@
         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
         mv.visitMaxs(-1, -1);
         mv.visitEnd();
     }
 
+    /**
+     * Generate a writeReplace method that supports serialization
+     */
+    private void generateQuotableMethod() {
+        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL,
+                                          NAME_METHOD_QUOTED, DESCR_METHOD_QUOTED,
+                                          null, null);
+
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitFieldInsn(GETFIELD, lambdaClassName, quotedInstanceFieldName, quotedInstanceFieldDesc);
+        mv.visitInsn(ARETURN);
+
+        // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+    }
+
     /**
      * Generate a readObject/writeObject method that is hostile to serialization
      */
     private void generateSerializationHostileMethods() {
         MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
@@ -510,11 +623,11 @@
                 visitInsn(DUP);
             }
             if (useImplMethodHandle) {
                 visitLdcInsn(implMethodCondy);
             }
-            for (int i = 0; i < argNames.length; i++) {
+            for (int i = 0; i < argNames.length - reflectiveCaptureCount(); i++) {
                 visitVarInsn(ALOAD, 0);
                 visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]);
             }
 
             convertArgumentTypes(methodType);
@@ -545,11 +658,11 @@
         }
 
         private void convertArgumentTypes(MethodType samType) {
             int lvIndex = 0;
             int samParametersLength = samType.parameterCount();
-            int captureArity = factoryType.parameterCount();
+            int captureArity = factoryType.parameterCount() - reflectiveCaptureCount();
             for (int i = 0; i < samParametersLength; i++) {
                 Class<?> argType = samType.parameterType(i);
                 visitVarInsn(getLoadOpcode(argType), lvIndex + 1);
                 lvIndex += getParameterSize(argType);
                 convertType(argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i));
@@ -604,6 +717,10 @@
         } else {
             return 4;
         }
     }
 
+    private static Quoted makeQuoted(String opText, Object[] args) {
+        FuncOp op = (FuncOp)OpParser.fromStringOfFuncOp(opText);
+        return (Quoted)Interpreter.invoke(Lookup.IMPL_LOOKUP, op, args);
+    }
 }
diff a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java
--- a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -23,10 +23,12 @@
  * questions.
  */
 
 package java.lang.invoke;
 
+import java.lang.reflect.code.Quotable;
+
 import java.io.Serializable;
 import java.util.Arrays;
 import java.lang.reflect.Array;
 import java.util.Objects;
 
@@ -249,10 +251,14 @@
      * Flag for alternate metafactories indicating the lambda object requires
      * additional methods that invoke the {@code implementation}
      */
     public static final int FLAG_BRIDGES = 1 << 2;
 
+    /** Flag for {@link #altMetafactory} indicating the lambda object
+     * must be a {@code Quotable} object, inspectable using code reflection. */
+    public static final int FLAG_QUOTABLE = 1 << 3;
+
     private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
     private static final MethodType[] EMPTY_MT_ARRAY = new MethodType[0];
 
     // LambdaMetafactory bootstrap methods are startup sensitive, and may be
     // special cased in java.lang.invoke.BootstrapMethodInvoker to ensure
@@ -334,11 +340,12 @@
                                              Objects.requireNonNull(interfaceMethodType),
                                              Objects.requireNonNull(implementation),
                                              Objects.requireNonNull(dynamicMethodType),
                                              false,
                                              EMPTY_CLASS_ARRAY,
-                                             EMPTY_MT_ARRAY);
+                                             EMPTY_MT_ARRAY,
+                                 null);
         mf.validateMetafactoryArgs();
         return mf.buildCallSite();
     }
 
     /**
@@ -380,10 +387,12 @@
      *                          int flags,
      *                          int altInterfaceCount,        // IF flags has MARKERS set
      *                          Class... altInterfaces,       // IF flags has MARKERS set
      *                          int altMethodCount,           // IF flags has BRIDGES set
      *                          MethodType... altMethods      // IF flags has BRIDGES set
+     *                          MethodType reflectType        // IF flags has QUOTABLE set
+     *                          MethodHandle reflectField     // IF flags has QUOTABLE set
      *                          )
      * }</pre>
      *
      * <p>Arguments that appear in the argument list for
      * {@link #metafactory(MethodHandles.Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
@@ -403,10 +412,14 @@
      *     the function object should implement, and is present if and only if
      *     the {@code FLAG_BRIDGES} flag is set.</li>
      *     <li>{@code altMethods} is a variable-length list of additional
      *     methods signatures to implement, whose length equals {@code altMethodCount},
      *     and is present if and only if the {@code FLAG_BRIDGES} flag is set.</li>
+     *     <li>{@code quotableField} is a
+     *     {@linkplain MethodHandles.Lookup#findGetter(Class, String, Class) getter} method handle
+     *     that is used to retrieve the string representation of the quotable lambda's associated
+     *     intermediate representation.</li>
      * </ul>
      *
      * <p>Each class named by {@code altInterfaces} is subject to the same
      * restrictions as {@code Rd}, the return type of {@code factoryType},
      * as described {@link LambdaMetafactory above}.  Each {@code MethodType}
@@ -417,10 +430,13 @@
      * will implement {@code Serializable}, and will have a {@code writeReplace}
      * method that returns an appropriate {@link SerializedLambda}.  The
      * {@code caller} class must have an appropriate {@code $deserializeLambda$}
      * method, as described in {@link SerializedLambda}.
      *
+     * <p>When FLAG_QUOTABLE is set in {@code flags}, the function objects
+     * will implement {@code Quotable}.
+     *
      * <p>When the target of the {@code CallSite} returned from this method is
      * invoked, the resulting function objects are instances of a class with
      * the following properties:
      * <ul>
      *     <li>The class implements the interface named by the return type
@@ -485,10 +501,11 @@
         MethodHandle implementation = extractArg(args, argIndex++, MethodHandle.class);
         MethodType dynamicMethodType = extractArg(args, argIndex++, MethodType.class);
         int flags = extractArg(args, argIndex++, Integer.class);
         Class<?>[] altInterfaces = EMPTY_CLASS_ARRAY;
         MethodType[] altMethods = EMPTY_MT_ARRAY;
+        MethodHandle quotableField = null;
         if ((flags & FLAG_MARKERS) != 0) {
             int altInterfaceCount = extractArg(args, argIndex++, Integer.class);
             if (altInterfaceCount < 0) {
                 throw new IllegalArgumentException("negative argument count");
             }
@@ -505,10 +522,15 @@
             if (altMethodCount > 0) {
                 altMethods = extractArgs(args, argIndex, MethodType.class, altMethodCount);
                 argIndex += altMethodCount;
             }
         }
+        if ((flags & FLAG_QUOTABLE) != 0) {
+            quotableField = extractArg(args, argIndex++, MethodHandle.class);
+            altInterfaces = Arrays.copyOf(altInterfaces, altInterfaces.length + 1);
+            altInterfaces[altInterfaces.length-1] = Quotable.class;
+        }
         if (argIndex < args.length) {
             throw new IllegalArgumentException("too many arguments");
         }
 
         boolean isSerializable = ((flags & FLAG_SERIALIZABLE) != 0);
@@ -529,11 +551,12 @@
                                                   interfaceMethodType,
                                                   implementation,
                                                   dynamicMethodType,
                                                   isSerializable,
                                                   altInterfaces,
-                                                  altMethods);
+                                                  altMethods,
+                                                  quotableField);
         mf.validateMetafactoryArgs();
         return mf.buildCallSite();
     }
 
     private static <T> T extractArg(Object[] args, int index, Class<T> type) {
diff a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java
--- a/src/java.base/share/classes/java/lang/reflect/Method.java
+++ b/src/java.base/share/classes/java/lang/reflect/Method.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -43,13 +43,20 @@
 import sun.reflect.generics.scope.MethodScope;
 import sun.reflect.annotation.AnnotationType;
 import sun.reflect.annotation.AnnotationParser;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.AnnotationFormatError;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.parser.OpParser;
 import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Optional;
 import java.util.StringJoiner;
 
+import static java.lang.reflect.code.op.CoreOp.*;
+
 /**
  * A {@code Method} provides information about, and access to, a single method
  * on a class or interface.  The reflected method may be a class method
  * or an instance method (including an abstract method).
  *
@@ -93,10 +100,11 @@
     // potentially many Method objects pointing to it.)
     //
     // If this branching structure would ever contain cycles, deadlocks can
     // occur in annotation code.
     private Method              root;
+    private volatile Optional<FuncOp>     codeModel;
 
     // Generics infrastructure
     private String getGenericSignature() {return signature;}
 
     // Accessor for factory
@@ -243,10 +251,62 @@
     @Override
     public int getModifiers() {
         return modifiers;
     }
 
+    /**
+     * Returns the code model of the method body, if present.
+     * @return the code model of the method body.
+     * @since 99
+     */
+    // @@@ Make caller sensitive with the same access control as invoke
+    // and throwing IllegalAccessException
+//    @CallerSensitive
+    public Optional<FuncOp> getCodeModel() {
+        Optional<FuncOp> localRef = codeModel;
+        if (localRef == null) {
+            synchronized (this) {
+                localRef = codeModel;
+                if (localRef == null) {
+                    Optional<FuncOp> op = createCodeModel();
+                    codeModel = localRef = op;
+                }
+            }
+        }
+        return localRef;
+    }
+
+    private Optional<FuncOp> createCodeModel() {
+        Class<?> dc = getDeclaringClass();
+        String fieldName = getName() + "$" + "op";
+        Field f;
+        try {
+            f = dc.getDeclaredField(fieldName);
+        } catch (NoSuchFieldException e) {
+            return Optional.empty();
+        }
+
+        String modelText;
+        try {
+            // @@@ Use method handle with full power mode
+            f.setAccessible(true);
+            modelText = (String) f.get(null);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+
+        FuncOp op;
+        try {
+            List<Op> ops = OpParser.fromString(ExtendedOp.FACTORY, modelText);
+            op = (FuncOp) ops.get(0);
+        } catch (RuntimeException e) {
+            // @@@ Error or Exception?
+            throw e;
+        }
+        return Optional.of(op);
+    }
+
     /**
      * {@inheritDoc}
      * @throws GenericSignatureFormatError {@inheritDoc}
      * @since 1.5
      * @jls 8.4.4 Generic Methods
diff a/src/java.base/share/classes/java/lang/reflect/code/Block.java b/src/java.base/share/classes/java/lang/reflect/code/Block.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Block.java
@@ -0,0 +1,957 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.branch;
+
+/**
+ * A (basic) block containing an ordered sequence of operations, where the last operation is
+ * a {@link Op.Terminating terminating} operation.
+ * <p>
+ * The terminating operation, according to its specification, may branch to other blocks contained in the
+ * same parent body, by way of its {@link Op#successors() successors}, or exit the parent body and optionally
+ * yield a result.
+ * <p>
+ * Blocks may declare one or more block parameters.
+ */
+public final class Block implements CodeElement<Block, Op> {
+
+    /**
+     * A value that is a block parameter
+     */
+    public static final class Parameter extends Value {
+        Parameter(Block block, TypeElement type) {
+            super(block, type);
+        }
+
+        @Override
+        public Set<Value> dependsOn() {
+            return Set.of();
+        }
+
+        /**
+         * Returns the invokable operation associated with this block parameter.
+         * <p>
+         * If this block parameter is declared in an entry block and that
+         * block's ancestor operation (the parent of the entry block's parent body)
+         * is an instance of {@link Op.Invokable}, then that instance is returned,
+         * otherwise {@code null} is returned.
+         * <p>
+         * A non-{@code null} result implies this parameter is an invokable parameter.
+         *
+         * @apiNote
+         * This method may be used to pattern match on the returned result:
+         * {@snippet lang = "java":
+         *     if (p.invokableOperation() instanceof CoreOp.FuncOp f) {
+         *         assert f.parameters().indexOf(p) == p.index(); // @link substring="parameters()" target="Op.Invokable#parameters()"
+         *     }
+         *}
+         *
+         * @return the invokable operation, otherwise {@code null} if the operation
+         * is not an instance of {@link Op.Invokable}.
+         * @see Op.Invokable#parameters()
+         */
+        public Op.Invokable invokableOperation() {
+            if (declaringBlock().isEntryBlock() &&
+                    declaringBlock().parentBody().parentOp() instanceof Op.Invokable o) {
+                return o;
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * {@return the index of this block parameter in the parameters of its declaring block.}
+         * @see Value#declaringBlock()
+         * @see Block#parameters()
+         */
+        public int index() {
+            return declaringBlock().parameters().indexOf(this);
+        }
+    }
+
+    /**
+     * A block reference that refers to a block with arguments.
+     * <p>
+     * A terminating operation may refer, via a block reference, to one or more blocks as its successors.
+     * When control is passed from a block to a successor block the values of the block reference's arguments are
+     * assigned, in order, to the successor block's parameters.
+     */
+    public static final class Reference {
+        final Block target;
+        final List<Value> arguments;
+
+        /**
+         * Constructs a block reference for a given target block and arguments.
+         *
+         * @param target    the target block.
+         * @param arguments the target block arguments, a copy will be made as needed.
+         */
+        Reference(Block target, List<? extends Value> arguments) {
+            this.target = target;
+            this.arguments = List.copyOf(arguments);
+        }
+
+        /**
+         * {@return the target block.}
+         * @throws IllegalStateException if the target block is partially built
+         */
+        public Block targetBlock() {
+            if (!isBound()) {
+                throw new IllegalStateException("Target block is partially built");
+            }
+
+            return target;
+        }
+
+        /**
+         * {@return the block arguments.}
+         */
+        public List<Value> arguments() {
+            return arguments;
+        }
+
+        boolean isBound() {
+            return target.isBound();
+        }
+    }
+
+    final Body parentBody;
+
+    final List<Parameter> parameters;
+
+    final List<Op> ops;
+
+    // @@@ In topological order
+    // @@@ Create lazily
+    //     Can the representation be more efficient e.g. an array?
+    final Set<Block> predecessors;
+
+    // Reverse postorder index
+    // Set when block's body has sorted its blocks and therefore set when built
+    // Block is inoperable when < 0 i.e., when partially built
+    int index = -1;
+
+    Block(Body parentBody) {
+        this(parentBody, List.of());
+    }
+
+    Block(Body parentBody, List<TypeElement> parameterTypes) {
+        this.parentBody = parentBody;
+        this.parameters = new ArrayList<>();
+        for (TypeElement param : parameterTypes) {
+            parameters.add(new Parameter(this, param));
+        }
+        this.ops = new ArrayList<>();
+        this.predecessors = new LinkedHashSet<>();
+    }
+
+
+    /**
+     * Returns this block's parent body.
+     *
+     * @return this block's parent body.
+     */
+    @Override
+    public Body parent() {
+        return parentBody;
+    }
+
+    /**
+     * Returns this block's parent body.
+     *
+     * @return this block's parent body.
+     */
+    public Body parentBody() {
+        return parentBody;
+    }
+
+    @Override
+    public List<Op> children() {
+        return ops();
+    }
+
+    /**
+     * Returns the sequence of operations contained in this block.
+     *
+     * @return returns the sequence operations, as an unmodifiable list.
+     */
+    public List<Op> ops() {
+        return Collections.unmodifiableList(ops);
+    }
+
+    /**
+     * Returns this block's index within the parent body's blocks.
+     * <p>
+     * The following identity holds true:
+     * {@snippet lang = "java" :
+     *     this.parentBody().blocks().indexOf(this) == this.index();
+     * }
+     *
+     * @apiNote
+     * The block's index may be used to efficiently track blocks using
+     * bits sets or boolean arrays.
+     *
+     * @return the block index.
+     */
+    public int index() {
+        return index;
+    }
+
+    /**
+     * Returns the block parameters.
+     *
+     * @return the block parameters, as an unmodifiable list.
+     */
+    public List<Parameter> parameters() {
+        return Collections.unmodifiableList(parameters);
+    }
+
+    /**
+     * Returns the block parameter types.
+     *
+     * @return the block parameter types, as am unmodifiable list.
+     */
+    public List<TypeElement> parameterTypes() {
+        return parameters.stream().map(Value::type).toList();
+    }
+
+    /**
+     * Finds the operation in this block that is the ancestor of the given operation.
+     *
+     * @param op the given operation.
+     * @return the operation in this block that is the ancestor of the given operation,
+     * otherwise {@code null}
+     */
+    public Op findAncestorOpInBlock(Op op) {
+        Objects.requireNonNull(op);
+
+        while (op != null && op.parentBlock() != this) {
+            Body encBody = op.ancestorBody();
+            if (encBody == null) {
+                return null;
+            }
+
+            op = encBody.parentOp();
+        }
+
+        return op;
+    }
+
+    /**
+     * Returns the first operation in this block.
+     *
+     * @return the first operation in this block.
+     */
+    public Op firstOp() {
+        return ops.getFirst();
+    }
+
+    /**
+     * Returns the last, terminating, operation in this block.
+     * <p>
+     * The terminating operation implements {@link Op.Terminating}.
+     *
+     * @return the last, terminating, operation in this block.
+     */
+    public Op terminatingOp() {
+        Op lop = ops.getLast();
+        assert lop instanceof Op.Terminating;
+        return lop;
+    }
+
+    /**
+     * Returns the next operation after the given operation, otherwise {@code null}
+     * if this operation is the last operation.
+     *
+     * @param op the operation
+     * @return the next operation after the given operation.
+     */
+    public Op nextOp(Op op) {
+        int i = ops.indexOf(op);
+        if (i == -1) {
+            throw new IllegalArgumentException();
+        }
+        return i < ops().size() - 1 ? ops.get(i + 1) : null;
+    }
+
+    /**
+     * Returns the list of predecessors, namely each block in the parent body that refers
+     * to this block as a successor.
+     *
+     * @return the set of predecessors, as an unmodifiable list.
+     * @apiNote A block may refer to itself as a successor and therefore also be its predecessor.
+     */
+    public Set<Block> predecessors() {
+        return Collections.unmodifiableSet(predecessors);
+    }
+
+    /**
+     * Returns the list of successors referring to other blocks in the parent body.
+     * <p>
+     * The successors are declared by the terminating operation contained in this block.
+     *
+     * @return the list of successors, as an unmodifiable list.
+     */
+    public List<Reference> successors() {
+        Op lopr = ops.get(ops.size() - 1);
+        return lopr.successors();
+    }
+
+    /**
+     * Returns true if this block is an entry block.
+     *
+     * @return true if this block is an entry block.
+     */
+    public boolean isEntryBlock() {
+        return parentBody.entryBlock() == this;
+    }
+
+    /**
+     * Returns {@code true} if this block is
+     * <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">dominated by</a> the given block {@code dom}.
+     * This block is dominated by {@code dom}, if every path from the root entry block to this block passes through
+     * {@code dom}.
+     * <p>
+     * If this block, {@code b} say, and {@code dom} are not in the same parent body,
+     * then {@code b} becomes the nearest ancestor block, result of {@code b.parentBody().parentOp().parentBlock()},
+     * and so on until either:
+     * {@code b} is {@code null}, therefore {@code b} is <b>not</b> dominated by {@code dom} and this method
+     * returns {@code false}; or
+     * {@code b.parentBody() == dom.parentBody()}, therefore this method returns the result
+     * of {@code b.isDominatedBy(dom)}.
+     * <p>
+     * If this method returns {@code true} then {@code dom.isDominatedBy(this)}
+     * will return {@code false}. However, if this method returns {@code false} then it
+     * does not imply {@code dom.isDominatedBy(this)} returns {@code true}, as neither
+     * block may dominate the other.
+     *
+     * @param dom the dominating block
+     * @return {@code true} if this block is dominated by the given block.
+     */
+    // @@@ Should this be reversed and named dominates(Block b)
+    public boolean isDominatedBy(Block dom) {
+        Block b = findBlockForDomBody(this, dom.parentBody());
+        if (b == null) {
+            return false;
+        }
+
+        // A block non-strictly dominates itself
+        if (b == dom) {
+            return true;
+        }
+
+        // The entry block in b's body dominates all other blocks in the body
+        Block entry = b.parentBody().entryBlock();
+        if (dom == entry) {
+            return true;
+        }
+
+        // Traverse the immediate dominators until dom is reached or the entry block
+        Map<Block, Block> idoms = b.parentBody().immediateDominators();
+        Block idom = idoms.get(b);
+        while (idom != entry) {
+            if (idom == dom) {
+                return true;
+            }
+
+            idom = idoms.get(idom);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the immediate dominator of this block, otherwise {@code null} if this block is the entry block.
+     * Both this block and the immediate dominator (if defined) have the same parent body.
+     * <p>
+     * The immediate dominator is the unique block that strictly dominates this block, but does not strictly dominate
+     * any other block that strictly dominates this block.
+     *
+     * @return the immediate dominator of this block, otherwise {@code null} if this block is the entry block.
+     */
+    public Block immediateDominator() {
+        if (this == parentBody().entryBlock()) {
+            return null;
+        }
+
+        Map<Block, Block> idoms = parentBody().immediateDominators();
+        return idoms.get(this);
+    }
+
+    // @@@ isPostDominatedBy and immediatePostDominator
+
+    private static Block findBlockForDomBody(Block b, final Body domr) {
+        Body rb = b.parentBody();
+        while (domr != rb) {
+            // @@@ What if body is isolated
+
+            b = rb.parentOp().parentBlock();
+            // null when op is top-level (and its body is isolated), or not yet assigned to block
+            if (b == null) {
+                return null;
+            }
+            rb = b.parentBody();
+        }
+        return b;
+    }
+
+    /**
+     * A builder of a block.
+     * <p>
+     * When the parent body builder is built this block builder is also built. If a built builder
+     * is operated on to append a block parameter, append an operation, or add a block, then
+     * an {@code IllegalStateException} is thrown.
+     */
+    public final class Builder implements Function<Op, Op.Result> {
+        final Body.Builder parentBody;
+        final CopyContext cc;
+        final OpTransformer ot;
+
+        Builder(Body.Builder parentBody, CopyContext cc, OpTransformer ot) {
+            this.parentBody = parentBody;
+            this.cc = cc;
+            this.ot = ot;
+        }
+
+        void check() {
+            parentBody.check();
+        }
+
+        Block target() {
+            return Block.this;
+        }
+
+        /**
+         * {@return the block builder's operation transformer}
+         */
+        public OpTransformer transformer() {
+            return ot;
+        }
+
+        /**
+         * {@return the block builder's context}
+         */
+        public CopyContext context() {
+            return cc;
+        }
+
+        /**
+         * {@return the parent body builder}
+         */
+        public Body.Builder parentBody() {
+            return parentBody;
+        }
+
+        /**
+         * Returns the entry block builder for parent body.
+         *
+         * <p>The returned block is rebound if necessary to this block builder's
+         * context and transformer.
+         *
+         * @return the entry block builder for parent body builder
+         */
+        public Block.Builder entryBlock() {
+            return parentBody.entryBlock.rebind(cc, ot);
+        }
+
+        /**
+         * {@return true if this block builder is a builder of the entry block}
+         */
+        public boolean isEntryBlock() {
+            return Block.this == parentBody.target().entryBlock();
+        }
+
+        /**
+         * Rebinds this block builder with the given context and operation transformer.
+         *
+         * <p>Either this block builder and the returned block builder may be operated on to build
+         * the same block.
+         * Both are equal to each other, and both are closed when the parent body builder is closed.
+         *
+         * @param cc the context
+         * @param ot the operation transformer
+         * @return the rebound block builder
+         */
+        public Block.Builder rebind(CopyContext cc, OpTransformer ot) {
+            return this.cc == cc && this.ot == ot
+                    ? this
+                    : this.target().new Builder(parentBody(), cc, ot);
+        }
+
+        /**
+         * Adds a new block to the parent body.
+         *
+         * @param params the block's parameter types
+         * @return the new block builder
+         */
+        public Block.Builder block(TypeElement... params) {
+            return block(List.of(params));
+        }
+
+        /**
+         * Adds a new block to the parent body.
+         *
+         * @param params the block's parameter types
+         * @return the new block builder
+         */
+        public Block.Builder block(List<TypeElement> params) {
+            return parentBody.block(params, cc, ot);
+        }
+
+        /**
+         * Returns an unmodifiable list of the block's parameters.
+         *
+         * @return the unmodifiable list of the block's parameters
+         */
+        public List<Parameter> parameters() {
+            return Collections.unmodifiableList(parameters);
+        }
+
+        /**
+         * Appends a block parameter to the block's parameters.
+         *
+         * @param p the parameter type
+         * @return the appended block parameter
+         */
+        public Parameter parameter(TypeElement p) {
+            check();
+            return appendBlockParameter(p);
+        }
+
+        /**
+         * Creates a reference to this block that can be used as a successor of a terminating operation.
+         *
+         * @param args the block arguments
+         * @return the reference to this block
+         * @throws IllegalStateException if this block builder is associated with the entry block.
+         */
+        public Reference successor(Value... args) {
+            return successor(List.of(args));
+        }
+
+        /**
+         * Creates a reference to this block that can be used as a successor of a terminating operation.
+         *
+         * @param args the block arguments
+         * @return the reference to this block
+         * @throws IllegalStateException if this block builder is associated with the entry block.
+         */
+        public Reference successor(List<? extends Value> args) {
+            if (isEntryBlock()) {
+                throw new IllegalStateException("Entry block cannot be referred to as a successor");
+            }
+
+            return new Reference(Block.this, List.copyOf(args));
+        }
+
+        /**
+         * An inline consumer that inserts a return operation with a value, if non-null.
+         */
+        public static final BiConsumer<Block.Builder, Value> INLINE_RETURN = (block, value) -> {
+            block.op(value != null ? _return(value) : _return());
+        };
+
+        /**
+         * Inlines the invokable operation into this block and returns the block builder from which to
+         * continue building.
+         * <p>
+         * This method {@link #transformBody(Body, List, CopyContext, OpTransformer) transforms} the body of the
+         * invokable operation with the given arguments, a new context, and an operation transformer that
+         * replaces return operations by applying the given consumer to a block builder and a return value.
+         * <p>
+         * The operation transformer copies all operations except return operations whose nearest invokable operation
+         * ancestor is the given the invokable operation. When such a return operation is encountered, then on
+         * first encounter of its grandparent body a return block builder is computed and used for this return operation
+         * and encounters of subsequent return operations with the same grandparent body.
+         * <p>
+         * If the grandparent body has only one block then operation transformer's block builder is the return
+         * block builder. Otherwise, if the grandparent body has one or more blocks then the return block builder is
+         * created from the operation transformer's block builder. The created return block builder will have a block
+         * parameter whose type corresponds to the return type, or will have no parameter for void return.
+         * The computation finishes by applying the return block builder and a return value to the inlining consumer.
+         * If the grandparent body has only one block then the return value is the value mapped from the return
+         * operation's operand, or is null for void return. Otherwise, if the grandparent body has one or more blocks
+         * then the value is the block parameter of the created return block builder, or is null for void return.
+         * <p>
+         * For every encounter of a return operation the associated return block builder is compared against the
+         * operation transformer's block builder. If they are not equal then a branch operation is added to the
+         * operation transformer's block builder whose successor is the return block builder with a block argument
+         * that is the value mapped from the return operation's operand, or with no block argument for void return.
+         * @apiNote
+         * It is easier to inline an invokable op if its body is in lowered form (there are no operations in the blocks
+         * of the body that are lowerable). This ensures a single exit point can be created (paired with the single
+         * entry point). If there are one or more nested return operations, then there is unlikely to be a single exit.
+         * Transforming the model to create a single exit point while preserving nested structure is in general
+         * non-trivial and outside the scope of this method. In such cases the invokable operation can be transformed
+         * with a lowering transformation after which it can then be inlined.
+         *
+         * @param invokableOp the invokable operation
+         * @param args the arguments to map to the invokable operation's parameters
+         * @param inlineConsumer the consumer applied to process the return from the invokable operation.
+         *                       This is called once for each grandparent body of a return operation, with a block to
+         *                       build replacement operations and the return value, or null for void return.
+         * @return the block builder to continue building from
+         * @param <O> The invokable type
+         */
+        public <O extends Op & Op.Invokable> Block.Builder inline(O invokableOp, List<? extends Value> args,
+                                                                  BiConsumer<Block.Builder, Value> inlineConsumer) {
+            Map<Body, Block.Builder> returnBlocks = new HashMap<>();
+            // Create new context, ensuring inlining is isolated
+            transformBody(invokableOp.body(), args, CopyContext.create(), (block, op) -> {
+                // If the return operation is associated with the invokable operation
+                if (op instanceof CoreOp.ReturnOp rop && getNearestInvokeableAncestorOp(op) == invokableOp) {
+                    // Compute the return block
+                    Block.Builder returnBlock = returnBlocks.computeIfAbsent(rop.ancestorBody(), _body -> {
+                        Block.Builder rb;
+                        // If the body has one block we know there is just one return op declared, otherwise there may
+                        // one or more. If so, create a new block that joins all the returns.
+                        // Note: we could count all return op in a body to avoid creating a new block for a body
+                        // with two or more blocks with only one returnOp is declared.
+                        Value r;
+                        if (rop.ancestorBody().blocks().size() != 1) {
+                            List<TypeElement> param = rop.returnValue() != null
+                                    ? List.of(invokableOp.invokableType().returnType())
+                                    : List.of();
+                            rb = block.block(param);
+                            r = !param.isEmpty()
+                                    ? rb.parameters().get(0)
+                                    : null;
+                        } else {
+                            r = rop.returnValue() != null
+                                    ? block.context().getValue(rop.returnValue())
+                                    : null;
+                            rb = block;
+                        }
+
+                        // Inline the return
+                        inlineConsumer.accept(rb, r);
+
+                        return rb;
+                    });
+
+                    // Replace the return op with a branch to the return block, if needed
+                    if (!returnBlock.equals(block)) {
+                        // Replace return op with branch to return block, with given return value
+                        List<Value> arg = rop.returnValue() != null
+                                ? List.of(block.context().getValue(rop.returnValue()))
+                                : List.of();
+                        block.apply(branch(returnBlock.successor(arg)));
+                    }
+
+                    return block;
+                }
+
+                block.apply(op);
+                return block;
+            });
+
+
+            Builder builder = returnBlocks.get(invokableOp.body());
+            return builder != null ? builder : this;
+        }
+
+        private static Op getNearestInvokeableAncestorOp(Op op) {
+            do {
+                op = op.ancestorBody().parentOp();
+            } while (!(op instanceof Op.Invokable));
+            return op;
+        }
+
+        /**
+         * Transforms a body into this block, with this block builder's context.
+         *
+         * @param bodyToTransform the body to transform
+         * @param args        the list of values to map to the parameters of the body's entry block
+         * @param ot          the operation transformer
+         * @see #transformBody(Body, List, CopyContext, OpTransformer)
+         */
+        public void transformBody(Body bodyToTransform, List<? extends Value> args,
+                                  OpTransformer ot) {
+            transformBody(bodyToTransform, args, cc, ot);
+        }
+
+        /**
+         * Transforms a body into this block.
+         * <p>
+         * First, a new context is created from the given context and that new context is used to map values and
+         * blocks.
+         * <p>
+         * Second, the entry block is mapped to this block builder rebound with the given operation transformer and
+         * copy context, the input block parameters of the body's entry block are mapped to the given arguments.
+         * <p>
+         * Third, for each input block in the body (except the entry block) an output block builder is created with
+         * equivalent parameters as the input block and with the given operation transformer and copy context.
+         * The input block parameters are mapped to the output block parameters, and the input block is mapped to the
+         * output block builder.
+         * <p>
+         * Fourth, for each input block in the body (in order) the input block's operations are transformed
+         * by applying the output block builder and input block to the given operation transformer.
+         * <p>
+         * When the parent body is built any empty non-entry blocks that have no successors will be removed.
+         *
+         * @param bodyToTransform the body to transform
+         * @param args            the list of values to map to the parameters of the body's entry block
+         * @param cc              the copy context, for values captured in the body
+         * @param ot              the operation transformer
+         */
+        public void transformBody(Body bodyToTransform, List<? extends Value> args,
+                                  CopyContext cc, OpTransformer ot) {
+            check();
+
+            // @@@ This might be a new context e.g., when transforming a body
+            cc = CopyContext.create(cc);
+
+            Block entryBlockToTransform  = bodyToTransform.entryBlock();
+            List<Block> blocksToTransform = bodyToTransform.blocks();
+
+            // Map entry block
+            // Rebind this block builder to the created context and transformer
+            Block.Builder startingBlock = rebind(cc, ot);
+            cc.mapBlock(entryBlockToTransform, startingBlock);
+            cc.mapValues(entryBlockToTransform.parameters(), args);
+
+            // Map subsequent blocks up front, for forward referencing successors
+            for (int i = 1; i < blocksToTransform.size(); i++) {
+                Block blockToTransform = blocksToTransform.get(i);
+                if (cc.getBlock(blockToTransform) != null) {
+                    throw new IllegalStateException("Block is already transformed");
+                }
+
+                // Create block and map block
+                Block.Builder transformedBlock = startingBlock.block(List.of());
+                for (Block.Parameter ba : blockToTransform.parameters()) {
+                    transformedBlock.parameter(ba.type());
+                }
+                cc.mapBlock(blockToTransform, transformedBlock);
+                cc.mapValues(blockToTransform.parameters(), transformedBlock.parameters());
+            }
+
+            for (Block blockToTransform : blocksToTransform) {
+                ot.apply(cc.getBlock(blockToTransform), blockToTransform);
+            }
+        }
+
+        /**
+         * Appends operations into the block builder in the scope of the builder as an argument
+         * to the given consumer.
+         *
+         * @param c the consumer.
+         */
+        // @@@ Is this needed?
+        public void ops(Consumer<Builder> c) {
+            c.accept(this);
+        }
+
+        /**
+         * Appends an operation to this block, with no operation result name, and this builder's transformer.
+         *
+         * @param op the operation to append
+         * @return the operation result of the appended operation
+         * @throws IllegalStateException if the operation is structurally invalid
+         * @see #op(Op, OpTransformer)
+         */
+        @Override
+        public Op.Result apply(Op op) {
+            return op(op, ot);
+        }
+
+        /**
+         * Appends an operation to this block, with no operation result name, and this builder's transformer.
+         *
+         * @param op the operation to append
+         * @return the operation result of the appended operation
+         * @throws IllegalStateException if the operation is structurally invalid
+         * @see #op(Op, OpTransformer)
+         */
+        public Op.Result op(Op op) {
+            return op(op, ot);
+        }
+
+        /**
+         * Appends an operation to this block.
+         * <p>
+         * If the operation is not bound to a block, then the operation is appended and bound to this block.
+         * Otherwise, if the operation is bound, the operation is first
+         * {@link Op#transform(CopyContext, OpTransformer) transformed} with this builder's context and the given
+         * operation transformer, the unbound transformed operation is appended, and the operation's result is mapped
+         * to the transformed operation's result (using the builder's context).
+         * <p>
+         * If the unbound operation (transformed, or otherwise) is structurally invalid then an
+         * {@code IllegalStateException} is thrown. An unbound operation is structurally invalid if:
+         * <ul>
+         * <li>any of its bodies does not have the same ancestor body as this block's parent body.
+         * <li>any of its operands (values) is not reachable from this block.
+         * <li>any of its successors is not a sibling of this block.
+         * <li>any of its successors arguments (values) is not reachable from this block.
+         * </ul>
+         * A value is reachable from this block if there is a path from this block's parent body,
+         * via its ancestor bodies, to the value's block's parent body. (Note this structural check
+         * ensures values are only used from the same tree being built, but it is weaker than a
+         * dominance check that may be performed when the parent body is built.)
+         *
+         * @param op the operation to append
+         * @param transformer the transformer to use when appending a bound operation
+         * @return the operation result of the appended operation
+         * @throws IllegalStateException if the operation is structurally invalid
+         */
+        public Op.Result op(Op op, OpTransformer transformer) {
+            check();
+            final Op.Result oprToTransform = op.result();
+
+            Op transformedOp = op;
+            if (oprToTransform != null) {
+                // If operation is assigned to block, then copy it and transform its contents
+                transformedOp = op.transform(cc, transformer);
+                assert transformedOp.result == null;
+            }
+
+            Op.Result transformedOpr = insertOp(transformedOp);
+
+            if (oprToTransform != null) {
+                // Map the result of the first transformation
+                // @@@ If the same operation is transformed more than once then subsequent
+                //  transformed ops will not get implicitly mapped
+                //  Should this be an error?
+                cc.mapValueIfAbsent(oprToTransform, transformedOpr);
+            }
+
+            return transformedOpr;
+        }
+
+        /**
+         * Returns true if this block builder is equal to the other object.
+         * <p>This block builder is equal if the other object is an instance of a block builder, and they are
+         * associated with the same block (but perhaps bound to different contexts and transformers).
+         *
+         * @param o the other object
+         * @return true if this builder is equal to the other object.
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            return o instanceof Builder that && Block.this == that.target();
+        }
+
+        @Override
+        public int hashCode() {
+            return Block.this.hashCode();
+        }
+    }
+
+    // Modifying methods
+
+    // Create block parameter associated with this block
+    private Parameter appendBlockParameter(TypeElement type) {
+        Parameter blockParameter = new Parameter(this, type);
+        parameters.add(blockParameter);
+
+        return blockParameter;
+    }
+
+    // Create an operation, adding to the end of the list of existing operations
+    private Op.Result insertOp(Op op) {
+        Op.Result opResult = new Op.Result(this, op);
+        bindOp(opResult, op);
+
+        ops.add(op);
+        return opResult;
+    }
+
+    private void bindOp(Op.Result opr, Op op) {
+        // Structural checks
+        if (!ops.isEmpty() && ops.getLast() instanceof Op.Terminating) {
+            throw new IllegalStateException("Operation cannot be appended, the block has a terminal operation");
+        }
+
+        for (Body b : op.bodies()) {
+            if (b.ancestorBody != null && b.ancestorBody != this.parentBody) {
+                throw new IllegalStateException("Body of operation is bound to a different ancestor body: ");
+            }
+        }
+
+        for (Value v : op.operands()) {
+            if (!isReachable(v)) {
+                throw new IllegalStateException(
+                        String.format("Operand of operation %s is not defined in tree: %s", op, v));
+            }
+            assert !v.isBound();
+        }
+
+        for (Reference s : op.successors()) {
+            if (s.target.parentBody != this.parentBody) {
+                throw new IllegalStateException("Target of block reference is not a sibling of this block");
+            }
+
+            for (Value v : s.arguments()) {
+                if (!isReachable(v)) {
+                    throw new IllegalStateException(
+                            String.format("Argument of block reference %s of terminating operation %s is not defined in tree: %s", s, op, v));
+                }
+                assert !v.isBound();
+            }
+        }
+
+        // State updates after structural checks
+        // @@@ The alternative is to close the body builder on failure, rendering it inoperable,
+        // so checks and updates can be merged
+        for (Value v : op.operands()) {
+            v.uses.add(opr);
+        }
+
+        for (Reference s : op.successors()) {
+            for (Value v : s.arguments()) {
+                v.uses.add(opr);
+            }
+
+            s.target.predecessors.add(Block.this);
+        }
+
+        op.result = opr;
+    }
+
+    // Determine if the parent body of value's block is an ancestor of this block
+    private boolean isReachable(Value v) {
+        Body b = parentBody;
+        while (b != null && b != v.block.parentBody) {
+            b = b.ancestorBody;
+        }
+        return b != null;
+    }
+
+    //
+
+    boolean isBound() {
+        return index >= 0;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Body.java b/src/java.base/share/classes/java/lang/reflect/code/Body.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Body.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.lang.reflect.code.type.FunctionType;
+import java.util.*;
+
+/**
+ * A body containing a sequence of (basic) blocks.
+ * <p>
+ * The sequence of blocks form a graph. The last operation in a block, a terminating operation,
+ * may refer to other blocks in the sequence as successors, thus forming the graph. Otherwise, the last
+ * operation defines how the body passes control flow back to the parent operation, and in doing so may optionally
+ * yield a value.
+ * <p>
+ * The first block in the sequence is the entry block, and no other blocks refer to it as a successor.
+ * <p>
+ * A body has a function type whose return type is the body's yield type and whose parameter types are the entry
+ * block's parameters types, in order.
+ * The function type describes the sequence of input parameters types for arguments that are passed to the
+ * body when control flow is passed to it, and describes the return type of values that are returned when body passes
+ * control back to its parent operation.
+ */
+public final class Body implements CodeElement<Body, Block> {
+    // Parent operation
+    // Non-null when body is built, and therefore bound to operation
+    Op parentOp;
+
+    // The ancestor body, when null the body is isolated and cannot refer to values defined outside
+    // When non-null and body is built, ancestorBody == parentOp.result.block.parentBody
+    final Body ancestorBody;
+
+    final TypeElement yieldType;
+
+    // Sorted in reverse postorder
+    final List<Block> blocks;
+
+    // Map of a block to its immediate dominator
+    // Computed lazily, null if not computed
+    Map<Block, Block> idoms;
+
+    /**
+     * Constructs a body, whose ancestor is the given ancestor body.
+     */
+    Body(Body ancestorBody, TypeElement yieldType) {
+        this.ancestorBody = ancestorBody;
+        this.yieldType = yieldType;
+        this.blocks = new ArrayList<>();
+    }
+
+    /**
+     * Returns this body's parent operation.
+     *
+     * @return the body's parent operation.
+     */
+    @Override
+    public Op parent() {
+        return parentOp;
+    }
+
+    /**
+     * Returns this body's parent operation.
+     *
+     * @return the body's parent operation.
+     */
+    public Op parentOp() {
+        return parentOp;
+    }
+
+    @Override
+    public List<Block> children() {
+        return blocks();
+    }
+
+    /**
+     * Returns body's blocks in reverse-postorder as an unmodifiable list.
+     *
+     * @return the body's blocks in reverse-postorder.
+     */
+    public List<Block> blocks() {
+        return Collections.unmodifiableList(blocks);
+    }
+
+    /**
+     * {@return the yield type of this body}
+     */
+    public TypeElement yieldType() {
+        return yieldType;
+    }
+
+    /**
+     * Returns the body's function type.
+     * <p>The function type is composed of the body's entry block parameter types and
+     * the body's yield type.
+     *
+     * @return the body type.
+     */
+    public FunctionType bodyType() {
+        Block entryBlock = entryBlock();
+        return FunctionType.functionType(yieldType, entryBlock.parameterTypes());
+    }
+
+    /**
+     * Finds the block in this body that is the ancestor of the given block.
+     *
+     * @param b the given block.
+     * @return the block in this body that is the ancestor of the given block,
+     * otherwise {@code null}
+     */
+    public Block findAncestorBlockInBody(Block b) {
+        Objects.requireNonNull(b);
+
+        while (b != null && b.parentBody() != this) {
+            b = b.parentBody().parentOp().parentBlock();
+        }
+
+        return b;
+    }
+
+    /**
+     * Returns this body's entry block.
+     * <p>
+     * The entry block is the first block in the sequence. No other blocks refer to it as a successor.
+     *
+     * @return the body's entry block
+     */
+    public Block entryBlock() {
+        return blocks.getFirst();
+    }
+
+    /**
+     * Returns a map of block to its immediate dominator.
+     *
+     * @return a map of block to its immediate dominator
+     */
+    public Map<Block, Block> immediateDominators() {
+        /*
+         * Compute dominators of blocks in a body.
+         * <p>
+         * https://www.cs.rice.edu/~keith/EMBED/dom.pdf
+         * A Simple, Fast Dominance Algorithm
+         * Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
+         */
+
+        if (idoms != null) {
+            return idoms;
+        }
+
+        Map<Block, Block> doms = idoms = new HashMap<>();
+        doms.put(entryBlock(), entryBlock());
+
+        // Blocks are sorted in reverse postorder
+        boolean changed;
+        do {
+            changed = false;
+            // Iterate through blocks in reverse postorder, except for entry block
+            for (int i = 1; i < blocks.size(); i++) {
+                Block b = blocks.get(i);
+
+                // Find first processed predecessor of b
+                Block newIdom = null;
+                for (Block p : b.predecessors()) {
+                    if (doms.containsKey(p)) {
+                        newIdom = p;
+                        break;
+                    }
+                }
+                assert b.predecessors().isEmpty() || newIdom != null : b;
+
+                // For all other predecessors, p, of b
+                for (Block p : b.predecessors()) {
+                    if (p == newIdom) {
+                        continue;
+                    }
+
+                    if (doms.containsKey(p)) {
+                        // If already calculated
+                        newIdom = intersect(doms, p, newIdom);
+                    }
+                }
+
+                if (doms.get(b) != newIdom) {
+                    doms.put(b, newIdom);
+                    changed = true;
+                }
+            }
+        } while (changed);
+
+        return doms;
+    }
+
+    static Block intersect(Map<Block, Block> doms, Block b1, Block b2) {
+        while (b1 != b2) {
+            while (b1.index > b2.index) {
+                b1 = doms.get(b1);
+            }
+
+            while (b2.index > b1.index) {
+                b2 = doms.get(b2);
+            }
+        }
+
+        return b1;
+    }
+
+    /**
+     * Returns the dominance frontier of each block in the body.
+     * <p>
+     * The dominance frontier of block, {@code B} say, is the set of all blocks, {@code C} say,
+     * such that {@code B} dominates a predecessor of {@code C} but does not strictly dominate
+     * {@code C}.
+     *
+     * @return the dominance frontier of each block in the body
+     */
+    public Map<Block, Set<Block>> dominanceFrontier() {
+        // @@@ cache result?
+        Map<Block, Block> idoms = immediateDominators();
+        Map<Block, Set<Block>> df = new HashMap<>();
+
+        for (Block b : blocks) {
+            Set<Block> preds = b.predecessors();
+
+            if (preds.size() > 1) {
+                for (Block p : preds) {
+                    Block runner = p;
+                    while (runner != idoms.get(b)) {
+                        df.computeIfAbsent(runner, k -> new LinkedHashSet<>()).add(b);
+                        runner = idoms.get(runner);
+                    }
+                }
+            }
+        }
+
+        return df;
+    }
+
+    /**
+     * Returns {@code true} if this body is dominated by the given body {@code dom}.
+     * <p>
+     * A body, {@code b} say, is dominated by {@code dom} if {@code b} is the same as {@code dom} or a descendant of
+     * {@code dom}. Specifically, if {@code b} and {@code dom} are not equal then {@code b} becomes the nearest ancestor
+     * body, the result of {@code b.parentOp().parentBlock().parentBody()}, and so on until either:
+     * {@code b == dom}, therefore {@code b} is dominated by {@code dom} and this method returns {@code true};
+     * or {@code b.parentOp().parentBlock() == null}, therefore {@code b} is <b>not</b> dominated
+     * by {@code dom} and this method returns {@code false}.
+     *
+     * @param dom the dominating body
+     * @return {@code true} if this body is dominated by the given body {@code dom}.
+     */
+    public boolean isDominatedBy(Body dom) {
+        return isDominatedBy(this, dom);
+    }
+
+    static boolean isDominatedBy(Body r, Body dom) {
+        while (r != dom) {
+            Block eb = r.parentOp().parentBlock();
+            if (eb == null) {
+                return false;
+            }
+
+            r = eb.parentBody();
+        }
+
+        return true;
+    }
+
+    /**
+     * Computes values captured by this body. A captured value is a value that dominates
+     * this body and is used by a descendant operation of this body.
+     * <p>
+     * The order of the captured values is first use encountered in depth
+     * first search of this body's descendant operations.
+     *
+     * @return the list of captured values, modifiable
+     */
+    public List<Value> capturedValues() {
+        Set<Value> cvs = new LinkedHashSet<>();
+
+        capturedValues(cvs, new ArrayDeque<>(), this);
+        return new ArrayList<>(cvs);
+    }
+
+    static void capturedValues(Set<Value> capturedValues, Deque<Body> bodyStack, Body body) {
+        bodyStack.push(body);
+
+        for (Block b : body.blocks()) {
+            for (Op op : b.ops()) {
+                for (Body childBody : op.bodies()) {
+                    capturedValues(capturedValues, bodyStack, childBody);
+                }
+
+                for (Value a : op.operands()) {
+                    if (!bodyStack.contains(a.declaringBlock().parentBody())) {
+                        capturedValues.add(a);
+                    }
+                }
+
+                for (Block.Reference s : op.successors()) {
+                    for (Value a : s.arguments()) {
+                        if (!bodyStack.contains(a.declaringBlock().parentBody())) {
+                            capturedValues.add(a);
+                        }
+                    }
+                }
+            }
+        }
+
+        bodyStack.pop();
+    }
+
+    /**
+     * A builder of a body.
+     * <p>
+     * When the body builder is built any associated block builders are also considered built.
+     */
+    public final class Builder {
+        /**
+         * Creates a body build with a new context, and a copying transformer.
+         *
+         * @param ancestorBody the nearest ancestor body builder
+         * @param bodyType     the body's function type
+         * @return the body builder
+         * @throws IllegalStateException if the ancestor body builder is built
+         * @see #of(Builder, FunctionType, CopyContext, OpTransformer)
+         */
+        public static Builder of(Builder ancestorBody, FunctionType bodyType) {
+            // @@@ Creation of CopyContext
+            return of(ancestorBody, bodyType, CopyContext.create(), OpTransformer.COPYING_TRANSFORMER);
+        }
+
+        /**
+         * Creates a body build with a copying transformer.
+         *
+         * @param ancestorBody the nearest ancestor body builder
+         * @param bodyType     the body's function type
+         * @param cc           the context
+         * @return the body builder
+         * @throws IllegalStateException if the ancestor body builder is built
+         * @see #of(Builder, FunctionType, CopyContext, OpTransformer)
+         */
+        public static Builder of(Builder ancestorBody, FunctionType bodyType, CopyContext cc) {
+            return of(ancestorBody, bodyType, cc, OpTransformer.COPYING_TRANSFORMER);
+        }
+
+        /**
+         * Creates a body builder.
+         * <p>
+         * Structurally, the created body builder must be built before its ancestor body builder (if non-null) is built,
+         * otherwise an {@code IllegalStateException} will occur.
+         * <p>
+         * A function type, referred to as the body type, defines the body's yield type and the initial sequence of
+         * entry block parameters.
+         * The body's yield type is the function type's return type.
+         * An entry block builder is created with appended block parameters corresponding, in order, to
+         * the function type's parameter types.
+         * <p>
+         * If the ancestor body is null then the created body builder is isolated and descendant operations may only
+         * refer to values declared within the created body builder. Otherwise, operations
+         * may refer to values declared in the ancestor body builders (outside the created body builder).
+         *
+         * @param ancestorBody the nearest ancestor body builder, may be null if isolated
+         * @param bodyType     the body's function type
+         * @param cc           the context
+         * @param ot           the transformer
+         * @return the body builder
+         * @throws IllegalStateException if the ancestor body builder is built
+         * @see #of(Builder, FunctionType, CopyContext, OpTransformer)
+         */
+        public static Builder of(Builder ancestorBody, FunctionType bodyType,
+                                 CopyContext cc, OpTransformer ot) {
+            Body body = new Body(ancestorBody != null ? ancestorBody.target() : null, bodyType.returnType());
+            return body.new Builder(ancestorBody, bodyType, cc, ot);
+        }
+
+        // The ancestor body, may be null
+        final Builder ancestorBody;
+
+        // The entry block of this body, whose parameters are given by the body's function type
+        final Block.Builder entryBlock;
+
+        // When non-null contains one or more great-grandchildren
+        List<Builder> greatgrandchildren;
+
+        // True when built
+        boolean closed;
+
+        Builder(Builder ancestorBody, FunctionType bodyType,
+                CopyContext cc, OpTransformer ot) {
+            // Structural check
+            // The ancestor body should not be built before this body is created
+            if (ancestorBody != null) {
+                ancestorBody.check();
+                ancestorBody.addGreatgrandchild(this);
+            }
+
+            this.ancestorBody = ancestorBody;
+            // Create entry block from the body's function type
+            Block eb = Body.this.createBlock(bodyType.parameterTypes());
+            this.entryBlock = eb.new Builder(this, cc, ot);
+        }
+
+        void addGreatgrandchild(Builder greatgrandchild) {
+            var l = greatgrandchildren == null
+                    ? (greatgrandchildren = new ArrayList<>()) : greatgrandchildren;
+            l.add(greatgrandchild);
+        }
+
+        /**
+         * Builds the body and its blocks, associating the body with a parent operation.
+         * <p>
+         * Structurally, any descendant body builders must be built before this body builder is built,
+         * otherwise an {@code IllegalStateException} will occur.
+         * <p>
+         * Blocks are sorted in reserve postorder.
+         * <p>
+         * Any unreferenced empty blocks are removed. An unreferenced block is a non-entry block with no predecessors.
+         *
+         * @param op the parent operation
+         * @return the build body
+         * @throws IllegalStateException if this body builder is built
+         * @throws IllegalStateException if any descendant body builders are not built
+         * @throws IllegalStateException if a block has no terminal operation, unless unreferenced and empty
+         */
+        // @@@ Validation
+        // e.g., every operand dominates the operation result (potentially expensive)
+        public Body build(Op op) {
+            // Structural check
+            // This body should not be closed
+            check();
+            closed = true;
+
+            // Structural check
+            // All great-grandchildren bodies should be built
+            if (greatgrandchildren != null) {
+                for (Builder greatgrandchild : greatgrandchildren) {
+                    if (!greatgrandchild.closed) {
+                        throw new IllegalStateException("Descendant body builder is not built");
+                    }
+                }
+            }
+
+            Iterator<Block> i = blocks.iterator();
+            while (i.hasNext()) {
+                Block block = i.next();
+
+                // Structural check
+                // All referenced blocks should have a terminating operation as the last operation
+                if (block.ops.isEmpty()) {
+                    if (block.isEntryBlock() || !block.predecessors.isEmpty()) {
+                        throw noTerminatingOperation();
+                    }
+
+                    // Remove unreferenced empty block
+                    assert !block.isEntryBlock() && block.predecessors.isEmpty();
+                    i.remove();
+                } else if (!(block.ops.getLast() instanceof Op.Terminating)) {
+                    throw noTerminatingOperation();
+                }
+            }
+
+            sortReversePostorder();
+
+            Body.this.parentOp = op;
+            return Body.this;
+        }
+
+        static IllegalStateException noTerminatingOperation() {
+            return new IllegalStateException("Block has no terminating operation as the last operation");
+        }
+
+        /**
+         * Returns the body builder's function type.
+         * <p>
+         * The function type is composed of the body builder's yield type, as the function type's return type, and the
+         * currently built entry block's parameter types, in order, as the function type's parameter types.
+         *
+         * @return the body builder's function type
+         */
+        public FunctionType bodyType() {
+            TypeElement returnType = Body.this.yieldType();
+            Block eb = Body.this.entryBlock();
+            return FunctionType.functionType(returnType, eb.parameterTypes());
+        }
+
+        /**
+         * {@return the body builder's nearest ancestor body builder}
+         */
+        public Builder ancestorBody() {
+            return ancestorBody;
+        }
+
+        /**
+         * {@return the body's entry block builder}
+         */
+        public Block.Builder entryBlock() {
+            return entryBlock;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            return o instanceof Builder that && Body.this == that.target();
+        }
+
+        @Override
+        public int hashCode() {
+            return Body.this.hashCode();
+        }
+
+        void check() {
+            if (closed) {
+                throw new IllegalStateException("Builder is closed");
+            }
+        }
+
+        Body target() {
+            return Body.this;
+        }
+
+        // Build new block in body
+        Block.Builder block(List<TypeElement> params, CopyContext cc, OpTransformer ot) {
+            check();
+            Block block = Body.this.createBlock(params);
+
+            return block.new Builder(this, cc, ot);
+        }
+    }
+
+    /**
+     * Copies the contents of this body.
+     *
+     * @param cc the copy context
+     * @return the builder of a body containing the copied body
+     * @see #transform(CopyContext, OpTransformer)
+     */
+    public Builder copy(CopyContext cc) {
+        return transform(cc, OpTransformer.COPYING_TRANSFORMER);
+    }
+
+    /**
+     * Transforms this body.
+     * <p>
+     * A new body builder is created with the same function type as this body.
+     * Then, this body is {@link Block.Builder#transformBody(Body, java.util.List, CopyContext, OpTransformer) transformed}
+     * into the body builder's entry block builder with the given copy context, operation transformer, and values
+     * that are the entry block's parameters.
+     *
+     * @param cc the copy context
+     * @param ot the operation transformer
+     * @return a body builder containing the transformed body
+     */
+    public Builder transform(CopyContext cc, OpTransformer ot) {
+        Block.Builder ancestorBlockBuilder = ancestorBody != null
+                ? cc.getBlock(ancestorBody.entryBlock()) : null;
+        Builder ancestorBodyBuilder = ancestorBlockBuilder != null
+                ? ancestorBlockBuilder.parentBody() : null;
+        Builder body = Builder.of(ancestorBodyBuilder,
+                // Create function type with just the return type and add parameters afterward
+                FunctionType.functionType(yieldType),
+                cc, ot);
+
+        for (Block.Parameter p : entryBlock().parameters()) {
+            body.entryBlock.parameter(p.type());
+        }
+
+        body.entryBlock.transformBody(this, body.entryBlock.parameters(), cc, ot);
+        return body;
+    }
+
+    // Sort blocks in reverse post order
+    // After sorting the following holds for a block
+    //   block.parentBody().blocks().indexOf(block) == block.index()
+    private void sortReversePostorder() {
+        if (blocks.size() < 2) {
+            for (int i = 0; i < blocks.size(); i++) {
+                blocks.get(i).index = i;
+            }
+            return;
+        }
+
+        // Reset block indexes
+        // Also ensuring blocks with no predecessors occur last
+        for (Block b : blocks) {
+            b.index = Integer.MAX_VALUE;
+        }
+
+        Deque<Block> stack = new ArrayDeque<>();
+        stack.push(blocks.get(0));
+
+        // Postorder iteration
+        int index = blocks.size();
+        while (!stack.isEmpty()) {
+            Block n = stack.peek();
+            if (n.index == Integer.MIN_VALUE) {
+                // If n's successor has been processed then add n
+                stack.pop();
+                n.index = --index;
+            } else if (n.index < Integer.MAX_VALUE) {
+                // If n has already been processed then ignore
+                stack.pop();
+            } else {
+                // Mark before processing successors, a successor may refer back to n
+                n.index = Integer.MIN_VALUE;
+                for (Block.Reference s : n.successors()) {
+                    if (s.target.index < Integer.MAX_VALUE) {
+                        continue;
+                    }
+
+                    stack.push(s.target);
+                }
+            }
+        }
+
+        blocks.sort(Comparator.comparingInt(b -> b.index));
+        if (blocks.get(0).index > 0) {
+            // There are blocks with no predecessors
+            // Reassign indexes to their natural indexes, sort order is preserved
+            for (int i = 0; i < blocks.size(); i++) {
+                blocks.get(i).index = i;
+            }
+        }
+    }
+
+    // Modifying methods
+
+    // Create block
+    private Block createBlock(List<TypeElement> params) {
+        Block b = new Block(this, params);
+        blocks.add(b);
+        return b;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/CodeElement.java b/src/java.base/share/classes/java/lang/reflect/code/CodeElement.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/CodeElement.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * A code element, one of {@link Body body}, {@link Block block}, or {@link Op operation}.
+ * <p>
+ * A code element may have child code elements, and so on, to form a tree. A (root) code element and all its descendants
+ * can be traversed.
+ *
+ * @param <E> the code element type
+ * @param <C> the child code element type.
+ * @sealedGraph
+ */
+// @@@ E may not be needed
+public sealed interface CodeElement<
+        E extends CodeElement<E, C>,
+        C extends CodeElement<C, ?>>
+        extends CodeItem
+        permits Body, Block, Op {
+
+    /**
+     * {@return a stream of code elements sorted topologically in pre-order traversal.}
+     */
+    // Code copied into the compiler cannot depend on new gatherer API
+    default Stream<CodeElement<?, ?>> elements() {
+/*__throw new UnsupportedOperationException();__*/        return Stream.of(Void.class).gather(() -> (_, _, downstream) -> traversePreOrder(downstream::push));
+    }
+
+//    private boolean traversePreOrder(Gatherer.Downstream<? super CodeElement<?, ?>> v) {
+    private boolean traversePreOrder(Predicate<? super CodeElement<?, ?>> v) {
+        if (!v.test(this)) {
+            return false;
+        }
+        for (C c : children()) {
+            if (!((CodeElement<?, ?>) c).traversePreOrder(v)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Traverses this code element and any descendant code elements.
+     * <p>
+     * Traversal is performed in pre-order, reporting each code element to the visitor.
+     *
+     * @param t   the traversing accumulator
+     * @param v   the code element visitor
+     * @param <T> accumulator type
+     * @return the traversing accumulator
+     */
+    default <T> T traverse(T t, BiFunction<T, CodeElement<?, ?>, T> v) {
+        t = v.apply(t, this);
+        for (C r : children()) {
+            t = r.traverse(t, v);
+        }
+
+        return t;
+    }
+
+    /**
+     * Creates a visiting function for bodies.
+     *
+     * @param v   the body visitor
+     * @param <T> accumulator type
+     * @return the visiting function for bodies
+     */
+    static <T> BiFunction<T, CodeElement<?, ?>, T> bodyVisitor(BiFunction<T, Body, T> v) {
+        return (t, e) -> e instanceof Body f
+                ? v.apply(t, f)
+                : t;
+    }
+
+    /**
+     * Creates a visiting function for blocks.
+     *
+     * @param v   the block visitor
+     * @param <T> accumulator type
+     * @return the visiting function for blocks
+     */
+    static <T> BiFunction<T, CodeElement<?, ?>, T> blockVisitor(BiFunction<T, Block, T> v) {
+        return (t, e) -> e instanceof Block f
+                ? v.apply(t, f)
+                : t;
+    }
+
+    /**
+     * Creates a visiting function for operations.
+     *
+     * @param v   the operation visitor
+     * @param <T> accumulator type
+     * @return the visiting function for operations
+     */
+    static <T> BiFunction<T, CodeElement<?, ?>, T> opVisitor(BiFunction<T, Op, T> v) {
+        return (t, e) -> e instanceof Op f
+                ? v.apply(t, f)
+                : t;
+    }
+
+    /**
+     * Returns the parent code element.
+     * <p>
+     * If this element is an instance of {@code Op} then the parent may be {@code null}
+     * if operation is not assigned to a block.
+     *
+     * @return the parent code element
+     */
+    CodeElement<?, E> parent();
+
+    /**
+     * Returns the child code elements, as an unmodifiable list.
+     *
+     * @return the child code elements
+     */
+    List<C> children();
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/CodeItem.java b/src/java.base/share/classes/java/lang/reflect/code/CodeItem.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/CodeItem.java
@@ -0,0 +1,10 @@
+package java.lang.reflect.code;
+
+/**
+ * A code item, one of {@link CodeElement}, {@link Value}, or {@link TypeElement}.
+ * @sealedGraph
+ */
+public sealed interface CodeItem
+        permits CodeElement, Value, TypeElement {
+    // @@@ Common functionality between elements and values?
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/CopyContext.java b/src/java.base/share/classes/java/lang/reflect/code/CopyContext.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/CopyContext.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.List;
+import java.util.function.Function;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * A context utilized when transforming code models.
+ * <p>
+ * The context holds a mapping of input values to output values, input blocks to output block builders,
+ * and input block references to output block references.
+ * These mappings are built as an input model is transformed to produce an output model. Mappings are built implicitly
+ * when an operation is transformed by copying, and can be explicitly added by the transformation when removing or
+ * adding new operations.
+ * <p>
+ * Unless otherwise specified the passing of a {@code null} argument to the methods of this interface results in a
+ * {@code NullPointerException}.
+ */
+public sealed interface CopyContext permits CopyContextImpl {
+
+    // @@@ ?
+    // CopyContext parent();
+
+
+    // Value mappings
+
+    /**
+     * {@return the output value mapped to the input value}
+     * <p>
+     * If this context is not isolated and there is no value mapping in this context then this method will return
+     * the result of calling {@code getValue} on the parent context, if present. Otherwise if this context is isolated
+     * or there is no parent context, then there is no mapping.
+     *
+     * @param input the input value
+     * @throws IllegalArgumentException if there is no mapping
+     */
+    Value getValue(Value input);
+
+    /**
+     * {@return the output value mapped to the input value or a default value if no mapping}
+     *
+     * @param input the input value
+     * @param defaultValue the default value to return if no mapping
+     */
+    Value getValueOrDefault(Value input, Value defaultValue);
+
+    /**
+     * Maps an input value to an output value.
+     * <p>
+     * Uses of the input value will be mapped to the output value when transforming.
+     *
+     * @param input the input value
+     * @param output the output value
+     * @throws IllegalArgumentException if the output value is already bound
+     */
+    void mapValue(Value input, Value output);
+
+    /**
+     * Maps an input value to an output value, if no such mapping exists.
+     * <p>
+     * Uses of the input value will be mapped to the output value when transforming.
+     *
+     * @param input the input value
+     * @param output the output value
+     * @return the previous mapped value, or null of there was no mapping.
+     * @throws IllegalArgumentException if the output value is already bound
+     */
+    // @@@ Is this needed?
+    Value mapValueIfAbsent(Value input, Value output);
+
+    /**
+     * Returns a list of mapped output values by obtaining, in order, the output value for each element in the list
+     * of input values.
+     *
+     * @param inputs the list of input values
+     * @return a modifiable list of output values
+     * @throws IllegalArgumentException if an input value has no mapping
+     */
+    // @@@ If getValue is modified to return null then this method should fail on null
+    default List<Value> getValues(List<? extends Value> inputs) {
+        return inputs.stream().map(this::getValue).collect(toList());
+    }
+
+    /**
+     * Maps the list of input values, in order, to the corresponding list of output values, up to the number of
+     * elements that is the minimum of the size of both lists.
+     * <p>
+     * Uses of an input value will be mapped to the corresponding output value when transforming.
+     *
+     * @param inputs the input values
+     * @param outputs the output values.
+     * @throws IllegalArgumentException if an output value is already bound
+     */
+    default void mapValues(List<? extends Value> inputs, List<? extends Value> outputs) {
+        // @@@ sizes should be the same?
+        for (int i = 0; i < Math.min(inputs.size(), outputs.size()); i++) {
+            mapValue(inputs.get(i), outputs.get(i));
+        }
+    }
+
+
+    // Block mappings
+
+    /**
+     * {@return the output block builder mapped to the input block, otherwise null if no mapping}
+     *
+     * @param input the input block
+     */
+    Block.Builder getBlock(Block input);
+
+    /**
+     * Maps an input block to an output block builder.
+     * <p>
+     * Uses of the input block will be mapped to the output block builder when transforming.
+     *
+     * @param input the input block
+     * @param output the output block builder
+     * @throws IllegalArgumentException if the output block is already bound
+     */
+    void mapBlock(Block input, Block.Builder output);
+
+
+    // Successor mappings
+
+    /**
+     * {@return the output block reference mapped to the input block reference,
+     * otherwise null if no mapping}
+     *
+     * @param input the input reference
+     */
+    Block.Reference getSuccessor(Block.Reference input);
+
+    /**
+     * Maps an input block reference to an output block reference.
+     * <p>
+     * Uses of the input block reference will be mapped to the output block reference when transforming.
+     *
+     * @param input the input block reference
+     * @param output the output block reference
+     * @throws IllegalArgumentException if the output block builder associated with the block reference or any of its
+     * argument values are already bound
+     */
+    void mapSuccessor(Block.Reference input, Block.Reference output);
+
+    /**
+     * Returns a mapped output block reference, if present, otherwise creates a new, unmapped, reference from the input
+     * block reference.
+     * <p>
+     * A new, unmapped reference, is created by obtaining the mapped output block builder from the input reference's
+     * target block, and creating a successor from the output block builder with arguments that is the result of
+     * obtaining the mapped values from the input reference's arguments.
+     *
+     * @param input the input block reference
+     * @return the output block reference, if present, otherwise a created block reference
+     * @throws IllegalArgumentException if a new reference is to be created and there is no mapped output block builder
+     */
+    default Block.Reference getSuccessorOrCreate(Block.Reference input) {
+        Block.Reference successor = getSuccessor(input);
+        if (successor != null) {
+            return successor;
+        }
+
+        // Create successor
+        Block.Builder outputBlock = getBlock(input.targetBlock());
+        if (outputBlock == null) {
+            throw new IllegalArgumentException("No mapping for input reference target block" + input.targetBlock());
+        }
+        return outputBlock.successor(getValues(input.arguments()));
+    }
+
+
+    // Properties mappings
+
+    /**
+     * {@return an object associated with a property key}
+     *
+     * @param key the property key
+     */
+    Object getProperty(Object key);
+
+    /**
+     * Associates an object with a property key.
+     *
+     * @param key the property key
+     * @param value the associated object
+     * @return the current associated object, or null if not associated
+     */
+    Object putProperty(Object key, Object value);
+
+    /**
+     * If the property key is not already associated with an object, attempts to compute the object using the
+     * mapping function and associates it unless {@code null}.
+     *
+     * @param key the property key
+     * @param mappingFunction the mapping function
+     * @return the current (existing or computed) object associated with the property key,
+     * or null if the computed object is null
+     */
+    Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction);
+
+
+    // Factories
+
+    /**
+     * {@return a new isolated context initialized with no mappings and no parent }
+     */
+    static CopyContext create() {
+        return new CopyContextImpl(null);
+    }
+
+    /**
+     * {@return a new non-isolated context initialized with no mappings and a parent }
+     * The returned context will query value and property mappings in the parent context
+     * if a query of its value and property mappings yields no result.
+     */
+    static CopyContext create(CopyContext parent) {
+        return new CopyContextImpl((CopyContextImpl) parent);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/CopyContextImpl.java b/src/java.base/share/classes/java/lang/reflect/code/CopyContextImpl.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/CopyContextImpl.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+final class CopyContextImpl implements CopyContext {
+
+    private static final Map<?, ?> EMPTY_MAP = Map.of();
+
+    final CopyContextImpl parent;
+
+    Map<Value, Value> valueMap;
+    Map<Block, Block.Builder> blockMap;
+    Map<Block.Reference, Block.Reference> successorMap;
+    Map<Object, Object> propertiesMap;
+
+    CopyContextImpl(CopyContextImpl that) {
+        this.parent = that;
+        this.blockMap = emptyMap();
+        this.valueMap = emptyMap();
+        this.successorMap = emptyMap();
+        this.propertiesMap = emptyMap();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <K, V> Map<K, V> emptyMap() {
+        return (Map<K, V>) EMPTY_MAP;
+    }
+
+
+    // Values
+
+    @Override
+    public Value getValue(Value input) {
+        Value output = getValueOrNull(input);
+        if (output != null) {
+            return output;
+        }
+        throw new IllegalArgumentException("No mapping for input value: " + input);
+    }
+
+    @Override
+    public Value getValueOrDefault(Value input, Value defaultValue) {
+        Value output = getValueOrNull(input);
+        if (output != null) {
+            return output;
+        }
+        return defaultValue;
+    }
+
+    private Value getValueOrNull(Value input) {
+        Objects.requireNonNull(input);
+
+        CopyContextImpl p = this;
+        do {
+            Value output = p.valueMap.get(input);
+            if (output != null) {
+                return output;
+            }
+            p = p.parent;
+        } while (p != null);
+
+        return null;
+    }
+
+    @Override
+    public void mapValue(Value input, Value output) {
+        Objects.requireNonNull(input);
+        Objects.requireNonNull(output);
+
+        if (output.isBound()) {
+            throw new IllegalArgumentException("Output value bound: " + output);
+        }
+
+        if (valueMap == EMPTY_MAP) {
+            valueMap = new HashMap<>();
+        }
+        valueMap.put(input, output);
+    }
+
+    @Override
+    public Value mapValueIfAbsent(Value input, Value output) {
+        Objects.requireNonNull(input);
+        Objects.requireNonNull(output);
+
+        if (output.isBound()) {
+            throw new IllegalArgumentException("Output value is bound: " + output);
+        }
+
+        if (valueMap == EMPTY_MAP) {
+            valueMap = new HashMap<>();
+        }
+        return valueMap.putIfAbsent(input, output);
+    }
+
+
+    // Blocks
+
+    @Override
+    public Block.Builder getBlock(Block input) {
+        Objects.requireNonNull(input);
+
+        return blockMap.get(input);
+    }
+
+    @Override
+    public void mapBlock(Block input, Block.Builder output) {
+        Objects.requireNonNull(input);
+        Objects.requireNonNull(output);
+
+        if (output.target().isBound()) {
+            throw new IllegalArgumentException("Output block builder is built: " + output);
+        }
+
+        if (blockMap == EMPTY_MAP) {
+            blockMap = new HashMap<>();
+        }
+        blockMap.put(input, output);
+    }
+
+
+    // Successors
+
+    @Override
+    public Block.Reference getSuccessor(Block.Reference input) {
+        Objects.requireNonNull(input);
+
+        return successorMap.get(input);
+    }
+
+    @Override
+    public void mapSuccessor(Block.Reference input, Block.Reference output) {
+        Objects.requireNonNull(input);
+        Objects.requireNonNull(output);
+
+        if (output.target.isBound()) {
+            throw new IllegalArgumentException("Output block reference target is built: " + output);
+        }
+
+        for (Value outputArgument : output.arguments()) {
+            if (outputArgument.isBound()) {
+                throw new IllegalArgumentException("Output block reference argument is bound: " + outputArgument);
+            }
+        }
+
+        if (successorMap == EMPTY_MAP) {
+            successorMap = new HashMap<>();
+        }
+        successorMap.put(input, output);
+    }
+
+
+    // Properties
+
+    @Override
+    public Object getProperty(Object key) {
+        CopyContextImpl p = this;
+        do {
+            Object value = p.propertiesMap.get(key);
+            if (value != null) {
+                return value;
+            }
+            p = p.parent;
+        } while (p != null);
+
+        return null;
+    }
+
+    @Override
+    public Object putProperty(Object key, Object value) {
+        if (propertiesMap == EMPTY_MAP) {
+            propertiesMap = new HashMap<>();
+        }
+        return propertiesMap.put(key, value);
+    }
+
+    @Override
+    public Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction) {
+        if (propertiesMap == EMPTY_MAP) {
+            propertiesMap = new HashMap<>();
+        }
+        Object value = getProperty(key);
+        if (value != null) {
+            return value;
+        }
+        propertiesMap.put(key, value = mappingFunction.apply(key));
+        return value;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Location.java b/src/java.base/share/classes/java/lang/reflect/code/Location.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Location.java
@@ -0,0 +1,47 @@
+package java.lang.reflect.code;
+
+/**
+ * Source location information.
+ *
+ * @param sourceRef the reference to the source
+ * @param line the line in the source
+ * @param column the column in the source
+ */
+public record Location(String sourceRef, int line, int column) {
+
+    /**
+     * The location value, {@code null}, indicating no location information.
+     */
+    public static final Location NO_LOCATION = null;
+
+    public Location(int line, int column) {
+        this(null, line, column);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append(line).append(":").append(column);
+        if (sourceRef != null) {
+            s.append(":").append(sourceRef);
+        }
+        return s.toString();
+    }
+
+    public static Location fromString(String s) {
+        String[] split = s.split(":", 3);
+        if (split.length < 2) {
+            throw new IllegalArgumentException();
+        }
+
+        int line = Integer.parseInt(split[0]);
+        int column = Integer.parseInt(split[1]);
+        String sourceRef;
+        if (split.length == 3) {
+            sourceRef = split[2];
+        } else {
+            sourceRef = null;
+        }
+        return new Location(sourceRef, line, column);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Op.java b/src/java.base/share/classes/java/lang/reflect/code/Op.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Op.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.writer.OpWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.function.BiFunction;
+
+/**
+ * An operation modelling a unit of functionality.
+ * <p>
+ * An operation might model the addition of two 32-bit integers, or a Java method call.
+ * Alternatively an operation may model something more complex like method bodies, lambda bodies, or
+ * try/catch/finally statements. In this case such an operation will contain one or more bodies modelling
+ * the nested structure.
+ */
+public non-sealed abstract class Op implements CodeElement<Op, Body> {
+
+    /**
+     * An operation characteristic indicating the operation is pure and has no side effects.
+     */
+    public interface Pure {
+    }
+
+    /**
+     * An operation characteristic indicating the operation has one or more bodies.
+     */
+    public interface Nested {
+        List<Body> bodies();
+    }
+
+    /**
+     * An operation characteristic indicating the operation represents a loop
+     */
+    public interface Loop extends Nested {
+        Body loopBody();
+    }
+
+    /**
+     * An operation characteristic indicating the operation has one or more bodies,
+     * all of which are isolated.
+     */
+    public interface Isolated extends Nested {
+    }
+
+    /**
+     * An operation characteristic indicating the operation is invokable, so the operation may be interpreted
+     * or compiled.
+     */
+    public interface Invokable extends Nested {
+        /**
+         * {@return the body of the invokable operation.}
+         */
+        Body body();
+
+        /**
+         * {@return the function type describing the invokable operation's parameter types and return type.}
+         */
+        FunctionType invokableType();
+
+        /**
+         * {@return the entry block parameters of this operation's body}
+         */
+        default List<Block.Parameter> parameters() {
+            return body().entryBlock().parameters();
+        }
+
+        /**
+         * Computes values captured by this invokable operation's body.
+         *
+         * @return the captured values.
+         * @see Body#capturedValues()
+         */
+        default List<Value> capturedValues() {
+            return List.of();
+        }
+    }
+
+    /**
+     * An operation characteristic indicating the operation can replace itself with a lowered form,
+     * consisting only of operations in the core dialect.
+     */
+    public interface Lowerable {
+        default Block.Builder lower(Block.Builder b) {
+            return lower(b, OpTransformer.NOOP_TRANSFORMER);
+        }
+
+        Block.Builder lower(Block.Builder b, OpTransformer opT);
+    }
+
+    /**
+     * An operation characteristic indicating the operation is a terminating operation
+     * occurring as the last operation in a block.
+     * <p>
+     * A terminating operation passes control to either another block within the same parent body
+     * or to that parent body.
+     */
+    public interface Terminating {
+    }
+
+    /**
+     * An operation characteristic indicating the operation is a body terminating operation
+     * occurring as the last operation in a block.
+     * <p>
+     * A body terminating operation passes control back to its nearest ancestor body.
+     */
+    public interface BodyTerminating extends Terminating {
+    }
+
+    /**
+     * An operation characteristic indicating the operation is a block terminating operation
+     * occurring as the last operation in a block.
+     * <p>
+     * The operation has one or more successors to other blocks within the same parent body, and passes
+     * control to one of those blocks.
+     */
+    public interface BlockTerminating extends Terminating {
+        List<Block.Reference> successors();
+    }
+
+    /**
+     * A value that is the result of an operation.
+     */
+    public static final class Result extends Value {
+        final Op op;
+
+        Result(Block block, Op op) {
+            super(block, op.resultType());
+
+            this.op = op;
+        }
+
+        @Override
+        public Set<Value> dependsOn() {
+            Set<Value> depends = new LinkedHashSet<>(op.operands());
+            if (op instanceof Terminating) {
+                op.successors().stream().flatMap(h -> h.arguments().stream()).forEach(depends::add);
+            }
+
+            return Collections.unmodifiableSet(depends);
+        }
+
+        /**
+         * Returns the result's operation.
+         *
+         * @return the result's operation.
+         */
+        public Op op() {
+            return op;
+        }
+    }
+
+    // Set when op is bound to block, otherwise null when unbound
+    Result result;
+
+    // null if not specified
+    Location location;
+
+    final String name;
+
+    final List<Value> operands;
+
+    /**
+     * Constructs an operation by copying given operation.
+     *
+     * @param that the operation to copy.
+     * @param cc   the copy context.
+     * @implSpec The default implementation calls the constructor with the operation's name, result type, and a list
+     * values computed, in order, by mapping the operation's operands using the copy context.
+     */
+    protected Op(Op that, CopyContext cc) {
+        this(that.name, cc.getValues(that.operands));
+        this.location = that.location;
+    }
+
+    /**
+     * Copies this operation and its bodies, if any.
+     * <p>
+     * The returned operation is structurally identical to this operation and is otherwise independent
+     * of the values declared and used.
+     *
+     * @return the copied operation.
+     */
+    public Op copy() {
+        return transform(CopyContext.create(), OpTransformer.COPYING_TRANSFORMER);
+    }
+
+    /**
+     * Copies this operation and its bodies, if any.
+     * <p>
+     * The returned operation is structurally identical to this operation and is otherwise independent
+     * of the values declared and used.
+     *
+     * @param cc the copy context.
+     * @return the copied operation.
+     */
+    public Op copy(CopyContext cc) {
+        return transform(cc, OpTransformer.COPYING_TRANSFORMER);
+    }
+
+    /**
+     * Copies this operation and transforms its bodies, if any.
+     * <p>
+     * Bodies are {@link Body#transform(CopyContext, OpTransformer) transformed} with the given copy context and
+     * operation transformer.
+     *
+     * @param cc the copy context.
+     * @param ot the operation transformer.
+     * @return the transformed operation.
+     */
+    public abstract Op transform(CopyContext cc, OpTransformer ot);
+
+    /**
+     * Constructs an operation with a name and list of operands.
+     *
+     * @param name       the operation name.
+     * @param operands   the list of operands, a copy of the list is performed if required.
+     */
+    protected Op(String name, List<? extends Value> operands) {
+        this.name = name;
+        this.operands = List.copyOf(operands);
+    }
+
+    /**
+     * Sets the originating source location of this operation, if unbound.
+     *
+     * @param l the location, the {@link Location#NO_LOCATION} value indicates the location is not specified.
+     * @throws IllegalStateException if this operation is bound
+     */
+    public final void setLocation(Location l) {
+        // @@@ Fail if location != null?
+        if (result != null && result.block.isBound()) {
+            throw new IllegalStateException();
+        }
+
+        location = l;
+    }
+
+    /**
+     * {@return the originating source location of this operation, otherwise {@code null} if not specified}
+     */
+    public final Location location() {
+        return location;
+    }
+
+    /**
+     * Returns this operation's parent block, otherwise {@code null} if the operation is not assigned to a block.
+     *
+     * @return operation's parent block, or {@code null} if the operation is not assigned to a block.
+     */
+    @Override
+    public final Block parent() {
+        return parentBlock();
+    }
+
+    /**
+     * Returns this operation's parent block, otherwise {@code null} if the operation is not assigned to a block.
+     *
+     * @return operation's parent block, or {@code null} if the operation is not assigned to a block.
+     */
+    public final Block parentBlock() {
+        if (result == null) {
+            return null;
+        }
+
+        if (!result.block.isBound()) {
+            throw new IllegalStateException("Parent block is partially constructed");
+        }
+
+        return result.block;
+    }
+
+    @Override
+    public final List<Body> children() {
+        return bodies();
+    }
+
+    /**
+     * {@return the operation's bodies, as an unmodifiable list}
+     * @implSpec this implementation returns an unmodifiable empty list.
+     */
+    public List<Body> bodies() {
+        return List.of();
+    }
+
+    /**
+     * Returns the operation's result, otherwise {@code null} if the operation is not assigned to a block.
+     *
+     * @return the operation's result, or {@code null} if not assigned to a block.
+     */
+    public final Result result() {
+        return result;
+    }
+
+
+    /**
+     * Returns this operation's nearest ancestor body (the parent body of this operation's parent block),
+     * otherwise {@code null} if the operation is not assigned to a block.
+     *
+     * @return operation's nearest ancestor body, or {@code null} if the operation is not assigned to a block.
+     */
+    public final Body ancestorBody() {
+        if (result == null) {
+            return null;
+        }
+
+        if (!result.block.isBound()) {
+            throw new IllegalStateException("Parent body is partially constructed");
+        }
+
+        return result.block.parentBody;
+    }
+
+    /**
+     * {@return the operation name}
+     */
+    public String opName() {
+        return name;
+    }
+
+    /**
+     * {@return the operation's operands, as an unmodifiable list}
+     */
+    public List<Value> operands() {
+        return operands;
+    }
+
+    /**
+     * {@return the operation's successors, as an unmodifiable list}
+     */
+    public List<Block.Reference> successors() {
+        return List.of();
+    }
+
+    /**
+     * {@return the operation's result type}
+     */
+    public abstract TypeElement resultType();
+
+    /**
+     * Returns the operation's function type.
+     * <p>
+     * The function type's result type is the operation's result type and the function type's parameter types are the
+     * operation's operand types, in order.
+     *
+     * @return the function type
+     */
+    public FunctionType opType() {
+        List<TypeElement> operandTypes = operands.stream().map(Value::type).toList();
+        return FunctionType.functionType(resultType(), operandTypes);
+    }
+
+    /**
+     * Traverse the operands of this operation that are the results of prior operations, recursively.
+     * <p>
+     * Traversal is performed in pre-order, reporting the operation of each operand to the visitor.
+     *
+     * @param t   the traversing accumulator
+     * @param v   the visitor
+     * @param <T> accumulator type
+     * @return the traversing accumulator
+     * @apiNote A visitor that implements the abstract method of {@code OpVisitor} and does not override any
+     * other default method will only visit operations. As such a lambda expression or method reference
+     * may be used to visit operations.
+     */
+    public final <T> T traverseOperands(T t, BiFunction<T, Op, T> v) {
+        for (Value arg : operands()) {
+            if (arg instanceof Result or) {
+                t = v.apply(t, or.op);
+                t = or.op.traverseOperands(t, v);
+            }
+        }
+
+        return t;
+    }
+
+    /**
+     * Computes values captured by this operation. A captured value is a value that dominates
+     * this operation and is used by a descendant operation.
+     * <p>
+     * The order of the captured values is first use encountered in depth
+     * first search of this operation's descendant operations.
+     *
+     * @return the list of captured values, modifiable
+     * @see Body#capturedValues()
+     */
+    public List<Value> capturedValues() {
+        Set<Value> cvs = new LinkedHashSet<>();
+
+        capturedValues(cvs, new ArrayDeque<>(), this);
+        return new ArrayList<>(cvs);
+    }
+
+    static void capturedValues(Set<Value> capturedValues, Deque<Body> bodyStack, Op op) {
+        for (Body childBody : op.bodies()) {
+            Body.capturedValues(capturedValues, bodyStack, childBody);
+        }
+    }
+
+    /**
+     * Writes the textual form of this operation to the given output stream, using the UTF-8 character set.
+     *
+     * @param out the stream to write to.
+     */
+    public void writeTo(OutputStream out) {
+        writeTo(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+    }
+
+    /**
+     * Writes the textual form of this operation to the given writer.
+     *
+     * @param w the writer to write to.
+     */
+    public void writeTo(Writer w) {
+        OpWriter.writeTo(w, this);
+    }
+
+    /**
+     * Returns the textual form of this operation.
+     *
+     * @return the textual form of this operation.
+     */
+    public String toText() {
+        return OpWriter.toText(this);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/OpTransformer.java b/src/java.base/share/classes/java/lang/reflect/code/OpTransformer.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/OpTransformer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * An operation transformer.
+ */
+@FunctionalInterface
+public interface OpTransformer extends BiFunction<Block.Builder, Op, Block.Builder> {
+    /**
+     * A copying transformer that applies the operation to the block builder, and returning the block builder.
+     */
+    OpTransformer COPYING_TRANSFORMER = (block, op) -> {
+        block.op(op);
+        return block;
+    };
+
+    /**
+     * A transformer that performs no action on the block builder.
+     */
+    OpTransformer NOOP_TRANSFORMER = (block, op) -> block;
+
+    /**
+     * A transformer that drops location information from operations.
+     */
+    OpTransformer DROP_LOCATION_TRANSFORMER = (block, op) -> {
+        Op.Result r = block.op(op);
+        r.op().setLocation(Location.NO_LOCATION);
+        return block;
+    };
+
+    /**
+     * A transformer that lowers operations that are {@link Op.Lowerable lowerable},
+     * and copies other operations.
+     */
+    OpTransformer LOWERING_TRANSFORMER = (block, op) -> {
+        if (op instanceof Op.Lowerable lop) {
+            return lop.lower(block);
+        } else {
+            block.op(op);
+            return block;
+        }
+    };
+
+    /**
+     * Transforms a given operation to zero or more other operations appended to the
+     * given block builder. Returns a block builder to be used for appending further operations, such
+     * as subsequent operations from the same block as the given operation.
+     *
+     * @param block the block builder.
+     * @param op    the operation to transform.
+     * @return      the block builder to append to for subsequent operations to transform that have same parent block.
+     */
+    Block.Builder apply(Block.Builder block, Op op);
+
+    /**
+     * Transforms a given block to zero or more operations appended to the given block builder.
+     *
+     * @implSpec
+     * The default implementation iterates through each operation of the block to transform
+     * and {@link #apply(Block.Builder, Op) applies} a block builder and the operation to this
+     * transformer.
+     * On first iteration the block builder that is applied is block builder passed as an argument
+     * to this method.
+     * On second and subsequent iterations the block builder that is applied is the resulting
+     * block builder of the prior iteration.
+     *
+     * @param block the block builder
+     * @param b     the block to transform
+     * @throws NullPointerException if a resulting block builder is null
+     */
+    default void apply(Block.Builder block, Block b) {
+        for (Op op : b.ops()) {
+            block = apply(block, op);
+            // @@@ See andThen composition
+            Objects.requireNonNull(block);
+        }
+    }
+
+    default OpTransformer compose(OpTransformer before) {
+        return before.andThen(this);
+    }
+
+    default OpTransformer andThen(OpTransformer after) {
+        if (after == NOOP_TRANSFORMER) {
+            return this;
+        } else if (this == NOOP_TRANSFORMER) {
+            return after;
+        } else {
+            return (bb, o) -> {
+                Block.Builder nbb = apply(bb, o);
+                if (nbb != null) {
+                    return after.apply(nbb, o);
+                } else {
+                    // @@@ This does not currently occur
+                    return null;
+                }
+            };
+        }
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Quotable.java b/src/java.base/share/classes/java/lang/reflect/code/Quotable.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Quotable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+/**
+ * Classes implementing this interface support code reflection. That is, they can obtain
+ * a {@link Quoted} object using {@link #quoted()}, which returns the intermediate
+ * representation associated with a lambda expression or method reference.
+ */
+public interface Quotable {
+    default Quoted quoted() {
+        throw new UnsupportedOperationException();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Quoted.java b/src/java.base/share/classes/java/lang/reflect/code/Quoted.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Quoted.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * The quoted form of an operation.
+ * <p>
+ * The quoted form is utilized when the code model of some code is to be obtained rather than obtaining the result of
+ * executing that code. For example passing the of a lambda expression in quoted form rather than the expression being
+ * targeted to a functional interface from which it can be invoked.
+ */
+public final class Quoted {
+    private final Op op;
+    private final Map<Value, Object> capturedValues;
+
+    /**
+     * Constructs the quoted form of a given operation.
+     *
+     * @param op the invokable operation.
+     */
+    public Quoted(Op op) {
+        this(op, Map.of());
+    }
+
+    /**
+     * Constructs the quoted form of a given operation.
+     * <p>
+     * The captured values key set must have the same elements and same encounter order as
+     * operation's captured values, specifically the following expression should evaluate to true:
+     * {@snippet lang=java :
+     * op.capturedValues().equals(new ArrayList<>(capturedValues.keySet()));
+     * }
+     *
+     * @param op             the operation.
+     * @param capturedValues the captured values referred to by the operation
+     * @see Op#capturedValues()
+     */
+    public Quoted(Op op, Map<Value, Object> capturedValues) {
+        // @@@ This check is potentially expensive, remove or keep as assert?
+        // @@@ Or make Quoted an interface, with a module private implementation?
+        assert op.capturedValues().equals(new ArrayList<>(capturedValues.keySet()));
+        this.op = op;
+        this.capturedValues = Collections.unmodifiableMap(new LinkedHashMap<>(capturedValues));
+    }
+
+    /**
+     * Returns the operation.
+     *
+     * @return the operation.
+     */
+    public Op op() {
+        return op;
+    }
+
+    /**
+     * Returns the captured values.
+     * <p>
+     * The captured values key set has the same elements and same encounter order as
+     * operation's captured values, specifically the following expression evaluates to true:
+     * {@snippet lang=java :
+     * op().capturedValues().equals(new ArrayList<>(capturedValues().keySet()));
+     * }
+     *
+     * @return the captured values, as an unmodifiable map.
+     */
+    public Map<Value, Object> capturedValues() {
+        return capturedValues;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/TypeElement.java b/src/java.base/share/classes/java/lang/reflect/code/TypeElement.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/TypeElement.java
@@ -0,0 +1,125 @@
+package java.lang.reflect.code;
+
+import java.lang.reflect.code.type.TypeElementFactory;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A type, that defines a set of values.
+ * <p>
+ * A type can be assigned to a {@link Value value} in a code model,
+ * and implies the value is a member of the type's set.
+ * <p>
+ * The {@code equals} method should be used to check if two type elements
+ * are equal to each other.
+ * @apiNote
+ * Code model types enable reasoning statically about a code model,
+ * approximating run time behaviour.
+ */
+public non-sealed interface TypeElement extends CodeItem {
+    // @@@ Common useful methods generally associated with properties of a type
+    // e.g., arguments, is an array etc. (dimensions)
+
+    /**
+     * A type element's externalized content in structured symbolic form.
+     * <p>
+     * A {@link TypeElement type element} can be constructed from an externalized type element
+     * using a {@link TypeElementFactory}.
+     *
+     * @param identifier the externalized type's identifier
+     * @param arguments  the externalized type's arguments
+     */
+    record ExternalizedTypeElement(String identifier, List<ExternalizedTypeElement> arguments) {
+
+        /**
+         * {@inheritDoc}
+         */
+        public ExternalizedTypeElement {
+            arguments = List.copyOf(arguments);
+        }
+
+        @Override
+        public String toString() {
+            return toString(this);
+        }
+
+        static String toString(ExternalizedTypeElement t) {
+            if (t.arguments.isEmpty()) {
+                return t.identifier;
+            }
+
+            // Unpack array-like identifier [+
+            int dimensions = 0;
+            if (t.arguments.size() == 1) {
+                dimensions = dimensions(t.identifier);
+                if (dimensions > 0) {
+                    t = t.arguments.getFirst();
+                }
+            }
+
+            StringBuilder s = new StringBuilder();
+            s.append(t.identifier);
+            if (!t.arguments.isEmpty()) {
+                String args = t.arguments.stream()
+                        .map(Object::toString)
+                        .collect(Collectors.joining(", ", "<", ">"));
+                s.append(args);
+            }
+
+            // Write out array-like syntax at end []+
+            if (dimensions > 0) {
+                s.append("[]".repeat(dimensions));
+            }
+
+            return s.toString();
+        }
+
+        static int dimensions(String identifier) {
+            if (!identifier.isEmpty() && identifier.charAt(0) == '[') {
+                for (int i = 1; i < identifier.length(); i++) {
+                    if (identifier.charAt(i) != '[') {
+                        return 0;
+                    }
+                }
+                return identifier.length();
+            } else {
+                return 0;
+            }
+        }
+
+        // Factories
+
+        /**
+         * Parses a string as an externalized type element.
+         * <p>
+         * For any given externalized type element, {@code te}, the following
+         * expression returns {@code true}.
+         * {@snippet lang=java
+         * te.equals(ExternalizedTypeElement.ofString(te.toString()));
+         * }
+         * @param s the string
+         * @return the externalized code type.
+         */
+        // Copied code in jdk.compiler module throws UOE
+        public static ExternalizedTypeElement ofString(String s) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseExTypeElem(s);
+        }
+    }
+
+    /**
+     * Externalizes this type element's content.
+     *
+     * @return the type element's content.
+     * @throws UnsupportedOperationException if the type element is not externalizable
+     */
+    ExternalizedTypeElement externalize();
+
+    @Override
+    String toString();
+
+    @Override
+    boolean equals(Object o);
+
+    @Override
+    int hashCode();
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/Value.java b/src/java.base/share/classes/java/lang/reflect/code/Value.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/Value.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A value, that is the result of an operation or a block parameter.
+ * @sealedGraph
+ */
+public sealed abstract class Value implements Comparable<Value>, CodeItem
+        permits Block.Parameter, Op.Result {
+    final Block block;
+    final TypeElement type;
+    // @@@ In topological order?
+    //     Can the representation be more efficient e.g. an array?
+    final Set<Op.Result> uses;
+
+    Value(Block block, TypeElement type) {
+        this.block = block;
+        this.type = type;
+        this.uses = new LinkedHashSet<>();
+    }
+
+    /**
+     * Returns this value's declaring block.
+     * <p>If the value is an operation result, then the declaring block is the operation's parent block.
+     * If the value is a block parameter then the declaring block is the block declaring the parameter.
+     *
+     * @return the value's declaring block.
+     * @throws IllegalStateException if the declaring block is partially built
+     */
+    public Block declaringBlock() {
+        if (!isBound()) {
+            throw new IllegalStateException("Declaring block is partially constructed");
+        }
+        return block;
+    }
+
+    /**
+     * Returns the type of the value.
+     *
+     * @return the type of the value.
+     */
+    public TypeElement type() {
+        return type;
+    }
+
+    /**
+     * Returns the values this value directly depends on.
+     * <p>
+     * An operation result depends on the set of values whose members are the operation's operands and block arguments
+     * of the operation's successors.
+     * A block parameter does not depend on any values.
+     *
+     * @return the values this value directly depends on, as an unmodifiable set.
+     */
+    public abstract Set<Value> dependsOn();
+
+    /**
+     * Returns the uses of this value, specifically each operation result of an operation where this value is used as
+     * an operand or as an argument of a block reference that is a successor.
+     *
+     * @return the uses of this value, as an unmodifiable set.
+     * @throws IllegalStateException if the declaring block is partially built
+     */
+    public Set<Op.Result> uses() {
+        if (!isBound()) {
+            throw new IllegalStateException("Users are partially constructed");
+        }
+
+        return Collections.unmodifiableSet(uses);
+    }
+
+    /**
+     * Returns {@code true} if this value is dominated by the given value {@code dom}.
+     * <p>
+     * If {@code v} and {@code dom} are in not declared in the same block then, domination is the result of
+     * if the declaring block of {@code v} is dominated by the declaring block of {@code dom}.
+     * <p>
+     * Otherwise, if {@code v} and {@code dom} are declared in the same block then (in order):
+     * <ul>
+     * <li>if {@code dom} is a block parameter, then {@code v} is dominated by {@code dom}.
+     * <li>if {@code v} is a block parameter, then {@code v} is <b>not</b> dominated by {@code dom}.
+     * <li>otherwise, both {@code v} and {@code dom} are operation results, then {@code v} is dominated by {@code dom}
+     * if {@code v} is the same as {@code dom} or {@code v} occurs after {@code dom} in the declaring block.
+     * </ul>
+     *
+     * @param dom the dominating value
+     * @return {@code true} if this value is dominated by the given value {@code dom}.
+     * @throws IllegalStateException if the declaring block is partially built
+     */
+    public boolean isDominatedBy(Value dom) {
+        if (this == dom) {
+            return true;
+        }
+
+        if (declaringBlock() != dom.declaringBlock()) {
+            return declaringBlock().isDominatedBy(dom.declaringBlock());
+        }
+
+        // Any value is dominated by a block parameter
+        if (dom instanceof Block.Parameter) {
+            return true;
+        } else if (this instanceof Block.Parameter) {
+            return false;
+        } else {
+            assert this instanceof Op.Result &&
+                    dom instanceof Op.Result;
+            List<Op> ops = declaringBlock().ops();
+            return ops.indexOf(((Op.Result) this).op()) >= ops.indexOf(((Op.Result) dom).op());
+        }
+    }
+
+
+    @Override
+    public int compareTo(Value o) {
+        return compare(this, o);
+    }
+
+    // @@@
+    public static int compare(Value v1, Value v2) {
+        if (v1 == v2) return 0;
+
+        Block b1 = v1.declaringBlock();
+        Block b2 = v2.declaringBlock();
+        if (b1 == b2) {
+            if (v1 instanceof Op.Result or1 && v2 instanceof Op.Result or2) {
+                List<Op> ops = b1.ops();
+                return Integer.compare(ops.indexOf(or1.op()), ops.indexOf(or2.op()));
+            } else if (v1 instanceof Op.Result) {
+                // v2 instanceof BlockParameter
+                return 1;
+            } else if (v2 instanceof Op.Result) {
+                // v1 instanceof BlockParameter
+                return -1;
+            } else { // v1 && v2 instanceof BlockParameter
+                assert v1 instanceof Block.Parameter && v2 instanceof Block.Parameter;
+                List<Block.Parameter> args = b1.parameters();
+                return Integer.compare(args.indexOf(v1), args.indexOf(v2));
+            }
+        }
+
+        Body r1 = b1.parentBody();
+        Body r2 = b2.parentBody();
+        if (r1 == r2) {
+            // @@@ order should be defined by CFG and dominator relations
+            List<Block> bs = r1.blocks();
+            return Integer.compare(bs.indexOf(b1), bs.indexOf(b2));
+        }
+
+        Op o1 = r1.parentOp();
+        Op o2 = r2.parentOp();
+        if (o1 == o2) {
+            List<Body> rs = o1.bodies();
+            return Integer.compare(rs.indexOf(r1), rs.indexOf(r2));
+        }
+
+        return compare(o1.result(), o2.result());
+    }
+
+    boolean isBound() {
+        return block.isBound();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/analysis/Liveness.java b/src/java.base/share/classes/java/lang/reflect/code/analysis/Liveness.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/analysis/Liveness.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.reflect.code.analysis;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.writer.OpWriter;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Provides liveness information for values declared in the bodies of an operation.
+ */
+public class Liveness {
+
+    /**
+     * Liveness information associated with a block.
+     * Each block has two sets of values, live-in values and live-out values.
+     */
+    public static final class BlockInfo {
+        final Block block;
+        final Deque<Value> inValues;
+        final Deque<Value> outValues;
+
+        BlockInfo(Block block) {
+            this.block = block;
+            this.inValues = new ArrayDeque<>();
+            this.outValues = new ArrayDeque<>();
+        }
+
+        /**
+         * {@return the block associated with the liveness information}
+         */
+        public Block getBlock() {
+            return block;
+        }
+
+        /**
+         * Returns true if a value is live-in for the associated block.
+         * <p>
+         * A value is live-in for a block if it is not declared in the block
+         * and is used in the block or (transitively) by some successor.
+         *
+         * @param value the value
+         * @return true if the value is live-in
+         */
+        public boolean isLiveIn(Value value) {
+            return inValues.contains(value);
+        }
+
+        /**
+         * {@return the set of live-in values}
+         */
+        public Set<Value> liveIn() {
+            return new HashSet<>(inValues);
+        }
+
+        /**
+         * Returns true if a value is live-out for the associated block.
+         * <p>
+         * A value is live-out for a block if it is used (transitively) by some successor.
+         *
+         * @param value the value
+         * @return true if the value is live-out
+         */
+        public boolean isLiveOut(Value value) {
+            return outValues.contains(value);
+        }
+
+        /**
+         * {@return the set of live-out values}
+         */
+        public Set<Value> liveOut() {
+            return new HashSet<>(outValues);
+        }
+
+        /**
+         * Returns the first operation associated with a value and the associated block.
+         * <p>
+         * If the value is live-in or a block argument then the blocks first operation
+         * is returned. Otherwise, the value is an operation result and its operation
+         * is returned.
+         *
+         * @param value the value
+         * @return first operation associated with a value and the associated block.
+         */
+        public Op getStartOperation(Value value) {
+            if (isLiveIn(value) || value instanceof Block.Parameter) {
+                // @@@ Check value is from this block
+                return block.firstOp();
+            } else {
+                // @@@ Check value is from block
+                Op.Result or = (Op.Result) value;
+                return or.op();
+            }
+        }
+
+        /**
+         * Returns the end operation associated with a value and the associated block.
+         * <p>
+         * If the value is live-out then the blocks last (and terminating) operation
+         * is returned. Otherwise, the value is dying in this block and the last
+         * operation to use this value is returned.
+         *
+         * @param value the value
+         * @return first operation associated with a value and the associated block.
+         */
+        public Op getEndOperation(Value value, Op startOp) {
+            // Value is used by some other operation
+            if (isLiveOut(value)) {
+                return block.terminatingOp();
+            }
+
+            // Value may be last used in this block, if so find it
+            // @@@ Check startOp is of this block
+            Op endOp = startOp;
+            for (Op.Result useOpr : value.uses()) {
+                Op useOp = useOpr.op();
+                // Find the operation in the current block
+                useOp = block.findAncestorOpInBlock(useOp);
+                // Update if after
+                if (useOp != null && isBeforeInBlock(endOp, useOp)) {
+                    endOp = useOp;
+                }
+            }
+            return endOp;
+        }
+    }
+
+    final Op op;
+    final Map<Block, BlockInfo> livenessMapping;
+
+    /**
+     * Constructs liveness information for values declared in the bodies
+     * of an operation.
+     *
+     * @param op the operation.
+     */
+    @SuppressWarnings("this-escape")
+    public Liveness(Op op) {
+        this.op = op;
+        this.livenessMapping = new HashMap<>();
+        for (Body cfg : op.bodies()) {
+            Compute_LiveSets_SSA_ByVar(cfg);
+        }
+    }
+
+    /*
+    The algorithm to compute liveness information is derived from
+    Domaine, & Brandner, Florian & Boissinot, Benoit & Darte, Alain & Dinechin, Benoit & Rastello, Fabrice.
+    (2011). Computing Liveness Sets for SSA-Form Programs.
+    https://inria.hal.science/inria-00558509v2/document
+    Specifically Algorithm 6 & 7, adapted to work with block arguments and
+    block parameters instead of phi operations.
+    This is a simple algorithm that is easy to understand. We may need to review
+    its usage within exception regions.
+    We also may revisit this later with a more performant implementation
+    perhaps based on the well known algorithm that uses fixpoint iteration.
+     */
+
+    void Compute_LiveSets_SSA_ByVar(Body CFG) {
+        for (Block b : CFG.blocks()) {
+            livenessMapping.put(b, new BlockInfo(b));
+        }
+        for (Block b : CFG.blocks()) {
+            for (Block.Parameter p : b.parameters()) {
+                Compute_LiveSets_SSA_ByVar(CFG, p);
+            }
+
+            for (Op op : b.ops()) {
+                Compute_LiveSets_SSA_ByVar(CFG, op.result());
+            }
+        }
+    }
+
+    void Compute_LiveSets_SSA_ByVar(Body CFG, Value v) {
+        for (Op.Result use : v.uses()) {
+            Block B = CFG.findAncestorBlockInBody(use.declaringBlock());
+            Up_and_Mark_Stack(B, v);
+        }
+    }
+
+    void Up_and_Mark_Stack(Block B, Value v) {
+        if (v.declaringBlock() == B) {
+            return;
+        }
+        var lbi = livenessMapping.get(B);
+        if (lbi.inValues.peek() == v) {
+            return;
+        }
+        lbi.inValues.push(v);
+        for (Block P : B.predecessors()) {
+            lbi = livenessMapping.get(P);
+            if (lbi.outValues.peek() != v) {
+                lbi.outValues.push(v);
+            }
+            Up_and_Mark_Stack(P, v);
+        }
+    }
+
+    /**
+     * {@return the liveness information as a string}
+     */
+    public String toString() {
+        StringWriter w = new StringWriter();
+        writeTo(w);
+        return w.toString();
+    }
+
+    /**
+     * Writes the liveness information to the given writer.
+     *
+     * @param w the writer to write to.
+     */
+    public void writeTo(Writer w) {
+        OpWriter ow = new OpWriter(w);
+        ow.writeOp(op);
+        try {
+            w.write("\n");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        Function<CodeItem, String> namer = ow.namer();
+        op.traverse(null, CodeElement.blockVisitor((_, b) -> {
+            BlockInfo liveness = getLiveness(b);
+            try {
+                w.write("^" + namer.apply(b));
+                w.write("\n");
+                w.write("  Live-in values: ");
+                w.write(liveness.inValues.stream()
+                        .map(v -> "%" + namer.apply(v))
+                        .collect(Collectors.joining(",")));
+                w.write("\n");
+                w.write("  Live-out values: ");
+                w.write(liveness.outValues.stream()
+                        .map(v -> "%" + namer.apply(v))
+                        .collect(Collectors.joining(",")));
+                w.write("\n");
+                return null;
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }));
+    }
+
+    /**
+     * Returns true if a value is last used by an operation.
+     * <p>
+     * The liveness information for the operation's parent block
+     * is obtained. If the value is live-out then the value escapes
+     * the block and is therefore not the last use, and this method
+     * returns false.
+     * If the operation is the last to use the value, this method
+     * returns true. If the operation does not use the value and
+     * the {@link BlockInfo#getEndOperation end operation}
+     * occurs before the operation, this method returns true.
+     * Otherwise, this method returns false.
+     *
+     * @param value the value
+     * @param op    the operation
+     * @return true if a value is last used by an operation
+     */
+    public boolean isLastUse(Value value, Op op) {
+        Block block = op.parentBlock();
+        BlockInfo liveness = getLiveness(block);
+
+        // Value is used by some successor
+        if (liveness.isLiveOut(value))
+            return false;
+
+        Op endOp = liveness.getEndOperation(value, op);
+        // Last use or operation is after last use
+        return endOp == op || isBeforeInBlock(endOp, op);
+    }
+
+    /**
+     * {@return the liveness information associated with a block}
+     *
+     * @param block the block
+     * @throws IllegalArgumentException if the block has no liveness information
+     */
+    public BlockInfo getLiveness(Block block) {
+        BlockInfo lbi = livenessMapping.get(block);
+        if (lbi == null) {
+            throw new IllegalArgumentException("Block has no liveness information");
+        }
+        return lbi;
+    }
+
+    private static boolean isBeforeInBlock(Op thisOp, Op thatOp) {
+        if (thisOp.result() == null || thatOp.result() == null) {
+            throw new IllegalArgumentException("This or the given operation is not assigned to a block");
+        }
+
+        if (thisOp.parentBlock() != thatOp.parentBlock()) {
+            throw new IllegalArgumentException("This and that operation are not assigned to the same blocks");
+        }
+
+        List<Op> ops = thisOp.parentBlock().ops();
+        return ops.indexOf(thisOp) < ops.indexOf(thatOp);
+    }
+
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/analysis/Patterns.java b/src/java.base/share/classes/java/lang/reflect/code/analysis/Patterns.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/analysis/Patterns.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.analysis;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.CodeElement;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.op.CoreOp;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+/**
+ * A simple and experimental pattern match mechanism on values and operations.
+ * <p>
+ * When the language has support for pattern matching with matcher methods we should be able to express
+ * matching on values and operations more powerfully and concisely.
+ */
+public final class Patterns {
+
+    private Patterns() {
+    }
+
+
+    /**
+     * Traverses this operation and its descendant operations and returns the set of operations that are unused
+     * (have no uses) and are pure (are instances of {@code Op.Pure} and thus have no side effects).
+     *
+     * @param op the operation to traverse
+     * @return the set of used and pure operations.
+     */
+    public static Set<Op> matchUnusedPureOps(Op op) {
+        return matchUnusedPureOps(op, o -> o instanceof Op.Pure);
+    }
+
+    /**
+     * Traverses this operation and its descendant operations and returns the set of operations that are unused
+     * (have no uses) and are pure (according to the given predicate).
+     *
+     * @param op       the operation to traverse
+     * @param testPure the predicate to test if an operation is pure
+     * @return the set of used and pure operations.
+     */
+    public static Set<Op> matchUnusedPureOps(Op op, Predicate<Op> testPure) {
+        return match(
+                new HashSet<>(),
+                op, opP(o -> isDeadOp(o, testPure)),
+                (ms, deadOps) -> {
+                    deadOps.add(ms.op());
+
+                    // Dependent dead ops
+                    matchDependentDeadOps(ms.op(), deadOps, testPure);
+                    // @@@ No means to control traversal and only go deeper when
+                    // there is only one user
+//                    ms.op().traverseOperands(null, (_a, arg) -> {
+//                        if (arg.result().users().size() == 1) {
+//                            deadOps.add(arg);
+//                        }
+//
+//                        return null;
+//                    });
+
+                    return deadOps;
+                });
+    }
+
+    static boolean isDeadOp(Op op, Predicate<Op> testPure) {
+        if (op instanceof Op.Terminating) {
+            return false;
+        }
+
+        return op.result() != null && op.result().uses().isEmpty() && testPure.test(op);
+    }
+
+    // @@@ this could be made generic with a method traversing backwards
+    static void matchDependentDeadOps(Op op, Set<Op> deadOps, Predicate<Op> testPure) {
+        for (Value arg : op.operands()) {
+            if (arg instanceof Op.Result or) {
+                if (arg.uses().size() == 1 && testPure.test(or.op())) {
+                    deadOps.add(or.op());
+
+                    // Traverse only when a single user
+                    matchDependentDeadOps(or.op(), deadOps, testPure);
+                }
+            }
+        }
+    }
+
+
+    // Matching of patterns
+
+    /**
+     * The state of a successful match of an operation with matched operands (if any)
+     */
+    public static final class MatchState {
+        final Op op;
+        final List<Value> matchedOperands;
+
+        MatchState(Op op, List<Value> matchedOperands) {
+            this.op = op;
+            this.matchedOperands = matchedOperands;
+        }
+
+        /**
+         * {@return the matched operation}
+         */
+        public Op op() {
+            return op;
+        }
+
+        /**
+         * {@return the matched operands}
+         */
+        public List<Value> matchedOperands() {
+            return matchedOperands;
+        }
+    }
+
+    record PatternAndFunction<R>(OpPattern p, BiFunction<MatchState, R, R> f) {
+    }
+
+    // Visiting op pattern matcher
+    static class OpPatternMatcher<R> implements BiFunction<R, Op, R> {
+        final List<PatternAndFunction<R>> patterns;
+        final PatternState state;
+        final Map<MatchState, BiFunction<MatchState, R, R>> matches;
+
+        OpPatternMatcher(OpPattern p, BiFunction<MatchState, R, R> f) {
+            this(List.of(new PatternAndFunction<>(p, f)));
+        }
+
+        OpPatternMatcher(List<PatternAndFunction<R>> patterns) {
+            this.patterns = patterns;
+            this.state = new PatternState();
+            this.matches = new HashMap<>();
+        }
+
+        @Override
+        public R apply(R r, Op op) {
+            for (PatternAndFunction<R> pf : patterns) {
+                if (pf.p.match(op, state)) {
+                    MatchState ms = new MatchState(op, state.resetOnMatch());
+
+                    r = pf.f.apply(ms, r);
+                } else {
+                    state.resetOnNoMatch();
+                }
+            }
+
+            return r;
+        }
+    }
+
+    /**
+     * A match builder for declaring matching for one or more groups of operation patterns against a given traversable
+     * and descendant operations (in order).
+     * @param <R> the match result type
+     */
+    public static final class MultiMatchBuilder<R> {
+        final CodeElement<?, ?> o;
+        final R r;
+        List<PatternAndFunction<R>> patterns;
+
+        MultiMatchBuilder(CodeElement<?, ?> o, R r) {
+            this.o = o;
+            this.r = r;
+            this.patterns = new ArrayList<>();
+        }
+
+        /**
+         * Declares a first of possibly other operation patterns in a group.
+         *
+         * @param p the operation pattern
+         * @return a builder to declare further patterns in the group.
+         */
+        public MultiMatchCaseBuilder pattern(OpPattern p) {
+            return new MultiMatchCaseBuilder(p);
+        }
+
+        public R matchThenApply() {
+            OpPatternMatcher<R> opm = new OpPatternMatcher<>(patterns);
+            return o.traverse(r, CodeElement.opVisitor(opm));
+        }
+
+        /**
+         * A builder to declare further operation patterns in a group or to associate a
+         * target function to be applied if any of the patterns in the group match.
+         */
+        public final class MultiMatchCaseBuilder {
+            List<OpPattern> patterns;
+
+            MultiMatchCaseBuilder(OpPattern p) {
+                this.patterns = new ArrayList<>();
+                patterns.add(p);
+            }
+
+            /**
+             * Declares an operation pattern in the group.
+             *
+             * @param p the operation pattern
+             * @return this builder.
+             */
+            public MultiMatchCaseBuilder pattern(OpPattern p) {
+                patterns.add(p);
+                return this;
+            }
+
+            /**
+             * Declares the target function to be applied if any of the operation patterns on the group match.
+             *
+             * @param f the target function.
+             * @return the match builder to build further groups.
+             */
+            public MultiMatchBuilder<R> target(BiFunction<MatchState, R, R> f) {
+                patterns.stream().map(p -> new PatternAndFunction<>(p, f)).forEach(MultiMatchBuilder.this.patterns::add);
+                return MultiMatchBuilder.this;
+            }
+        }
+    }
+
+    /**
+     * Constructs a match builder from which to declare matching for one or more groups of operation patterns against a
+     * given traversable and descendant operations (in order).
+     *
+     * @param r   the initial match result
+     * @param t   the traversable
+     * @param <R> the match result type
+     * @return the match builder
+     */
+    public static <R> MultiMatchBuilder<R> multiMatch(R r, CodeElement<?, ?> t) {
+        return new MultiMatchBuilder<>(t, r);
+    }
+
+    /**
+     * Matches an operation pattern against the given traversable and descendant operations (in order).
+     *
+     * @param r         the initial match result
+     * @param t         the traversable
+     * @param opPattern the operation pattern
+     * @param matcher   the function to be applied with a match state and the current match result when an
+     *                  encountered operation matches the operation pattern
+     * @param <R>       the match result type
+     * @return the match result
+     */
+    public static <R> R match(R r, CodeElement<?, ?> t, OpPattern opPattern,
+                              BiFunction<MatchState, R, R> matcher) {
+        OpPatternMatcher<R> opm = new OpPatternMatcher<>(opPattern, matcher);
+        return t.traverse(r, CodeElement.opVisitor(opm));
+    }
+
+
+    // Pattern classes
+
+    static final class PatternState {
+        List<Value> matchedOperands;
+
+        void addOperand(Value v) {
+            if (matchedOperands == null) {
+                matchedOperands = new ArrayList<>();
+            }
+            matchedOperands.add(v);
+        }
+
+        List<Value> resetOnMatch() {
+            if (matchedOperands != null) {
+                List<Value> r = matchedOperands;
+                matchedOperands = null;
+                return r;
+            } else {
+                return List.of();
+            }
+        }
+
+        void resetOnNoMatch() {
+            if (matchedOperands != null) {
+                matchedOperands.clear();
+            }
+        }
+    }
+
+    /**
+     * A pattern matching against a value or operation.
+     */
+    public sealed static abstract class Pattern {
+        Pattern() {
+        }
+
+        abstract boolean match(Value v, PatternState state);
+    }
+
+    /**
+     * A pattern matching against an operation.
+     */
+    public static final class OpPattern extends Pattern {
+        final Predicate<Op> opTest;
+        final List<Pattern> operandPatterns;
+
+        OpPattern(Predicate<Op> opTest, List<Pattern> operandPatterns) {
+            this.opTest = opTest;
+            this.operandPatterns = List.copyOf(operandPatterns);
+        }
+
+        @Override
+        boolean match(Value v, PatternState state) {
+            if (v instanceof Op.Result or) {
+                return match(or.op(), state);
+            } else {
+                return false;
+            }
+        }
+
+        boolean match(Op op, PatternState state) {
+            // Test does not match
+            if (!opTest.test(op)) {
+                return false;
+            }
+
+            if (!operandPatterns.isEmpty()) {
+                // Arity does not match
+                if (op.operands().size() != operandPatterns.size()) {
+                    return false;
+                }
+
+                // Match all arguments
+                for (int i = 0; i < operandPatterns.size(); i++) {
+                    Pattern p = operandPatterns.get(i);
+                    Value v = op.operands().get(i);
+
+                    if (!p.match(v, state)) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * A pattern that unconditionally matches a value which is captured. If the value is an operation result of an
+     * operation, then an operation pattern (if any) is further matched against the operation.
+     */
+    // @@@ type?
+    static final class ValuePattern extends Pattern {
+        final OpPattern opMatcher;
+
+        ValuePattern() {
+            this(null);
+        }
+
+        public ValuePattern(OpPattern opMatcher) {
+            this.opMatcher = opMatcher;
+        }
+
+        @Override
+        boolean match(Value v, PatternState state) {
+            // Capture the operand
+            state.addOperand(v);
+
+            // Match on operation on nested pattern, if any
+            return opMatcher == null || opMatcher.match(v, state);
+        }
+    }
+
+    /**
+     * A pattern that conditionally matches an operation result which is captured,  then an operation pattern (if any)
+     * is further matched against the result's operation.
+     */
+    static final class OpResultPattern extends Pattern {
+        final OpPattern opMatcher;
+
+        OpResultPattern() {
+            this(null);
+        }
+
+        public OpResultPattern(OpPattern opMatcher) {
+            this.opMatcher = opMatcher;
+        }
+
+        @Override
+        boolean match(Value v, PatternState state) {
+            if (!(v instanceof Op.Result)) {
+                return false;
+            }
+
+            // Capture the operand
+            state.addOperand(v);
+
+            // Match on operation on nested pattern, if any
+            return opMatcher == null || opMatcher.match(v, state);
+        }
+    }
+
+    /**
+     * A pattern that conditionally matches a block parameter which is captured.
+     */
+    static final class BlockParameterPattern extends Pattern {
+        BlockParameterPattern() {
+        }
+
+        @Override
+        boolean match(Value v, PatternState state) {
+            if (!(v instanceof Block.Parameter)) {
+                return false;
+            }
+
+            // Capture the operand
+            state.addOperand(v);
+
+            return true;
+        }
+    }
+
+    /**
+     * A pattern matching any value or operation.
+     */
+    static final class AnyPattern extends Pattern {
+        AnyPattern() {
+        }
+
+        @Override
+        boolean match(Value v, PatternState state) {
+            return true;
+        }
+    }
+
+
+    // Pattern factories
+
+    /**
+     * Creates an operation pattern that tests against an operation by applying it to the predicate, and if
+     * {@code true}, matches operand patterns against the operation's operands (in order) .
+     * This operation pattern matches an operation if the test returns {@code true} and all operand patterns match
+     * against the operation's operands.
+     *
+     * @param opTest the predicate
+     * @param patterns the operand patterns
+     * @return the operation pattern
+     */
+    public static OpPattern opP(Predicate<Op> opTest, Pattern... patterns) {
+        return opP(opTest, List.of(patterns));
+    }
+
+    /**
+     * Creates an operation pattern that tests against an operation by applying it to the predicate, and if
+     * {@code true}, matches operand patterns against the operation's operands (in order) .
+     * This operation pattern matches an operation if the test returns {@code true} and all operand patterns match
+     * against the operation's operands.
+     *
+     * @param opTest the predicate
+     * @param patterns the operand patterns
+     * @return the operation pattern
+     */
+    public static OpPattern opP(Predicate<Op> opTest, List<Pattern> patterns) {
+        return new OpPattern(opTest, patterns);
+    }
+
+    /**
+     * Creates an operation pattern that tests if the operation is an instance of the class, and if
+     * {@code true}, matches operand patterns against the operation's operands (in order) .
+     * This operation pattern matches an operation if the test returns {@code true} and all operand patterns match
+     * against the operation's operands.
+     *
+     * @param opClass the operation class
+     * @param patterns the operand patterns
+     * @return the operation pattern
+     */
+    public static OpPattern opP(Class<?> opClass, Pattern... patterns) {
+        return opP(opClass::isInstance, patterns);
+    }
+
+    /**
+     * Creates an operation pattern that tests if the operation is a {@link CoreOp.ConstantOp constant} operation
+     * and whose constant value is equal to the given value.
+     * This operation pattern matches an operation if the test returns {@code true}.
+     *
+     * @param value the value
+     * @return the operation pattern.
+     */
+    public static OpPattern constantP(Object value) {
+        return opP(op -> {
+            if (op instanceof CoreOp.ConstantOp cop) {
+                return Objects.equals(value, cop.value());
+            }
+
+            return false;
+        });
+    }
+
+    /**
+     * Creates a value pattern that unconditionally matches any value and captures the value in match state.
+     *
+     * @return the value pattern.
+     */
+    public static Pattern valueP() {
+        return new ValuePattern();
+    }
+
+    /**
+     * Creates a value pattern that unconditionally matches any value and captures the value in match state, and
+     * if the value is an operation result of an operation, then the operation pattern is matched against that
+     * operation.
+     * This value pattern matches value if value is not an operation result, or otherwise matches if the operation
+     * pattern matches.
+     *
+     * @param opMatcher the operation pattern
+     * @return the value pattern.
+     */
+    public static Pattern valueP(OpPattern opMatcher) {
+        return new ValuePattern(opMatcher);
+    }
+
+    /**
+     * Creates an operation result pattern that conditionally matches an operation result and captures it in match state.
+     *
+     * @return the operation result.
+     */
+    public static Pattern opResultP() {
+        return new OpResultPattern();
+    }
+
+    /**
+     * Creates an operation result pattern that conditionally matches an operation result and captures it in match state,
+     * then the operation pattern is matched against the result's operation.
+     *
+     * @param opMatcher the operation pattern
+     * @return the operation result.
+     */
+    public static Pattern opResultP(OpPattern opMatcher) {
+        return new OpResultPattern(opMatcher);
+    }
+
+    /**
+     * Creates a block parameter result pattern that conditionally matches a block parameter and captures it in match state.
+     *
+     * @return the block parameter.
+     */
+    public static Pattern blockParameterP() {
+        return new BlockParameterPattern();
+    }
+
+    /**
+     * Creates a pattern that unconditionally matches any value or operation.
+     *
+     * @return the value pattern.
+     */
+    public static Pattern _P() {
+        return new AnyPattern();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/analysis/SSA.java b/src/java.base/share/classes/java/lang/reflect/code/analysis/SSA.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/analysis/SSA.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.analysis;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Functionality to transform a code model into pure SSA form, replacing operations that declare variables and
+ * access them with the use of values they depend on or additional block parameters.
+ */
+public final class SSA {
+    private SSA() {
+    }
+
+    /**
+     * Applies an SSA transformation to an invokable operation, replacing operations that declare variables and
+     * access them with the use of values they depend on or additional block parameters.
+     * <p>
+     * The operation should first be in lowered form before applying this transformation.
+     * <p>
+     * Note: this implementation does not currently work correctly when a variable is stored to within an exception
+     * region and read from outside as a result of catching an exception. In such cases a complete transformation may be
+     * not possible and such variables will need to be retained.
+     *
+     * @param iop the invokable operation
+     * @return the transformed operation
+     * @param <T> the invokable type
+     */
+    public static <T extends Op & Op.Invokable> T transform(T iop) {
+        Map<Block, Set<CoreOp.VarOp>> joinPoints = new HashMap<>();
+        Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues = new HashMap<>();
+        Map<Block.Reference, List<Object>> joinSuccessorValues = new HashMap<>();
+
+        Map<Body, Boolean> visited = new HashMap<>();
+        Map<Block, Map<CoreOp.VarOp, Block.Parameter>> joinBlockArguments = new HashMap<>();
+        @SuppressWarnings("unchecked")
+        T liop = (T) iop.transform(CopyContext.create(), (block, op) -> {
+            // Compute join points and value mappings for body
+            visited.computeIfAbsent(op.ancestorBody(), b -> {
+                findJoinPoints(b, joinPoints);
+                variableToValue(b, joinPoints, loadValues, joinSuccessorValues);
+                return true;
+            });
+
+            if (op instanceof CoreOp.VarOp || op instanceof CoreOp.VarAccessOp) {
+                // Drop var operations
+                if (op instanceof CoreOp.VarAccessOp.VarLoadOp vl) {
+                    // Replace result of load
+                    Object loadValue = loadValues.get(vl);
+                    CopyContext cc = block.context();
+                    Value v = loadValue instanceof VarOpBlockArgument vba
+                            ? joinBlockArguments.get(vba.b()).get(vba.vop())
+                            : cc.getValue((Value) loadValue);
+                    cc.mapValue(op.result(), v);
+                }
+            } else if (op instanceof Op.Terminating) {
+                for (Block.Reference s : op.successors()) {
+                    List<Object> joinValues = joinSuccessorValues.get(s);
+                    // Successor has join values
+                    if (joinValues != null) {
+                        CopyContext cc = block.context();
+
+                        // Lazily append target block arguments
+                        joinBlockArguments.computeIfAbsent(s.targetBlock(), b -> {
+                            Block.Builder bb = cc.getBlock(b);
+                            return joinPoints.get(b).stream().collect(Collectors.toMap(
+                                    varOp -> varOp,
+                                    varOp -> bb.parameter(varOp.varType())));
+                        });
+
+                        // Append successor arguments
+                        List<Value> values = new ArrayList<>();
+                        for (Object o : joinValues) {
+                            Value v = o instanceof VarOpBlockArgument vba
+                                    ? joinBlockArguments.get(vba.b()).get(vba.vop())
+                                    : cc.getValue((Value) o);
+                            values.add(v);
+                        }
+
+                        // Map successor with append arguments
+                        List<Value> toArgs = cc.getValues(s.arguments());
+                        toArgs.addAll(values);
+                        Block.Reference toS = cc.getBlock(s.targetBlock()).successor(toArgs);
+                        cc.mapSuccessor(s, toS);
+                    }
+                }
+
+                block.apply(op);
+            } else {
+                block.apply(op);
+            }
+
+            return block;
+        });
+        return liop;
+    }
+
+    record VarOpBlockArgument(Block b, CoreOp.VarOp vop) {
+    }
+
+    // @@@ Check for var uses in exception regions
+    //     A variable cannot be converted to SAA form if the variable is stored
+    //     to in an exception region and accessed from an associated catch region
+
+    static void variableToValue(Body body,
+                                Map<Block, Set<CoreOp.VarOp>> joinPoints,
+                                Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues,
+                                Map<Block.Reference, List<Object>> joinSuccessorValues) {
+        Map<CoreOp.VarOp, Deque<Object>> variableStack = new HashMap<>();
+        Node top = buildDomTree(body.entryBlock(), body.immediateDominators());
+        variableToValue(top, variableStack, joinPoints, loadValues, joinSuccessorValues);
+    }
+
+    /**
+     * Replaces usages of a variable with the corresponding value, from a given block node in the dominator tree.
+     * <p>
+     * The result of a {@code VarLoadOp} for variable, {@code V} say the result of a {@code VarOp} operation,
+     * is replaced with the value passed as an operand to the immediately dominating {@code VarStoreOp} that operates
+     * on {@code V}, or a block argument representing the equivalent of a phi-value of {@code V}.
+     * After which, any related {@code VarOp}, {@code VarLoadOp}, or {@code VarStoreOp} operations are removed.
+     *
+     * @param n             the node in the dominator tree
+     * @param variableStack the variable stack
+     * @param joinPoints    the join points
+     * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al.
+     * Section 5.2 and Figure 12.
+     */
+    static void variableToValue(Node n,
+                                Map<CoreOp.VarOp, Deque<Object>> variableStack,
+                                Map<Block, Set<CoreOp.VarOp>> joinPoints,
+                                Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues,
+                                Map<Block.Reference, List<Object>> joinSuccessorValues) {
+        int size = n.b().ops().size();
+
+        // Check if V is associated with block argument (phi)
+        // Push argument onto V's stack
+        {
+            Set<CoreOp.VarOp> varOps = joinPoints.get(n.b());
+            if (varOps != null) {
+                varOps.forEach(v -> {
+                    assert variableStack.get(v) != null;
+                    variableStack.get(v).push(new VarOpBlockArgument(n.b(), v));
+                });
+            }
+        }
+
+        {
+            for (int i = 0; i < size - 1; i++) {
+                Op op = n.b().ops().get(i);
+
+                if (op instanceof CoreOp.VarOp varOp) {
+                    // Initial value assigned to variable
+                    Value current = op.operands().get(0);
+                    variableStack.computeIfAbsent(varOp, _k -> new ArrayDeque<>())
+                            .push(current);
+                } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
+                    // Value assigned to variable
+                    Value current = op.operands().get(1);
+                    variableStack.computeIfAbsent(storeOp.varOp(), _k -> new ArrayDeque<>())
+                            .push(current);
+                } else if (op instanceof CoreOp.VarAccessOp.VarLoadOp loadOp) {
+                    Object to = variableStack.get(loadOp.varOp()).peek();
+                    loadValues.put(loadOp, to);
+                } else if (op instanceof Op.Nested) {
+                    // Traverse descendant variable loads for variables
+                    // declared in the block's parent body
+                    op.traverse(null, (o, codeElement) -> {
+                        if (o instanceof CoreOp.VarAccessOp.VarLoadOp loadOp &&
+                                loadOp.varOp().ancestorBody() == op.ancestorBody()) {
+                            Object to = variableStack.get(loadOp.varOp()).peek();
+                            loadValues.put(loadOp, to);
+                        }
+                        return null;
+                    });
+                }
+            }
+
+            // Add successor args for joint points
+            for (Block.Reference succ : n.b().successors()) {
+                Set<CoreOp.VarOp> varOps = joinPoints.get(succ.targetBlock());
+                if (varOps != null) {
+                    List<Object> joinValues = varOps.stream()
+                            .map(vop -> variableStack.get(vop).peek()).toList();
+                    joinSuccessorValues.put(succ, joinValues);
+                }
+            }
+
+            // The result of a VarOp, a variable value, can only be used in VarStoreOp and VarLoadOp
+            // therefore there is no need to check existing successor arguments
+        }
+
+        // Traverse children of dom tree
+        for (Node y : n.children()) {
+            variableToValue(y, variableStack, joinPoints, loadValues, joinSuccessorValues);
+        }
+
+        // Pop off values for variables
+        {
+            Set<CoreOp.VarOp> varOps = joinPoints.get(n.b());
+            if (varOps != null) {
+                varOps.forEach(v -> {
+                    variableStack.get(v).pop();
+                });
+            }
+
+            for (int i = 0; i < size - 1; i++) {
+                Op op = n.b().ops().get(i);
+
+                if (op instanceof CoreOp.VarOp varOp) {
+                    variableStack.get(varOp).pop();
+                } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
+                    variableStack.get(storeOp.varOp()).pop();
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds the join points of a body.
+     * <p>
+     * A join point is a block that is in the dominance frontier of one or more predecessors, that make one or more
+     * stores to variables (using the {@code VarStoreOp} operation on the result of a {@code VarOp} operation).
+     * The join point contains the set variables ({@code VarOp} operations) that are stored to.
+     * <p>
+     * A variable of a joint point indicates that a block argument may need to be added to the join point's block
+     * when converting variables to SSA form. Different values of a variable may occur at different control flow
+     * paths at the join point. The block argument represents the convergence of multiple values for the same
+     * variable, where a predecessor assigns to the block argument.
+     * (Block arguments are equivalent to phi-values, or phi-nodes, used in other representations.)
+     *
+     * @param body the body.
+     * @param joinPoints the returned join points.
+     * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al.
+     * Section 5.1 and Figure 11.
+     */
+    public static void findJoinPoints(Body body, Map<Block, Set<CoreOp.VarOp>> joinPoints) {
+        Map<Block, Set<Block>> df = body.dominanceFrontier();
+        Map<CoreOp.VarOp, Set<Block>> a = findVarStores(body);
+
+        int iterCount = 0;
+        int[] hasAlready = new int[body.blocks().size()];
+        int[] work = new int[body.blocks().size()];
+
+        Deque<Block> w = new ArrayDeque<>();
+
+        for (CoreOp.VarOp v : a.keySet()) {
+            iterCount++;
+
+            for (Block x : a.get(v)) {
+                work[x.index()] = iterCount;
+                w.push(x);
+            }
+
+            while (!w.isEmpty()) {
+                Block x = w.pop();
+
+                for (Block y : df.getOrDefault(x, Set.of())) {
+                    if (hasAlready[y.index()] < iterCount) {
+                        // Only add to the join points if y is dominated by the var's block
+                        if (y.isDominatedBy(v.parentBlock())) {
+                            joinPoints.computeIfAbsent(y, _k -> new LinkedHashSet<>()).add(v);
+                        }
+                        hasAlready[y.index()] = iterCount;
+
+                        if (work[y.index()] < iterCount) {
+                            work[y.index()] = iterCount;
+                            w.push(y);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Returns map of variable to blocks that contain stores to the variables declared in the body
+    // Throws ISE if a descendant store operation is encountered
+    // @@@ Compute map for whole tree, then traverse keys with filter
+    static Map<CoreOp.VarOp, Set<Block>> findVarStores(Body r) {
+        return r.traverse(new LinkedHashMap<>(), CodeElement.opVisitor((stores, op) -> {
+            if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
+                if (storeOp.varOp().ancestorBody() != storeOp.ancestorBody()) {
+                    throw new IllegalStateException("Descendant variable store operation");
+                }
+                if (storeOp.varOp().ancestorBody() == r) {
+                    stores.computeIfAbsent(storeOp.varOp(), _v -> new LinkedHashSet<>()).add(storeOp.parentBlock());
+                }
+            }
+            return stores;
+        }));
+    }
+
+    record Node(Block b, Set<Node> children) {
+    }
+
+    static Node buildDomTree(Block entryBlock, Map<Block, Block> idoms) {
+        Map<Block, Node> tree = new HashMap<>();
+        for (Map.Entry<Block, Block> e : idoms.entrySet()) {
+            Block id = e.getValue();
+            Block b = e.getKey();
+
+            Node parent = tree.computeIfAbsent(id, _k -> new Node(_k, new HashSet<>()));
+            if (b == entryBlock) {
+                continue;
+            }
+
+            Node child = tree.computeIfAbsent(b, _k -> new Node(_k, new HashSet<>()));
+            parent.children.add(child);
+        }
+        return tree.get(entryBlock);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/analysis/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/analysis/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/analysis/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for analysing code models.
+ */
+package java.lang.reflect.code.analysis;
diff a/src/java.base/share/classes/java/lang/reflect/code/bytecode/BranchCompactor.java b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BranchCompactor.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BranchCompactor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.reflect.code.bytecode;
+
+import java.lang.classfile.CodeBuilder;
+import java.lang.classfile.CodeElement;
+import java.lang.classfile.CodeTransform;
+import java.lang.classfile.PseudoInstruction;
+import java.lang.classfile.instruction.BranchInstruction;
+import java.lang.classfile.instruction.LabelTarget;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * BranchCompactor is a CodeTransform skipping redundant branches to immediate targets.
+ */
+public final class BranchCompactor implements CodeTransform {
+
+    private BranchInstruction branch;
+    private final List<PseudoInstruction> buffer = new ArrayList<>();
+
+    public BranchCompactor() {
+    }
+
+    @Override
+    public void accept(CodeBuilder cob, CodeElement coe) {
+        if (branch == null) {
+            if (coe instanceof BranchInstruction bi && bi.opcode().isUnconditionalBranch()) {
+                //unconditional branch is stored
+                branch = bi;
+            } else {
+                //all other elements are passed
+                cob.with(coe);
+            }
+        } else {
+            switch (coe) {
+                case LabelTarget lt when branch.target() == lt.label() -> {
+                    //skip branch to immediate target
+                    branch = null;
+                    //flush the buffer
+                    atEnd(cob);
+                    //pass the target
+                    cob.with(lt);
+                }
+                case PseudoInstruction pi -> {
+                    //buffer pseudo instructions
+                    buffer.add(pi);
+                }
+                default -> {
+                    //any other instruction flushes the branch and buffer
+                    atEnd(cob);
+                    //replay the code element
+                    accept(cob, coe);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void atEnd(CodeBuilder cob) {
+        if (branch != null) {
+            //flush the branch
+            cob.with(branch);
+            branch = null;
+        }
+        //flush the buffer
+        buffer.forEach(cob::with);
+        buffer.clear();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeGenerator.java b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeGenerator.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeGenerator.java
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.bytecode;
+
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.CodeBuilder;
+import java.lang.classfile.Label;
+import java.lang.constant.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.CoreOp.*;
+
+import java.lang.classfile.ClassBuilder;
+import java.lang.classfile.Opcode;
+import java.lang.classfile.TypeKind;
+import java.lang.classfile.attribute.ConstantValueAttribute;
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Op;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.analysis.Liveness;
+import java.lang.reflect.code.type.ArrayType;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.VarType;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.lang.constant.ConstantDescs.*;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.code.Quotable;
+import java.util.stream.Stream;
+
+/**
+ * Transformer of code models to bytecode.
+ */
+public final class BytecodeGenerator {
+
+    private static final DirectMethodHandleDesc DMHD_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
+            LambdaMetafactory.class.describeConstable().orElseThrow(),
+            "metafactory",
+            CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
+
+    private static final DirectMethodHandleDesc DMHD_LAMBDA_ALT_METAFACTORY = ofCallsiteBootstrap(
+            LambdaMetafactory.class.describeConstable().orElseThrow(),
+            "altMetafactory",
+            CD_CallSite, CD_Object.arrayType());
+
+    /**
+     * Transforms the invokable operation to bytecode encapsulated in a method of hidden class and exposed
+     * for invocation via a method handle.
+     *
+     * @param l the lookup
+     * @param iop the invokable operation to transform to bytecode
+     * @return the invoking method handle
+     * @param <O> the type of the invokable operation
+     */
+    public static <O extends Op & Op.Invokable> MethodHandle generate(MethodHandles.Lookup l, O iop) {
+        String name = iop instanceof FuncOp fop ? fop.funcName() : "m";
+        byte[] classBytes = generateClassData(l, name, iop);
+
+        MethodHandles.Lookup hcl;
+        try {
+            hcl = l.defineHiddenClass(classBytes, true);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+
+        try {
+            FunctionType ft = iop.invokableType();
+            MethodType mt = MethodRef.toNominalDescriptor(ft).resolveConstantDesc(hcl);
+            return hcl.findStatic(hcl.lookupClass(), name, mt);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Transforms the function operation to bytecode encapsulated in a method of a class file.
+     * <p>
+     * The name of the method is the function operation's {@link FuncOp#funcName() function name}.
+     *
+     * @param lookup the lookup
+     * @param fop the function operation to transform to bytecode
+     * @return the class file bytes
+     */
+    public static byte[] generateClassData(MethodHandles.Lookup lookup, FuncOp fop) {
+        return generateClassData(lookup, fop.funcName(), fop);
+    }
+
+    /**
+     * Transforms the invokable operation to bytecode encapsulated in a method of a class file.
+     *
+     * @param lookup the lookup
+     * @param name the name to use for the method of the class file
+     * @param iop the invokable operation to transform to bytecode
+     * @return the class file bytes
+     * @param <O> the type of the invokable operation
+     */
+    public static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
+                                                                         String name,
+                                                                         O iop) {
+        if (!iop.capturedValues().isEmpty()) {
+            throw new UnsupportedOperationException("Operation captures values");
+        }
+
+        String packageName = lookup.lookupClass().getPackageName();
+        ClassDesc className = ClassDesc.of(packageName.isEmpty()
+                ? name
+                : packageName + "." + name);
+        byte[] classBytes = ClassFile.of().build(className, clb -> {
+            List<LambdaOp> lambdaSink = new ArrayList<>();
+            BitSet quotable = new BitSet();
+            generateMethod(lookup, className, name, iop, clb, lambdaSink, quotable);
+            for (int i = 0; i < lambdaSink.size(); i++) {
+                LambdaOp lop = lambdaSink.get(i);
+                if (quotable.get(i)) {
+                    clb.withField("lambda$" + i + "$op", CD_String, fb -> fb
+                            .withFlags(ClassFile.ACC_STATIC)
+                            .with(ConstantValueAttribute.of(quote(lop).toText())));
+                }
+                generateMethod(lookup, className, "lambda$" + i, lop, clb, lambdaSink, quotable);
+            }
+        });
+        return classBytes;
+    }
+
+    private static <O extends Op & Op.Invokable> void generateMethod(MethodHandles.Lookup lookup,
+                                                                     ClassDesc className,
+                                                                     String methodName,
+                                                                     O iop,
+                                                                     ClassBuilder clb,
+                                                                     List<LambdaOp> lambdaSink,
+                                                                     BitSet quotable) {
+
+        List<Value> capturedValues = iop instanceof LambdaOp lop ? lop.capturedValues() : List.of();
+        MethodTypeDesc mtd = MethodRef.toNominalDescriptor(
+                iop.invokableType()).insertParameterTypes(0, capturedValues.stream()
+                        .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new));
+        clb.withMethodBody(methodName, mtd, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
+                cb -> cb.transforming(new BranchCompactor(), cob ->
+                    new BytecodeGenerator(lookup, className, capturedValues, new Liveness(iop),
+                                          iop.body().blocks(), cob, lambdaSink, quotable).generate()));
+    }
+
+    private record Slot(int slot, TypeKind typeKind) {}
+    private record ExceptionRegionWithBlocks(CoreOp.ExceptionRegionEnter ere, BitSet blocks) {}
+
+    private final MethodHandles.Lookup lookup;
+    private final ClassDesc className;
+    private final List<Value> capturedValues;
+    private final List<Block> blocks;
+    private final CodeBuilder cob;
+    private final Label[] blockLabels;
+    private final List<ExceptionRegionWithBlocks> allExceptionRegions;
+    private final BitSet[] blocksRegionStack;
+    private final BitSet blocksToVisit, catchingBlocks;
+    private final Map<Value, Slot> slots;
+    private final List<LambdaOp> lambdaSink;
+    private final BitSet quotable;
+    private Op.Result oprOnStack;
+
+    private BytecodeGenerator(MethodHandles.Lookup lookup,
+                              ClassDesc className,
+                              List<Value> capturedValues,
+                              Liveness liveness,
+                              List<Block> blocks,
+                              CodeBuilder cob,
+                              List<LambdaOp> lambdaSink,
+                              BitSet quotable) {
+        this.lookup = lookup;
+        this.className = className;
+        this.capturedValues = capturedValues;
+        this.blocks = blocks;
+        this.cob = cob;
+        this.blockLabels = new Label[blocks.size()];
+        this.allExceptionRegions = new ArrayList<>();
+        this.blocksRegionStack = new BitSet[blocks.size()];
+        this.blocksToVisit = new BitSet(blocks.size());
+        this.catchingBlocks = new BitSet();
+        this.slots = new HashMap<>();
+        this.lambdaSink = lambdaSink;
+        this.quotable = quotable;
+    }
+
+    private void setExceptionRegionStack(Block.Reference target, BitSet activeRegionStack) {
+        setExceptionRegionStack(target.targetBlock().index(), activeRegionStack);
+    }
+
+    private void setExceptionRegionStack(int blockIndex, BitSet activeRegionStack) {
+        if (blocksRegionStack[blockIndex] == null) {
+            blocksToVisit.set(blockIndex);
+            blocksRegionStack[blockIndex] = activeRegionStack;
+            activeRegionStack.stream().forEach(r -> allExceptionRegions.get(r).blocks.set(blockIndex));
+        }
+    }
+
+    private Label getLabel(Block.Reference target) {
+        return getLabel(target.targetBlock().index());
+    }
+
+    private Label getLabel(int blockIndex) {
+        Label l = blockLabels[blockIndex];
+        if (l == null) {
+            blockLabels[blockIndex] = l = cob.newLabel();
+        }
+        return l;
+    }
+
+    private Slot allocateSlot(Value v) {
+        return slots.computeIfAbsent(v, _ -> {
+            TypeKind tk = toTypeKind(v.type());
+            return new Slot(cob.allocateLocal(tk), tk);
+        });
+    }
+
+    private void storeIfUsed(Value v) {
+        if (!v.uses().isEmpty()) {
+            Slot slot = allocateSlot(v);
+            cob.storeInstruction(slot.typeKind(), slot.slot());
+        } else {
+            // Only pop results from stack if the value has no further use (no valid slot)
+            switch (toTypeKind(v.type()).slotSize()) {
+                case 1 -> cob.pop();
+                case 2 -> cob.pop2();
+            }
+        }
+    }
+
+    private Slot load(Value v) {
+        if (v instanceof Op.Result or &&
+                or.op() instanceof CoreOp.ConstantOp constantOp &&
+                !constantOp.resultType().equals(JavaType.J_L_CLASS)) {
+            cob.constantInstruction(((Constable)constantOp.value()).describeConstable().orElseThrow());
+            return null;
+        } else {
+            Slot slot = slots.get(v);
+            cob.loadInstruction(slot.typeKind(), slot.slot());
+            return slot;
+        }
+    }
+
+    private void processFirstOperand(Op op) {
+        processOperand(op.operands().getFirst());;
+    }
+
+    private void processOperand(Value operand) {
+        if (oprOnStack == null) {
+            load(operand);
+        } else {
+            assert oprOnStack == operand;
+            oprOnStack = null;
+        }
+    }
+
+    private void processOperands(Op op) {
+        processOperands(op.operands());
+    }
+
+    private void processOperands(List<Value> operands) {
+        if (oprOnStack == null) {
+            operands.forEach(this::load);
+        } else {
+            assert !operands.isEmpty() && oprOnStack == operands.getFirst();
+            oprOnStack = null;
+            for (int i = 1; i < operands.size(); i++) {
+                load(operands.get(i));
+            }
+        }
+    }
+
+    // Some of the operations can be deferred
+    private static boolean canDefer(Op op) {
+        return switch (op) {
+            case ConstantOp cop -> canDefer(cop);
+            case VarOp vop -> canDefer(vop);
+            case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
+            default -> false;
+        };
+    }
+
+    // Constant can be deferred, except for loading of a class constant, which  may throw an exception
+    private static boolean canDefer(ConstantOp op) {
+        return !op.resultType().equals(JavaType.J_L_CLASS);
+    }
+
+    // Var with a single-use block parameter operand can be deferred
+    private static boolean canDefer(VarOp op) {
+        return op.operands().getFirst() instanceof Block.Parameter bp && bp.uses().size() == 1;
+    }
+
+    // Var load can be deferred when not used as immediate operand
+    private static boolean canDefer(VarAccessOp.VarLoadOp op) {
+        return !isNextUse(op.result());
+    }
+
+    // This method narrows the first operand inconveniences of some operations
+    private static boolean isFirstOperand(Op nextOp, Op.Result opr) {
+        return switch (nextOp) {
+            // When there is no next operation
+            case null -> false;
+            // New object cannot use first operand from stack, new array fall through to the default
+            case NewOp op when !(op.constructorType().returnType() instanceof ArrayType) ->
+                false;
+            // For lambda the effective operands are captured values
+            case LambdaOp op ->
+                !op.capturedValues().isEmpty() && op.capturedValues().getFirst() == opr;
+            // Conditional branch may delegate to its binary test operation
+            case ConditionalBranchOp op when getConditionForCondBrOp(op) instanceof CoreOp.BinaryTestOp bto ->
+                isFirstOperand(bto, opr);
+            // Var store effective first operand is not the first one
+            case VarAccessOp.VarStoreOp op ->
+                op.operands().get(1) == opr;
+            // regular check of the first operand
+            default ->
+                !nextOp.operands().isEmpty() && nextOp.operands().getFirst() == opr;
+        };
+    }
+
+    // Determines if the operation result is immediatelly used by the next operation and so can stay on stack
+    private static boolean isNextUse(Op.Result opr) {
+        // Pass over deferred operations
+        Op nextOp = opr.op();
+        do {
+            nextOp = opr.declaringBlock().nextOp(nextOp);
+        } while (canDefer(nextOp));
+        return isFirstOperand(nextOp, opr);
+    }
+
+    private static boolean isConditionForCondBrOp(CoreOp.BinaryTestOp op) {
+        // Result of op has one use as the operand of a CondBrOp op,
+        // and both ops are in the same block
+
+        Set<Op.Result> uses = op.result().uses();
+        if (uses.size() != 1) {
+            return false;
+        }
+        Op.Result use = uses.iterator().next();
+
+        if (use.declaringBlock() != op.parentBlock()) {
+            return false;
+        }
+
+        // Check if used in successor
+        for (Block.Reference s : use.op().successors()) {
+            if (s.arguments().contains(op.result())) {
+                return false;
+            }
+        }
+
+        return use.op() instanceof CoreOp.ConditionalBranchOp;
+    }
+
+    private static ClassDesc toClassDesc(TypeElement t) {
+        return switch (t) {
+            case VarType vt -> toClassDesc(vt.valueType());
+            case JavaType jt -> jt.toNominalDescriptor();
+            default ->
+                throw new IllegalArgumentException("Bad type: " + t);
+        };
+    }
+
+    private static TypeKind toTypeKind(TypeElement t) {
+        return switch (t) {
+            case VarType vt -> toTypeKind(vt.valueType());
+            case JavaType jt -> {
+                TypeElement bt = jt.toBasicType();
+                if (bt.equals(JavaType.VOID)) {
+                    yield TypeKind.VoidType;
+                } else if (bt.equals(JavaType.INT)) {
+                    yield TypeKind.IntType;
+                } else if (bt.equals(JavaType.J_L_OBJECT)) {
+                    yield TypeKind.ReferenceType;
+                } else if (bt.equals(JavaType.LONG)) {
+                    yield TypeKind.LongType;
+                } else if (bt.equals(JavaType.DOUBLE)) {
+                    yield TypeKind.DoubleType;
+                } else if (bt.equals(JavaType.BOOLEAN)) {
+                    yield TypeKind.BooleanType;
+                } else if (bt.equals(JavaType.BYTE)) {
+                    yield TypeKind.ByteType;
+                } else if (bt.equals(JavaType.CHAR)) {
+                    yield TypeKind.CharType;
+                } else if (bt.equals(JavaType.FLOAT)) {
+                    yield TypeKind.FloatType;
+                } else if (bt.equals(JavaType.SHORT)) {
+                    yield TypeKind.ShortType;
+                } else {
+                    throw new IllegalArgumentException("Bad type: " + t);
+                }
+            }
+            default ->
+                throw new IllegalArgumentException("Bad type: " + t);
+        };
+    }
+
+    private void generate() {
+        // Compute exception region membership
+        setExceptionRegionStack(0, new BitSet());
+        int blockIndex;
+        while ((blockIndex = blocksToVisit.nextSetBit(0)) >= 0) {
+            blocksToVisit.clear(blockIndex);
+            BitSet activeRegionStack = blocksRegionStack[blockIndex];
+            Block b = blocks.get(blockIndex);
+            Op top = b.terminatingOp();
+            switch (top) {
+                case CoreOp.BranchOp bop ->
+                    setExceptionRegionStack(bop.branch(), activeRegionStack);
+                case CoreOp.ConditionalBranchOp cop -> {
+                    setExceptionRegionStack(cop.falseBranch(), activeRegionStack);
+                    setExceptionRegionStack(cop.trueBranch(), activeRegionStack);
+                }
+                case CoreOp.ExceptionRegionEnter er -> {
+                    for (Block.Reference catchBlock : er.catchBlocks().reversed()) {
+                        catchingBlocks.set(catchBlock.targetBlock().index());
+                        setExceptionRegionStack(catchBlock, activeRegionStack);
+                    }
+                    activeRegionStack = (BitSet)activeRegionStack.clone();
+                    activeRegionStack.set(allExceptionRegions.size());
+                    ExceptionRegionWithBlocks newNode = new ExceptionRegionWithBlocks(er, new BitSet());
+                    allExceptionRegions.add(newNode);
+                    setExceptionRegionStack(er.start(), activeRegionStack);
+                }
+                case CoreOp.ExceptionRegionExit er -> {
+                    activeRegionStack = (BitSet)activeRegionStack.clone();
+                    activeRegionStack.clear(activeRegionStack.length() - 1);
+                    setExceptionRegionStack(er.end(), activeRegionStack);
+                }
+                default -> {
+                }
+            }
+        }
+
+        // Declare the exception regions
+        for (ExceptionRegionWithBlocks erNode : allExceptionRegions.reversed()) {
+            int start  = erNode.blocks.nextSetBit(0);
+            while (start >= 0) {
+                int end = erNode.blocks.nextClearBit(start);
+                Label startLabel = getLabel(start);
+                Label endLabel = getLabel(end);
+                for (Block.Reference cbr : erNode.ere.catchBlocks()) {
+                    List<Block.Parameter> params = cbr.targetBlock().parameters();
+                    if (!params.isEmpty()) {
+                        JavaType jt = (JavaType) params.get(0).type();
+                        cob.exceptionCatch(startLabel, endLabel, getLabel(cbr), jt.toNominalDescriptor());
+                    } else {
+                        cob.exceptionCatchAll(startLabel, endLabel, getLabel(cbr));
+                    }
+                }
+                start = erNode.blocks.nextSetBit(end);
+            }
+        }
+
+        // Process blocks in topological order
+        // A jump instruction assumes the false successor block is
+        // immediately after, in sequence, to the predecessor
+        // since the jump instructions branch on a true condition
+        // Conditions are inverted when lowered to bytecode
+        for (Block b : blocks) {
+            // Ignore any non-entry blocks that have no predecessors
+            if (!b.isEntryBlock() && b.predecessors().isEmpty()) {
+                continue;
+            }
+
+            Label blockLabel = getLabel(b.index());
+            cob.labelBinding(blockLabel);
+
+            // If b is the entry block then all its parameters conservatively require slots
+            // Some unused parameters might be declared before others that are used
+            if (b.isEntryBlock()) {
+                List<Block.Parameter> parameters = b.parameters();
+                int i = 0;
+                // Captured values prepend parameters in lambda impl methods
+                for (Value cv : capturedValues) {
+                    slots.put(cv, new Slot(cob.parameterSlot(i++), toTypeKind(cv.type())));
+                }
+                for (Block.Parameter bp : parameters) {
+                    slots.put(bp, new Slot(cob.parameterSlot(i++), toTypeKind(bp.type())));
+                }
+            }
+
+            // If b is a catch block then the exception argument will be represented on the stack
+            if (catchingBlocks.get(b.index())) {
+                // Retain block argument for exception table generation
+                storeIfUsed(b.parameters().get(0));
+            }
+
+            List<Op> ops = b.ops();
+            oprOnStack = null;
+            for (int i = 0; i < ops.size() - 1; i++) {
+                final Op o = ops.get(i);
+                final TypeElement oprType = o.resultType();
+                final TypeKind rvt = toTypeKind(oprType);
+                switch (o) {
+                    case ConstantOp op -> {
+                        if (!canDefer(op)) {
+                            // Constant can be deferred, except for a class constant, which  may throw an exception
+                            cob.ldc(((JavaType)op.value()).toNominalDescriptor());
+                            push(op.result());
+                        }
+                    }
+                    case VarOp op -> {
+                        //     %1 : Var<int> = var %0 @"i";
+                        if (canDefer(op)) {
+                            // Var with a single-use block parameter operand can be deferred
+                            slots.put(op.result(), slots.get(op.operands().getFirst()));
+                        } else {
+                            processOperand(op.operands().getFirst());
+                            allocateSlot(op.result());
+                            push(op.result());
+                        }
+                    }
+                    case VarAccessOp.VarLoadOp op -> {
+                        if (canDefer(op)) {
+                            // Var load can be deferred when not used as immediate operand
+                            slots.computeIfAbsent(op.result(), r -> slots.get(op.operands().getFirst()));
+                        } else {
+                            processFirstOperand(op);
+                            push(op.result());
+                        }
+                    }
+                    case VarAccessOp.VarStoreOp op -> {
+                        processOperand(op.operands().get(1));
+                        storeIfUsed(op.operands().get(0));
+                    }
+                    case ConvOp op -> {
+                        Value first = op.operands().getFirst();
+                        processOperand(first);
+                        TypeKind tk = toTypeKind(first.type());
+                        if (tk != rvt) cob.convertInstruction(tk, rvt);
+                        push(op.result());
+                    }
+                    case NegOp op -> {
+                        processFirstOperand(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::neg(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ineg();
+                            case LongType -> cob.lneg();
+                            case FloatType -> cob.fneg();
+                            case DoubleType -> cob.dneg();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case NotOp op -> {
+                        processFirstOperand(op);
+                        cob.ifThenElse(CodeBuilder::iconst_0, CodeBuilder::iconst_1);
+                        push(op.result());
+                    }
+                    case AddOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::add(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.iadd();
+                            case LongType -> cob.ladd();
+                            case FloatType -> cob.fadd();
+                            case DoubleType -> cob.dadd();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case SubOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::sub(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.isub();
+                            case LongType -> cob.lsub();
+                            case FloatType -> cob.fsub();
+                            case DoubleType -> cob.dsub();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case MulOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::mul(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.imul();
+                            case LongType -> cob.lmul();
+                            case FloatType -> cob.fmul();
+                            case DoubleType -> cob.dmul();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case DivOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::div(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.idiv();
+                            case LongType -> cob.ldiv();
+                            case FloatType -> cob.fdiv();
+                            case DoubleType -> cob.ddiv();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case ModOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::rem(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.irem();
+                            case LongType -> cob.lrem();
+                            case FloatType -> cob.frem();
+                            case DoubleType -> cob.drem();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case AndOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::and(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.iand();
+                            case LongType -> cob.land();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case OrOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::or(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ior();
+                            case LongType -> cob.lor();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case XorOp op -> {
+                        processOperands(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::xor(TypeKind)
+                            case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ixor();
+                            case LongType -> cob.lxor();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case LshlOp op -> {
+                        processOperands(op);
+                        adjustRightTypeToInt(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::shl(TypeKind)
+                            case IntType -> cob.ishl();
+                            case LongType -> cob.lshl();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case AshrOp op -> {
+                        processOperands(op);
+                        adjustRightTypeToInt(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::shr(TypeKind)
+                            case IntType, ByteType, ShortType, CharType -> cob.ishr();
+                            case LongType -> cob.lshr();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case LshrOp op -> {
+                        processOperands(op);
+                        adjustRightTypeToInt(op);
+                        switch (rvt) { //this can be moved to CodeBuilder::ushr(TypeKind)
+                            case IntType, ByteType, ShortType, CharType -> cob.iushr();
+                            case LongType -> cob.lushr();
+                            default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
+                        }
+                        push(op.result());
+                    }
+                    case ArrayAccessOp.ArrayLoadOp op -> {
+                        processOperands(op);
+                        cob.arrayLoadInstruction(rvt);
+                        push(op.result());
+                    }
+                    case ArrayAccessOp.ArrayStoreOp op -> {
+                        processOperands(op);
+                        cob.arrayStoreInstruction(toTypeKind(op.operands().get(2).type()));
+                        push(op.result());
+                    }
+                    case ArrayLengthOp op -> {
+                        processFirstOperand(op);
+                        cob.arraylength();
+                        push(op.result());
+                    }
+                    case BinaryTestOp op -> {
+                        if (!isConditionForCondBrOp(op)) {
+                            processOperands(op);
+                            cob.ifThenElse(prepareReverseCondition(op), CodeBuilder::iconst_0, CodeBuilder::iconst_1);
+                            push(op.result());
+                        }
+                        // Processing is deferred to the CondBrOp, do not process the op result
+                    }
+                    case NewOp op -> {
+                        switch (op.constructorType().returnType()) {
+                            case ArrayType at -> {
+                                processOperands(op);
+                                if (at.dimensions() == 1) {
+                                    ClassDesc ctd = at.componentType().toNominalDescriptor();
+                                    if (ctd.isPrimitive()) {
+                                        cob.newarray(TypeKind.from(ctd));
+                                    } else {
+                                        cob.anewarray(ctd);
+                                    }
+                                } else {
+                                    cob.multianewarray(at.toNominalDescriptor(), op.operands().size());
+                                }
+                            }
+                            case JavaType jt -> {
+                                cob.new_(jt.toNominalDescriptor())
+                                    .dup();
+                                processOperands(op);
+                                cob.invokespecial(
+                                        ((JavaType) op.resultType()).toNominalDescriptor(),
+                                        ConstantDescs.INIT_NAME,
+                                        MethodRef.toNominalDescriptor(op.constructorType())
+                                                 .changeReturnType(ConstantDescs.CD_void));
+                            }
+                            default ->
+                                throw new IllegalArgumentException("Invalid return type: "
+                                                                    + op.constructorType().returnType());
+                        }
+                        push(op.result());
+                    }
+                    case InvokeOp op -> {
+                        processOperands(op);
+                        // @@@ Enhance method descriptor to include how the method is to be invoked
+                        // Example result of DirectMethodHandleDesc.toString()
+                        //   INTERFACE_VIRTUAL/IntBinaryOperator::applyAsInt(IntBinaryOperator,int,int)int
+                        // This will avoid the need to reflectively operate on the descriptor
+                        // which may be insufficient in certain cases.
+                        DirectMethodHandleDesc.Kind descKind;
+                        try {
+                            descKind = resolveToMethodHandleDesc(lookup, op.invokeDescriptor()).kind();
+                        } catch (ReflectiveOperationException e) {
+                            // @@@ Approximate fallback
+                            if (op.hasReceiver()) {
+                                descKind = DirectMethodHandleDesc.Kind.VIRTUAL;
+                            } else {
+                                descKind = DirectMethodHandleDesc.Kind.STATIC;
+                            }
+                        }
+                        MethodRef md = op.invokeDescriptor();
+                        cob.invokeInstruction(
+                                switch (descKind) {
+                                    case STATIC, INTERFACE_STATIC   -> Opcode.INVOKESTATIC;
+                                    case VIRTUAL                    -> Opcode.INVOKEVIRTUAL;
+                                    case INTERFACE_VIRTUAL          -> Opcode.INVOKEINTERFACE;
+                                    case SPECIAL, INTERFACE_SPECIAL -> Opcode.INVOKESPECIAL;
+                                    default ->
+                                        throw new IllegalStateException("Bad method descriptor resolution: "
+                                                                        + op.opType() + " > " + op.invokeDescriptor());
+                                },
+                                ((JavaType) md.refType()).toNominalDescriptor(),
+                                md.name(),
+                                MethodRef.toNominalDescriptor(md.type()),
+                                switch (descKind) {
+                                    case INTERFACE_STATIC, INTERFACE_VIRTUAL, INTERFACE_SPECIAL -> true;
+                                    default -> false;
+                                });
+
+                        push(op.result());
+                    }
+                    case FieldAccessOp.FieldLoadOp op -> {
+                        processOperands(op);
+                        FieldRef fd = op.fieldDescriptor();
+                        if (op.operands().isEmpty()) {
+                            cob.getstatic(
+                                    ((JavaType) fd.refType()).toNominalDescriptor(),
+                                    fd.name(),
+                                    ((JavaType) fd.type()).toNominalDescriptor());
+                        } else {
+                            cob.getfield(
+                                    ((JavaType) fd.refType()).toNominalDescriptor(),
+                                    fd.name(),
+                                    ((JavaType) fd.type()).toNominalDescriptor());
+                        }
+                        push(op.result());
+                    }
+                    case FieldAccessOp.FieldStoreOp op -> {
+                        processOperands(op);
+                        FieldRef fd = op.fieldDescriptor();
+                        if (op.operands().size() == 1) {
+                            cob.putstatic(
+                                    ((JavaType) fd.refType()).toNominalDescriptor(),
+                                    fd.name(),
+                                    ((JavaType) fd.type()).toNominalDescriptor());
+                        } else {
+                            cob.putfield(
+                                    ((JavaType) fd.refType()).toNominalDescriptor(),
+                                    fd.name(),
+                                    ((JavaType) fd.type()).toNominalDescriptor());
+                        }
+                    }
+                    case InstanceOfOp op -> {
+                        processFirstOperand(op);
+                        cob.instanceof_(((JavaType) op.type()).toNominalDescriptor());
+                        push(op.result());
+                    }
+                    case CastOp op -> {
+                        processFirstOperand(op);
+                        cob.checkcast(((JavaType) op.type()).toNominalDescriptor());
+                        push(op.result());
+                    }
+                    case LambdaOp op -> {
+                        JavaType intfType = (JavaType)op.functionalInterface();
+                        MethodTypeDesc mtd = MethodRef.toNominalDescriptor(op.invokableType());
+                        try {
+                            Class<?> intfClass = (Class<?>)intfType.erasure().resolve(lookup);
+                            processOperands(op.capturedValues());
+                            ClassDesc[] captureTypes = op.capturedValues().stream()
+                                    .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new);
+                            int lambdaIndex = lambdaSink.size();
+                            if (Quotable.class.isAssignableFrom(intfClass)) {
+                                // @@@ double the captured values to enable LambdaMetafactory.FLAG_QUOTABLE
+                                for (Value cv : op.capturedValues()) {
+                                    load(cv);
+                                }
+                                cob.invokedynamic(DynamicCallSiteDesc.of(
+                                        DMHD_LAMBDA_ALT_METAFACTORY,
+                                        funcIntfMethodName(intfClass),
+                                        // @@@ double the descriptor parameters
+                                        MethodTypeDesc.of(intfType.toNominalDescriptor(),
+                                                          Stream.concat(Stream.of(captureTypes),
+                                                                        Stream.of(captureTypes)).toList()),
+                                        mtd,
+                                        MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
+                                                                  className,
+                                                                  "lambda$" + lambdaIndex,
+                                                                  mtd.insertParameterTypes(0, captureTypes)),
+                                        mtd,
+                                        LambdaMetafactory.FLAG_QUOTABLE,
+                                        MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.STATIC_GETTER,
+                                                                 className,
+                                                                 "lambda$" + lambdaIndex + "$op",
+                                                                 CD_String)));
+                                quotable.set(lambdaSink.size());
+                            } else {
+                                cob.invokedynamic(DynamicCallSiteDesc.of(
+                                        DMHD_LAMBDA_METAFACTORY,
+                                        funcIntfMethodName(intfClass),
+                                        MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
+                                        mtd,
+                                        MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
+                                                                  className,
+                                                                  "lambda$" + lambdaIndex,
+                                                                  mtd.insertParameterTypes(0, captureTypes)),
+                                        mtd));
+                            }
+                            lambdaSink.add(op);
+                        } catch (ReflectiveOperationException e) {
+                            throw new IllegalArgumentException(e);
+                        }
+                        push(op.result());
+                    }
+                    default ->
+                        throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
+                }
+            }
+            Op top = b.terminatingOp();
+            switch (top) {
+                case CoreOp.ReturnOp op -> {
+                    Value a = op.returnValue();
+                    if (a == null) {
+                        cob.return_();
+                    } else {
+                        processFirstOperand(op);
+                        cob.returnInstruction(toTypeKind(a.type()));
+                    }
+                }
+                case ThrowOp op -> {
+                    processFirstOperand(op);
+                    cob.athrow();
+                }
+                case BranchOp op -> {
+                    assignBlockArguments(op.branch());
+                    cob.goto_(getLabel(op.branch()));
+                }
+                case ConditionalBranchOp op -> {
+                    if (getConditionForCondBrOp(op) instanceof CoreOp.BinaryTestOp btop) {
+                        // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
+                        processOperands(btop);
+                        conditionalBranch(btop, op.trueBranch(), op.falseBranch());
+                    } else {
+                        processOperands(op);
+                        conditionalBranch(Opcode.IFEQ, op, op.trueBranch(), op.falseBranch());
+                    }
+                }
+                case ExceptionRegionEnter op -> {
+                    assignBlockArguments(op.start());
+                }
+                case ExceptionRegionExit op -> {
+                    assignBlockArguments(op.end());
+                    cob.goto_(getLabel(op.end()));
+                }
+                default ->
+                    throw new UnsupportedOperationException("Terminating operation not supported: " + top);
+            }
+        }
+    }
+
+    private boolean inBlockArgs(Op.Result res) {
+        // Check if used in successor
+        for (Block.Reference s : res.declaringBlock().successors()) {
+            if (s.arguments().contains(res)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void push(Op.Result res) {
+        assert oprOnStack == null;
+        if (res.type().equals(JavaType.VOID)) return;
+        if (isNextUse(res)) {
+            if (res.uses().size() > 1 || inBlockArgs(res)) {
+                switch (toTypeKind(res.type()).slotSize()) {
+                    case 1 -> cob.dup();
+                    case 2 -> cob.dup2();
+                }
+                storeIfUsed(res);
+            }
+            oprOnStack = res;
+        } else {
+            storeIfUsed(res);
+            oprOnStack = null;
+        }
+    }
+
+    // the rhs of any shift instruction must be int or smaller -> convert longs
+    private void adjustRightTypeToInt(Op op) {
+        TypeElement right = op.operands().getLast().type();
+        if (right.equals(JavaType.LONG)) {
+            cob.convertInstruction(toTypeKind(right), TypeKind.IntType);
+        }
+    }
+
+    private static Op getConditionForCondBrOp(CoreOp.ConditionalBranchOp op) {
+        Value p = op.predicate();
+        if (p.uses().size() != 1) {
+            return null;
+        }
+
+        if (p.declaringBlock() != op.parentBlock()) {
+            return null;
+        }
+
+        // Check if used in successor
+        for (Block.Reference s : op.successors()) {
+            if (s.arguments().contains(p)) {
+                return null;
+            }
+        }
+
+        if (p instanceof Op.Result or) {
+            return or.op();
+        } else {
+            return null;
+        }
+    }
+
+    private String funcIntfMethodName(Class<?> intfc) {
+        String uniqueName = null;
+        for (Method m : intfc.getMethods()) {
+            // ensure it's SAM interface
+            String methodName = m.getName();
+            if (Modifier.isAbstract(m.getModifiers())
+                    && (m.getReturnType() != String.class
+                        || m.getParameterCount() != 0
+                        || !methodName.equals("toString"))
+                    && (m.getReturnType() != int.class
+                        || m.getParameterCount() != 0
+                        || !methodName.equals("hashCode"))
+                    && (m.getReturnType() != boolean.class
+                        || m.getParameterCount() != 1
+                        || m.getParameterTypes()[0] != Object.class
+                        || !methodName.equals("equals"))) {
+                if (uniqueName == null) {
+                    uniqueName = methodName;
+                } else if (!uniqueName.equals(methodName)) {
+                    // too many abstract methods
+                    throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
+                }
+            }
+        }
+        if (uniqueName == null) {
+            throw new IllegalArgumentException("No method in: " + intfc.getName());
+        }
+        return uniqueName;
+    }
+
+    private void conditionalBranch(BinaryTestOp op, Block.Reference trueBlock, Block.Reference falseBlock) {
+        conditionalBranch(prepareReverseCondition(op), op, trueBlock, falseBlock);
+    }
+
+    private void conditionalBranch(Opcode reverseOpcode, Op op, Block.Reference trueBlock, Block.Reference falseBlock) {
+        if (!needToAssignBlockArguments(falseBlock)) {
+            cob.branchInstruction(reverseOpcode, getLabel(falseBlock));
+        } else {
+            cob.ifThen(reverseOpcode,
+                bb -> {
+                    assignBlockArguments(falseBlock);
+                    bb.goto_(getLabel(falseBlock));
+                });
+        }
+        assignBlockArguments(trueBlock);
+        cob.goto_(getLabel(trueBlock));
+    }
+
+    private Opcode prepareReverseCondition(BinaryTestOp op) {
+        return switch (toTypeKind(op.operands().get(0).type())) {
+            case IntType ->
+                switch (op) {
+                    case EqOp _ -> Opcode.IF_ICMPNE;
+                    case NeqOp _ -> Opcode.IF_ICMPEQ;
+                    case GtOp _ -> Opcode.IF_ICMPLE;
+                    case GeOp _ -> Opcode.IF_ICMPLT;
+                    case LtOp _ -> Opcode.IF_ICMPGE;
+                    case LeOp _ -> Opcode.IF_ICMPGT;
+                    default ->
+                        throw new UnsupportedOperationException(op.opName() + " on int");
+                };
+            case ReferenceType ->
+                switch (op) {
+                    case EqOp _ -> Opcode.IF_ACMPNE;
+                    case NeqOp _ -> Opcode.IF_ACMPEQ;
+                    default ->
+                        throw new UnsupportedOperationException(op.opName() + " on Object");
+                };
+            case FloatType -> {
+                cob.fcmpg(); // FCMPL?
+                yield reverseIfOpcode(op);
+            }
+            case LongType -> {
+                cob.lcmp();
+                yield reverseIfOpcode(op);
+            }
+            case DoubleType -> {
+                cob.dcmpg(); //CMPL?
+                yield reverseIfOpcode(op);
+            }
+            default ->
+                throw new UnsupportedOperationException(op.opName() + " on " + op.operands().get(0).type());
+        };
+    }
+
+    private static Opcode reverseIfOpcode(BinaryTestOp op) {
+        return switch (op) {
+            case EqOp _ -> Opcode.IFNE;
+            case NeqOp _ -> Opcode.IFEQ;
+            case GtOp _ -> Opcode.IFLE;
+            case GeOp _ -> Opcode.IFLT;
+            case LtOp _ -> Opcode.IFGE;
+            case LeOp _ -> Opcode.IFGT;
+            default ->
+                throw new UnsupportedOperationException(op.opName());
+        };
+    }
+
+    private boolean needToAssignBlockArguments(Block.Reference ref) {
+        List<Value> sargs = ref.arguments();
+        List<Block.Parameter> bargs = ref.targetBlock().parameters();
+        boolean need = false;
+        for (int i = 0; i < bargs.size(); i++) {
+            Block.Parameter barg = bargs.get(i);
+            if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
+                need = true;
+                allocateSlot(barg);
+            }
+        }
+        return need;
+    }
+
+    private void assignBlockArguments(Block.Reference ref) {
+        List<Value> sargs = ref.arguments();
+        List<Block.Parameter> bargs = ref.targetBlock().parameters();
+        // First push successor arguments on the stack, then pop and assign
+        // so as not to overwrite slots that are reused slots at different argument positions
+        for (int i = 0; i < bargs.size(); i++) {
+            Block.Parameter barg = bargs.get(i);
+            Value value = sargs.get(i);
+            if (!barg.uses().isEmpty() && !barg.equals(value)) {
+                if (oprOnStack == value) {
+                    oprOnStack = null;
+                } else {
+                    load(value);
+                }
+                storeIfUsed(barg);
+            }
+        }
+    }
+
+    static DirectMethodHandleDesc resolveToMethodHandleDesc(MethodHandles.Lookup l,
+                                                            MethodRef d) throws ReflectiveOperationException {
+        MethodHandle mh = d.resolveToHandle(l);
+
+        if (mh.describeConstable().isEmpty()) {
+            throw new NoSuchMethodException();
+        }
+
+        MethodHandleDesc mhd = mh.describeConstable().get();
+        if (!(mhd instanceof DirectMethodHandleDesc dmhd)) {
+            throw new NoSuchMethodException();
+        }
+
+        return dmhd;
+    }
+
+    static CoreOp.FuncOp quote(CoreOp.LambdaOp lop) {
+        List<Value> captures = lop.capturedValues();
+
+        // Build the function type
+        List<TypeElement> params = captures.stream()
+                .map(v -> v.type() instanceof VarType vt ? vt.valueType() : v.type())
+                .toList();
+        FunctionType ft = FunctionType.functionType(CoreOp.QuotedOp.QUOTED_TYPE, params);
+
+        // Build the function that quotes the lambda
+        return CoreOp.func("q", ft).body(b -> {
+            // Create variables as needed and obtain the captured values
+            // for the copied lambda
+            List<Value> outputCaptures = new ArrayList<>();
+            for (int i = 0; i < captures.size(); i++) {
+                Value c = captures.get(i);
+                Block.Parameter p = b.parameters().get(i);
+                if (c.type() instanceof VarType _) {
+                    Value var = b.op(CoreOp.var(String.valueOf(i), p));
+                    outputCaptures.add(var);
+                } else {
+                    outputCaptures.add(p);
+                }
+            }
+
+            // Quoted the lambda expression
+            Value q = b.op(CoreOp.quoted(b.parentBody(), qb -> {
+                // Map the lambda's parent block to the quoted block
+                // We are copying lop in the context of the quoted block
+                qb.context().mapBlock(lop.parentBlock(), qb);
+                // Map the lambda's captured values
+                qb.context().mapValues(captures, outputCaptures);
+                // Return the lambda to be copied in the quoted operation
+                return lop;
+            }));
+            b.op(CoreOp._return(q));
+        });
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeLift.java b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeLift.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/bytecode/BytecodeLift.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.bytecode;
+
+import java.lang.classfile.Attributes;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.CodeElement;
+import java.lang.classfile.CodeModel;
+import java.lang.classfile.Instruction;
+import java.lang.classfile.Label;
+import java.lang.classfile.MethodModel;
+import java.lang.classfile.Opcode;
+import java.lang.classfile.TypeKind;
+import java.lang.classfile.attribute.StackMapFrameInfo;
+import java.lang.classfile.instruction.*;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.reflect.AccessFlag;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*;
+import java.lang.classfile.constantpool.ClassEntry;
+import java.lang.constant.MethodTypeDesc;
+
+
+public final class BytecodeLift {
+
+    private final Block.Builder entryBlock;
+    private final CodeModel codeModel;
+    private final Map<Label, Block.Builder> blockMap;
+    private final Map<String, Op.Result> varMap;
+    private final Deque<Value> stack;
+    private Block.Builder currentBlock;
+
+    private static String varName(int slot, TypeKind tk) {
+        return tk.typeName() + slot;
+    }
+
+    private static TypeElement toTypeElement(StackMapFrameInfo.VerificationTypeInfo vti) {
+        return switch (vti) {
+            case ITEM_INTEGER -> JavaType.INT;
+            case ITEM_FLOAT -> JavaType.FLOAT;
+            case ITEM_DOUBLE -> JavaType.DOUBLE;
+            case ITEM_LONG -> JavaType.LONG;
+            case ITEM_NULL -> JavaType.J_L_OBJECT;
+            case StackMapFrameInfo.ObjectVerificationTypeInfo ovti ->
+                    JavaType.type(ovti.classSymbol());
+            case StackMapFrameInfo.UninitializedVerificationTypeInfo _ ->
+                    JavaType.J_L_OBJECT;
+            default ->
+                throw new IllegalArgumentException("Unexpected VTI: " + vti);
+
+        };
+    }
+
+    private TypeElement toTypeElement(ClassEntry ce) {
+        return JavaType.type(ce.asSymbol());
+    }
+
+    private BytecodeLift(Block.Builder entryBlock, MethodModel methodModel) {
+        if (!methodModel.flags().has(AccessFlag.STATIC)) {
+            throw new IllegalArgumentException("Unsuported lift of non-static method: " + methodModel);
+        }
+        this.entryBlock = entryBlock;
+        this.currentBlock = entryBlock;
+        this.codeModel = methodModel.code().orElseThrow();
+        this.varMap = new HashMap<>();
+        this.stack = new ArrayDeque<>();
+        List<Block.Parameter> bps = entryBlock.parameters();
+        List<ClassDesc> mps = methodModel.methodTypeSymbol().parameterList();
+        for (int i = 0, slot = 0; i < bps.size(); i++) {
+            TypeKind tk = TypeKind.from(mps.get(i)).asLoadable();
+            varStore(slot, tk, bps.get(i));
+            slot += tk.slotSize();
+        }
+        this.blockMap = codeModel.findAttribute(Attributes.STACK_MAP_TABLE).map(sma ->
+                sma.entries().stream().collect(Collectors.toUnmodifiableMap(
+                        StackMapFrameInfo::target,
+                        smfi -> entryBlock.block(smfi.stack().stream().map(BytecodeLift::toTypeElement).toList())))).orElse(Map.of());
+    }
+
+    private void varStore(int slot, TypeKind tk, Value value) {
+        varMap.compute(varName(slot, tk), (varName, var) -> {
+            if (var == null) {
+                return op(CoreOp.var(varName, value));
+            } else {
+                op(CoreOp.varStore(var, value));
+                return var;
+            }
+        });
+    }
+
+    private Op.Result var(int slot, TypeKind tk) {
+        Op.Result r = varMap.get(varName(slot, tk));
+        if (r == null) throw new IllegalArgumentException("Undeclared variable: " + slot + "-" + tk); // @@@ these cases may need lazy var injection
+        return r;
+    }
+
+    private Op.Result op(Op op) {
+        return currentBlock.op(op);
+    }
+
+    // Lift to core dialect
+    public static CoreOp.FuncOp lift(byte[] classdata, String methodName) {
+        return lift(classdata, methodName, null);
+    }
+
+    public static CoreOp.FuncOp lift(byte[] classdata, String methodName, MethodTypeDesc methodType) {
+        return lift(ClassFile.of(
+                ClassFile.DebugElementsOption.DROP_DEBUG,
+                ClassFile.LineNumbersOption.DROP_LINE_NUMBERS).parse(classdata).methods().stream()
+                        .filter(mm -> mm.methodName().equalsString(methodName) && (methodType == null || mm.methodTypeSymbol().equals(methodType)))
+                        .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown method: " + methodName)));
+    }
+
+    public static CoreOp.FuncOp lift(MethodModel methodModel) {
+        return CoreOp.func(
+                methodModel.methodName().stringValue(),
+                MethodRef.ofNominalDescriptor(methodModel.methodTypeSymbol())).body(entryBlock ->
+                        new BytecodeLift(entryBlock, methodModel).lift());
+    }
+
+    private Block.Builder getBlock(Label l) {
+        Block.Builder bb = blockMap.get(l);
+        if (bb == null) {
+            if (currentBlock == null) {
+                throw new IllegalArgumentException("Block without an stack frame detected.");
+            } else {
+                return newBlock();
+            }
+        }
+        return bb;
+    }
+
+    private Block.Builder newBlock() {
+        return entryBlock.block(stack.stream().map(Value::type).toList());
+    }
+
+    private void moveTo(Block.Builder next) {
+        currentBlock = next;
+        // Stack is reconstructed from block parameters
+        stack.clear();
+        if (currentBlock != null) {
+            currentBlock.parameters().forEach(stack::add);
+        }
+    }
+
+    private void endOfFlow() {
+        currentBlock = null;
+        // Flow discontinued, stack cleared to be ready for the next label target
+        stack.clear();
+    }
+
+    private void lift() {
+        final Map<ExceptionCatch, Op.Result> exceptionRegionsMap = new HashMap<>();
+
+        List<CodeElement> elements = codeModel.elementList();
+        for (int i = 0; i < elements.size(); i++) {
+            switch (elements.get(i)) {
+                case ExceptionCatch _ -> {
+                    // Exception blocks are inserted by label target (below)
+                }
+                case LabelTarget lt -> {
+                    // Start of a new block
+                    Block.Builder next = getBlock(lt.label());
+                    if (currentBlock != null) {
+                        // Implicit goto next block, add explicitly
+                        // Use stack content as next block arguments
+                        op(CoreOp.branch(next.successor(List.copyOf(stack))));
+                    }
+                    moveTo(next);
+                    // Insert relevant tryStart and construct handler blocks, all in reversed order
+                    for (ExceptionCatch ec : codeModel.exceptionHandlers().reversed()) {
+                        if (lt.label() == ec.tryStart()) {
+                            Block.Builder handler = getBlock(ec.handler());
+                            // Create start block
+                            next = newBlock();
+                            Op ere = CoreOp.exceptionRegionEnter(next.successor(List.copyOf(stack)), handler.successor());
+                            op(ere);
+                            // Store ERE into map for exit
+                            exceptionRegionsMap.put(ec, ere.result());
+                            moveTo(next);
+                        }
+                    }
+                    // Insert relevant tryEnd blocks in normal order
+                    for (ExceptionCatch ec : codeModel.exceptionHandlers()) {
+                        if (lt.label() == ec.tryEnd()) {
+                            // Create exit block with parameters constructed from the stack
+                            next = newBlock();
+                            op(CoreOp.exceptionRegionExit(exceptionRegionsMap.get(ec), next.successor()));
+                            moveTo(next);
+                        }
+                    }
+                }
+                case BranchInstruction inst when inst.opcode().isUnconditionalBranch() -> {
+                    op(CoreOp.branch(getBlock(inst.target()).successor(List.copyOf(stack))));
+                    endOfFlow();
+                }
+                case BranchInstruction inst -> {
+                    // Conditional branch
+                    Value operand = stack.pop();
+                    Op cop = switch (inst.opcode()) {
+                        case IFNE -> CoreOp.eq(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFEQ -> CoreOp.neq(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFGE -> CoreOp.lt(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFLE -> CoreOp.gt(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFGT -> CoreOp.le(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFLT -> CoreOp.ge(operand, op(CoreOp.constant(JavaType.INT, 0)));
+                        case IFNULL -> CoreOp.neq(operand, op(CoreOp.constant(JavaType.J_L_OBJECT, null)));
+                        case IFNONNULL -> CoreOp.eq(operand, op(CoreOp.constant(JavaType.J_L_OBJECT, null)));
+                        case IF_ICMPNE -> CoreOp.eq(stack.pop(), operand);
+                        case IF_ICMPEQ -> CoreOp.neq(stack.pop(), operand);
+                        case IF_ICMPGE -> CoreOp.lt(stack.pop(), operand);
+                        case IF_ICMPLE -> CoreOp.gt(stack.pop(), operand);
+                        case IF_ICMPGT -> CoreOp.le(stack.pop(), operand);
+                        case IF_ICMPLT -> CoreOp.ge(stack.pop(), operand);
+                        case IF_ACMPEQ -> CoreOp.neq(stack.pop(), operand);
+                        case IF_ACMPNE -> CoreOp.eq(stack.pop(), operand);
+                        default -> throw new UnsupportedOperationException("Unsupported branch instruction: " + inst);
+                    };
+                    if (!stack.isEmpty()) {
+                        throw new UnsupportedOperationException("Operands on stack for branch not supported");
+                    }
+                    Block.Builder next = currentBlock.block();
+                    op(CoreOp.conditionalBranch(op(cop),
+                            next.successor(),
+                            getBlock(inst.target()).successor()));
+                    moveTo(next);
+                }
+    //                case LookupSwitchInstruction si -> {
+    //                    // Default label is first successor
+    //                    b.addSuccessor(blockMap.get(si.defaultTarget()));
+    //                    addSuccessors(si.cases(), blockMap, b);
+    //                }
+    //                case TableSwitchInstruction si -> {
+    //                    // Default label is first successor
+    //                    b.addSuccessor(blockMap.get(si.defaultTarget()));
+    //                    addSuccessors(si.cases(), blockMap, b);
+    //                }
+                case ReturnInstruction inst when inst.typeKind() == TypeKind.VoidType -> {
+                    op(CoreOp._return());
+                    endOfFlow();
+                }
+                case ReturnInstruction _ -> {
+                    op(CoreOp._return(stack.pop()));
+                    endOfFlow();
+                }
+                case ThrowInstruction _ -> {
+                    op(CoreOp._throw(stack.pop()));
+                    endOfFlow();
+                }
+                case LoadInstruction inst -> {
+                    stack.push(op(CoreOp.varLoad(var(inst.slot(), inst.typeKind()))));
+                }
+                case StoreInstruction inst -> {
+                    varStore(inst.slot(), inst.typeKind(), stack.pop());
+                }
+                case IncrementInstruction inst -> {
+                    varStore(inst.slot(), TypeKind.IntType, op(CoreOp.add(
+                            op(CoreOp.varLoad(var(inst.slot(), TypeKind.IntType))),
+                            op(CoreOp.constant(JavaType.INT, inst.constant())))));
+                }
+                case ConstantInstruction inst -> {
+                    stack.push(op(switch (inst.constantValue()) {
+                        case ClassDesc v -> CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(v));
+                        case Double v -> CoreOp.constant(JavaType.DOUBLE, v);
+                        case Float v -> CoreOp.constant(JavaType.FLOAT, v);
+                        case Integer v -> CoreOp.constant(JavaType.INT, v);
+                        case Long v -> CoreOp.constant(JavaType.LONG, v);
+                        case String v -> CoreOp.constant(JavaType.J_L_STRING, v);
+                        default ->
+                            // @@@ MethodType, MethodHandle, ConstantDynamic
+                            throw new IllegalArgumentException("Unsupported constant value: " + inst.constantValue());
+                    }));
+                }
+                case ConvertInstruction inst -> {
+                    stack.push(op(CoreOp.conv(switch (inst.toType()) {
+                        case ByteType -> JavaType.BYTE;
+                        case ShortType -> JavaType.SHORT;
+                        case IntType -> JavaType.INT;
+                        case FloatType -> JavaType.FLOAT;
+                        case LongType -> JavaType.LONG;
+                        case DoubleType -> JavaType.DOUBLE;
+                        case CharType -> JavaType.CHAR;
+                        case BooleanType -> JavaType.BOOLEAN;
+                        default ->
+                            throw new IllegalArgumentException("Unsupported conversion target: " + inst.toType());
+                    }, stack.pop())));
+                }
+                case OperatorInstruction inst -> {
+                    Value operand = stack.pop();
+                    stack.push(op(switch (inst.opcode()) {
+                        case IADD, LADD, FADD, DADD ->
+                                CoreOp.add(stack.pop(), operand);
+                        case ISUB, LSUB, FSUB, DSUB ->
+                                CoreOp.sub(stack.pop(), operand);
+                        case IMUL, LMUL, FMUL, DMUL ->
+                                CoreOp.mul(stack.pop(), operand);
+                        case IDIV, LDIV, FDIV, DDIV ->
+                                CoreOp.div(stack.pop(), operand);
+                        case IREM, LREM, FREM, DREM ->
+                                CoreOp.mod(stack.pop(), operand);
+                        case INEG, LNEG, FNEG, DNEG ->
+                                CoreOp.neg(operand);
+                        case ARRAYLENGTH ->
+                                CoreOp.arrayLength(operand);
+                        case IAND, LAND ->
+                                CoreOp.and(stack.pop(), operand);
+                        case IOR, LOR ->
+                                CoreOp.or(stack.pop(), operand);
+                        case IXOR, LXOR ->
+                                CoreOp.xor(stack.pop(), operand);
+                        default ->
+                            throw new IllegalArgumentException("Unsupported operator opcode: " + inst.opcode());
+                    }));
+                }
+                case FieldInstruction inst -> {
+                        FieldRef fd = FieldRef.field(
+                                JavaType.type(inst.owner().asSymbol()),
+                                inst.name().stringValue(),
+                                JavaType.type(inst.typeSymbol()));
+                        switch (inst.opcode()) {
+                            case GETFIELD ->
+                                stack.push(op(CoreOp.fieldLoad(fd, stack.pop())));
+                            case GETSTATIC ->
+                                stack.push(op(CoreOp.fieldLoad(fd)));
+                            case PUTFIELD -> {
+                                Value value = stack.pop();
+                                stack.push(op(CoreOp.fieldStore(fd, stack.pop(), value)));
+                            }
+                            case PUTSTATIC ->
+                                stack.push(op(CoreOp.fieldStore(fd, stack.pop())));
+                            default ->
+                                throw new IllegalArgumentException("Unsupported field opcode: " + inst.opcode());
+                        }
+                }
+                case ArrayStoreInstruction _ -> {
+                    Value value = stack.pop();
+                    Value index = stack.pop();
+                    op(CoreOp.arrayStoreOp(stack.pop(), index, value));
+                }
+                case ArrayLoadInstruction _ -> {
+                    Value index = stack.pop();
+                    stack.push(op(CoreOp.arrayLoadOp(stack.pop(), index)));
+                }
+                case InvokeInstruction inst -> {
+                    FunctionType mType = MethodRef.ofNominalDescriptor(inst.typeSymbol());
+                    List<Value> operands = new ArrayList<>();
+                    for (var _ : mType.parameterTypes()) {
+                        operands.add(stack.pop());
+                    }
+                    MethodRef mDesc = MethodRef.method(
+                            JavaType.type(inst.owner().asSymbol()),
+                            inst.name().stringValue(),
+                            mType);
+                    Op.Result result = switch (inst.opcode()) {
+                        case INVOKEVIRTUAL, INVOKEINTERFACE -> {
+                            operands.add(stack.pop());
+                            yield op(CoreOp.invoke(mDesc, operands.reversed()));
+                        }
+                        case INVOKESTATIC ->
+                            op(CoreOp.invoke(mDesc, operands.reversed()));
+                        case INVOKESPECIAL -> {
+                            if (inst.name().equalsString(ConstantDescs.INIT_NAME)) {
+                                yield op(CoreOp._new(
+                                        FunctionType.functionType(
+                                                mDesc.refType(),
+                                                mType.parameterTypes()),
+                                        operands.reversed()));
+                            } else {
+                                operands.add(stack.pop());
+                                yield op(CoreOp.invoke(mDesc, operands.reversed()));
+                            }
+                        }
+                        default ->
+                            throw new IllegalArgumentException("Unsupported invocation opcode: " + inst.opcode());
+                    };
+                    if (!result.type().equals(JavaType.VOID)) {
+                        stack.push(result);
+                    }
+                }
+                case NewObjectInstruction _ -> {
+                    // Skip over this and the dup to process the invoke special
+                    if (i + 2 < elements.size() - 1
+                            && elements.get(i + 1) instanceof StackInstruction dup
+                            && dup.opcode() == Opcode.DUP) {
+                        i++;
+                    } else {
+                        throw new UnsupportedOperationException("New must be followed by dup");
+                    }
+                }
+                case NewPrimitiveArrayInstruction inst -> {
+                    stack.push(op(CoreOp.newArray(
+                            switch (inst.typeKind()) {
+                                case BooleanType -> JavaType.BOOLEAN_ARRAY;
+                                case ByteType -> JavaType.BYTE_ARRAY;
+                                case CharType -> JavaType.CHAR_ARRAY;
+                                case DoubleType -> JavaType.DOUBLE_ARRAY;
+                                case FloatType -> JavaType.FLOAT_ARRAY;
+                                case IntType -> JavaType.INT_ARRAY;
+                                case LongType -> JavaType.LONG_ARRAY;
+                                case ShortType -> JavaType.SHORT_ARRAY;
+                                default ->
+                                        throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind());
+                            },
+                            stack.pop())));
+                }
+                case NewReferenceArrayInstruction inst -> {
+                    stack.push(op(CoreOp.newArray(
+                            JavaType.type(inst.componentType().asSymbol().arrayType()),
+                            stack.pop())));
+                }
+                case NewMultiArrayInstruction inst -> {
+                    stack.push(op(CoreOp._new(
+                            FunctionType.functionType(
+                                    JavaType.type(inst.arrayType().asSymbol()),
+                                    Collections.nCopies(inst.dimensions(), JavaType.INT)),
+                            IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed())));
+                }
+                case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> {
+                    stack.push(op(CoreOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop())));
+                }
+                case StackInstruction inst -> {
+                    switch (inst.opcode()) {
+                        case POP, POP2 -> stack.pop(); // @@@ check the type width
+                        case DUP, DUP2 -> stack.push(stack.peek());
+                        //@@@ implement all other stack ops
+                        default ->
+                            throw new UnsupportedOperationException("Unsupported stack instruction: " + inst);
+                    }
+                }
+                case Instruction inst ->
+                    throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name());
+                default ->
+                    throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i));
+            }
+        }
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/bytecode/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/bytecode/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/bytecode/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for generating bytecode from code models and for generating code models from bytecode.
+ */
+package java.lang.reflect.code.bytecode;
diff a/src/java.base/share/classes/java/lang/reflect/code/interpreter/Interpreter.java b/src/java.base/share/classes/java/lang/reflect/code/interpreter/Interpreter.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/interpreter/Interpreter.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.interpreter;
+
+import java.lang.invoke.*;
+import java.lang.reflect.Array;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.ArrayType;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.VarType;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toMap;
+
+public final class Interpreter {
+    private Interpreter() {
+    }
+
+    @SuppressWarnings("serial")
+    public static class InterpreterException extends RuntimeException {
+        public InterpreterException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    static InterpreterException interpreterException(Throwable cause) {
+        return new InterpreterException(cause);
+    }
+
+    record BlockContext(Block b, Map<Value, Object> values) {
+    }
+
+    static final class OpContext {
+        final Deque<BlockContext> stack = new ArrayDeque<>();
+        final Deque<ExceptionRegionRecord> erStack = new ArrayDeque<>();
+
+        Object getValue(Value v) {
+            // @@@ Only dominating values are accessible
+            BlockContext bc = findContext(v);
+            if (bc != null) {
+                return bc.values.get(v);
+            } else {
+                throw interpreterException(new IllegalArgumentException("Undefined value: " + v));
+            }
+        }
+
+        Object setValue(Value v, Object o) {
+            BlockContext bc = findContext(v);
+            if (bc != null) {
+                throw interpreterException(new IllegalArgumentException("Value already defined: " + v));
+            }
+            stack.peek().values.put(v, o);
+            return o;
+        }
+
+        BlockContext findContext(Value v) {
+            Optional<BlockContext> ob = stack.stream().filter(b -> b.values.containsKey(v)).findFirst();
+            return ob.orElse(null);
+        }
+
+        boolean contains(Block.Reference s) {
+            Block sb = s.targetBlock();
+            return stack.stream().anyMatch(bc -> bc.b.equals(sb));
+        }
+
+        void successor(Block.Reference sb) {
+            List<Object> sbValues = sb.arguments().stream().map(this::getValue).toList();
+
+            Block b = sb.targetBlock();
+            Map<Value, Object> bValues = new HashMap<>();
+            for (int i = 0; i < sbValues.size(); i++) {
+                bValues.put(b.parameters().get(i), sbValues.get(i));
+            }
+
+            if (contains(sb)) {
+                // if block is already dominating pop back up from the back branch to the block
+                // before the successor block
+                while (!stack.peek().b.equals(sb.targetBlock())) {
+                    stack.pop();
+                }
+                stack.pop();
+            }
+            stack.push(new BlockContext(b, bValues));
+        }
+
+        void successor(Block b, Map<Value, Object> bValues) {
+            stack.push(new BlockContext(b, bValues));
+        }
+
+        void popTo(BlockContext bc) {
+            while (!stack.peek().equals(bc)) {
+                stack.pop();
+            }
+        }
+
+        void pushExceptionRegion(ExceptionRegionRecord erb) {
+            erStack.push(erb);
+        }
+
+        void popExceptionRegion(CoreOp.ExceptionRegionEnter ers) {
+            if (erStack.peek().ers != ers) {
+                // @@@ Use internal exception type
+                throw interpreterException(new IllegalStateException("Mismatched exception regions"));
+            }
+            erStack.pop();
+        }
+
+        Block exception(MethodHandles.Lookup l, Throwable e) {
+            // Find the first matching exception region
+            // with a catch block whose argument type is assignable-compatible to the throwable
+            ExceptionRegionRecord er;
+            Block cb = null;
+            while ((er = erStack.poll()) != null &&
+                    (cb = er.match(l, e)) == null) {
+            }
+
+            if (er == null) {
+                return null;
+            }
+
+            // Pop the block context to the block defining the start of the exception region
+            popTo(er.mark);
+            return cb;
+        }
+    }
+
+    static final class VarBox
+            implements CoreOp.Var<Object> {
+        Object value;
+
+        public Object value() {
+            return value;
+        }
+
+        VarBox(Object value) {
+            this.value = value;
+        }
+    }
+
+    record ClosureRecord(CoreOp.ClosureOp op,
+                         Map<Value, Object> capturedValues) {
+    }
+
+    record TupleRecord(List<Object> components) {
+        Object getComponent(int index) {
+            return components.get(index);
+        }
+
+        TupleRecord with(int index, Object value) {
+            List<Object> copy = new ArrayList<>(components);
+            copy.set(index, value);
+            return new TupleRecord(copy);
+        }
+    }
+
+    record ExceptionRegionRecord(BlockContext mark, CoreOp.ExceptionRegionEnter ers)
+            implements CoreOp.ExceptionRegion {
+        Block match(MethodHandles.Lookup l, Throwable e) {
+            for (Block.Reference catchBlock : ers.catchBlocks()) {
+                Block target = catchBlock.targetBlock();
+                List<Block.Parameter> args = target.parameters();
+                if (args.size() != 1) {
+                    throw interpreterException(new IllegalStateException("Catch block must have one argument"));
+                }
+                TypeElement et = args.get(0).type();
+                if (et instanceof VarType vt) {
+                    et = vt.valueType();
+                }
+                if (resolveToClass(l, et).isInstance(e)) {
+                    return target;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(T op,
+                  Object... args) {
+        return invoke(MethodHandles.publicLookup(), op, args);
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(MethodHandles.Lookup l, T op,
+                  Object... args) {
+        return invoke(l, op, new ArrayList<>(Arrays.asList(args)));
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(MethodHandles.Lookup l, T op,
+                  Map<Value, Object> capturedValues,
+                  Object... args) {
+        return invoke(l, op, capturedValues, new ArrayList<>(Arrays.asList(args)));
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(T op,
+                  List<Object> args) {
+        return invoke(MethodHandles.publicLookup(), op, args);
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(T op,
+                  Map<Value, Object> capturedValues,
+                  List<Object> args) {
+        return invoke(MethodHandles.publicLookup(), op, capturedValues, args);
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(MethodHandles.Lookup l, T op,
+                  List<Object> args) {
+        return invoke(l, op, Map.of(), args);
+    }
+
+    public static <T extends Op & Op.Invokable>
+    Object invoke(MethodHandles.Lookup l, T invokableOp,
+                  Map<Value, Object> capturedValues,
+                  List<Object> args) {
+        Body r = invokableOp.bodies().get(0);
+        return invoke(l, r, capturedValues, new OpContext(), args);
+
+    }
+    static Object invoke(MethodHandles.Lookup l, Body r,
+                                 Map<Value, Object> capturedValues, OpContext oc,
+                                 List<Object> args) {
+        return invoke(l,r.entryBlock(), capturedValues, oc, args);
+    }
+
+    private static Object invoke(MethodHandles.Lookup l, Block first,
+                  Map<Value, Object> capturedValues, OpContext oc,
+                  List<Object> args) {
+
+        if (args.size() != first.parameters().size()) {
+            throw interpreterException(new IllegalArgumentException("Incorrect number of arguments"));
+        }
+        Map<Value, Object> values = new HashMap<>();
+        for (int i = 0; i < first.parameters().size(); i++) {
+            values.put(first.parameters().get(i), args.get(i));
+        }
+
+        // Note that first block cannot have any successors so the queue will have at least one entry
+        oc.stack.push(new BlockContext(first, values));
+        capturedValues.forEach(oc::setValue);
+        while (true) {
+            BlockContext bc = oc.stack.peek();
+
+            // Execute all but the terminating operation
+            int nops = bc.b.ops().size();
+            try {
+                for (int i = 0; i < nops - 1; i++) {
+                    Op op = bc.b.ops().get(i);
+                    assert !(op instanceof Op.Terminating) : op.opName();
+
+                    Object result = exec(l, oc, op);
+                    oc.setValue(op.result(), result);
+                }
+            } catch (InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                processThrowable(oc, l, t);
+                continue;
+            }
+
+            // Execute the terminating operation
+            Op to = bc.b.terminatingOp();
+            if (to instanceof CoreOp.ConditionalBranchOp cb) {
+                boolean p;
+                Object bop = oc.getValue(cb.predicate());
+                if (bop instanceof Boolean bp) {
+                    p = bp;
+                } else if (bop instanceof Integer ip) {
+                    // @@@ This is required when lifting up from bytecode, since boolean values
+                    // are erased to int values, abd the bytecode lifting implementation is not currently
+                    // sophisticated enough to recover the type information
+                    p = ip != 0;
+                } else {
+                    throw interpreterException(
+                            new UnsupportedOperationException("Unsupported type input to operation: " + cb));
+                }
+                Block.Reference sb = p ? cb.trueBranch() : cb.falseBranch();
+                oc.successor(sb);
+            } else if (to instanceof CoreOp.BranchOp b) {
+                Block.Reference sb = b.branch();
+
+                oc.successor(sb);
+            } else if (to instanceof CoreOp.ThrowOp _throw) {
+                Throwable t = (Throwable) oc.getValue(_throw.argument());
+                processThrowable(oc, l, t);
+            } else if (to instanceof CoreOp.ReturnOp ret) {
+                Value rv = ret.returnValue();
+                return rv == null ? null : oc.getValue(rv);
+            } else if (to instanceof CoreOp.YieldOp yop) {
+                Value yv = yop.yieldValue();
+                return yv == null ? null : oc.getValue(yv);
+            } else if (to instanceof CoreOp.ExceptionRegionEnter ers) {
+                var er = new ExceptionRegionRecord(oc.stack.peek(), ers);
+                oc.setValue(ers.result(), er);
+
+                oc.pushExceptionRegion(er);
+
+                oc.successor(ers.start());
+            } else if (to instanceof CoreOp.ExceptionRegionExit ere) {
+                oc.popExceptionRegion(ere.regionStart());
+
+                oc.successor(ere.end());
+            } else {
+                throw interpreterException(
+                        new UnsupportedOperationException("Unsupported terminating operation: " + to.opName()));
+            }
+        }
+    }
+
+    static <T extends Op & Op.Invokable>
+    Object interpretBody(MethodHandles.Lookup l, Body r,
+                         OpContext oc) {
+        return invoke(l, r, Map.of(), oc, List.of());
+    }
+
+    static void processThrowable(OpContext oc, MethodHandles.Lookup l, Throwable t) {
+        // Find a matching catch block
+        Block cb = oc.exception(l, t);
+        if (cb == null) {
+            // If there is no matching catch bock then rethrow back to the caller
+            eraseAndThrow(t);
+            throw new InternalError("should not reach here");
+        }
+
+        // Add a new block context to the catch block with the exception as the argument
+        Map<Value, Object> bValues = new HashMap<>();
+        Block.Parameter eArg = cb.parameters().get(0);
+        if (eArg.type() instanceof VarType) {
+            bValues.put(eArg, new VarBox(t));
+        } else {
+            bValues.put(eArg, t);
+        }
+        oc.successor(cb, bValues);
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> void eraseAndThrow(Throwable e) throws E {
+        throw (E) e;
+    }
+
+    static Object exec(MethodHandles.Lookup l, OpContext oc, Op o) {
+        if (o instanceof CoreOp.ConstantOp co) {
+            if (co.resultType().equals(JavaType.J_L_CLASS)) {
+                return resolveToClass(l, (JavaType) co.value());
+            } else {
+                return co.value();
+            }
+        } else if (o instanceof CoreOp.FuncCallOp fco) {
+            String name = fco.funcName();
+
+            // Find top-level op
+            Op top = fco;
+            while (top.ancestorBody() != null) {
+                top = top.ancestorBody().parentOp();
+            }
+
+            // Ensure top-level op is a module and function name
+            // is in the module's function table
+            if (top instanceof CoreOp.ModuleOp mop) {
+                CoreOp.FuncOp funcOp = mop.functionTable().get(name);
+                if (funcOp == null) {
+                    throw interpreterException(
+                            new IllegalStateException
+                                    ("Function " + name + " cannot be resolved: not in module's function table"));
+                }
+
+                Object[] values = o.operands().stream().map(oc::getValue).toArray();
+                return Interpreter.invoke(funcOp, values);
+            } else {
+                throw interpreterException(
+                        new IllegalStateException(
+                                "Function " + name + " cannot be resolved: top level op is not a module"));
+            }
+        } else if (o instanceof CoreOp.InvokeOp co) {
+            MethodHandle mh;
+            if (co.hasReceiver()) {
+                mh = methodHandle(l, co.invokeDescriptor());
+            } else {
+                mh = methodStaticHandle(l, co.invokeDescriptor());
+            }
+            MethodType target = resolveToMethodType(l, o.opType());
+            mh = mh.asType(target).asFixedArity();
+            Object[] values = o.operands().stream().map(oc::getValue).toArray();
+            return invoke(mh, values);
+        } else if (o instanceof CoreOp.NewOp no) {
+            Object[] values = o.operands().stream().map(oc::getValue).toArray();
+            JavaType nType = (JavaType) no.constructorType().returnType();
+            if (nType instanceof ArrayType at) {
+                if (values.length > at.dimensions()) {
+                    throw interpreterException(new IllegalArgumentException("Bad constructor NewOp: " + no));
+                }
+                int[] lengths = Stream.of(values).mapToInt(v -> (int) v).toArray();
+                for (int length : lengths) {
+                    nType = ((ArrayType)nType).componentType();
+                }
+                return Array.newInstance(resolveToClass(l, nType), lengths);
+            } else {
+                MethodHandle mh = constructorHandle(l, no.constructorType());
+                return invoke(mh, values);
+            }
+        } else if (o instanceof CoreOp.QuotedOp qo) {
+            Map<Value, Object> capturedValues = qo.capturedValues().stream()
+                    .collect(toMap(v -> v, oc::getValue, (v, _) -> v, LinkedHashMap::new));
+            return new Quoted(qo.quotedOp(), capturedValues);
+        } else if (o instanceof CoreOp.LambdaOp lo) {
+            Map<Value, Object> capturedValues = lo.capturedValues().stream()
+                    .collect(toMap(v -> v, oc::getValue, (v, _) -> v, LinkedHashMap::new));
+            Class<?> fi = resolveToClass(l, lo.functionalInterface());
+
+            MethodHandle fProxy = INVOKE_LAMBDA_MH.bindTo(l).bindTo(lo).bindTo(capturedValues)
+                    .asCollector(Object[].class, lo.body().entryBlock().parameters().size());
+            Object fiInstance = MethodHandleProxies.asInterfaceInstance(fi, fProxy);
+
+            // If a quotable lambda proxy again to implement Quotable
+            if (Quotable.class.isAssignableFrom(fi)) {
+                return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class<?>[]{fi},
+                        (_, method, args) -> {
+                            if (method.getDeclaringClass() == Quotable.class) {
+                                // Implement Quotable::quoted
+                                return new Quoted(lo, capturedValues);
+                            } else {
+                                // Delegate to FI instance
+                                return method.invoke(fiInstance, args);
+                            }
+                        });
+            } else {
+                return fiInstance;
+            }
+        } else if (o instanceof CoreOp.ClosureOp co) {
+            Map<Value, Object> capturedValues = co.capturedValues().stream()
+                    .collect(toMap(v -> v, oc::getValue));
+            return new ClosureRecord(co, capturedValues);
+        } else if (o instanceof CoreOp.ClosureCallOp cco) {
+            List<Object> values = o.operands().stream().map(oc::getValue).toList();
+            ClosureRecord cr = (ClosureRecord) values.get(0);
+
+            return Interpreter.invoke(l, cr.op(), cr.capturedValues, values.subList(1, values.size()));
+        } else if (o instanceof CoreOp.VarOp vo) {
+            return new VarBox(oc.getValue(o.operands().get(0)));
+        } else if (o instanceof CoreOp.VarAccessOp.VarLoadOp vlo) {
+            // Cast to CoreOp.Var, since the instance may have originated as an external instance
+            // via a captured value map
+            CoreOp.Var<?> vb = (CoreOp.Var<?>) oc.getValue(o.operands().get(0));
+            return vb.value();
+        } else if (o instanceof CoreOp.VarAccessOp.VarStoreOp vso) {
+            VarBox vb = (VarBox) oc.getValue(o.operands().get(0));
+            vb.value = oc.getValue(o.operands().get(1));
+            return null;
+        } else if (o instanceof CoreOp.TupleOp to) {
+            List<Object> values = o.operands().stream().map(oc::getValue).toList();
+            return new TupleRecord(values);
+        } else if (o instanceof CoreOp.TupleLoadOp tlo) {
+            TupleRecord tb = (TupleRecord) oc.getValue(o.operands().get(0));
+            return tb.getComponent(tlo.index());
+        } else if (o instanceof CoreOp.TupleWithOp two) {
+            TupleRecord tb = (TupleRecord) oc.getValue(o.operands().get(0));
+            return tb.with(two.index(), oc.getValue(o.operands().get(1)));
+        } else if (o instanceof CoreOp.FieldAccessOp.FieldLoadOp fo) {
+            if (fo.operands().isEmpty()) {
+                VarHandle vh = fieldStaticHandle(l, fo.fieldDescriptor());
+                return vh.get();
+            } else {
+                Object v = oc.getValue(o.operands().get(0));
+                VarHandle vh = fieldHandle(l, fo.fieldDescriptor());
+                return vh.get(v);
+            }
+        } else if (o instanceof CoreOp.FieldAccessOp.FieldStoreOp fo) {
+            if (fo.operands().size() == 1) {
+                Object v = oc.getValue(o.operands().get(0));
+                VarHandle vh = fieldStaticHandle(l, fo.fieldDescriptor());
+                vh.set(v);
+            } else {
+                Object r = oc.getValue(o.operands().get(0));
+                Object v = oc.getValue(o.operands().get(1));
+                VarHandle vh = fieldHandle(l, fo.fieldDescriptor());
+                vh.set(r, v);
+            }
+            return null;
+        } else if (o instanceof CoreOp.InstanceOfOp io) {
+            Object v = oc.getValue(o.operands().get(0));
+            return isInstance(l, io.type(), v);
+        } else if (o instanceof CoreOp.CastOp co) {
+            Object v = oc.getValue(o.operands().get(0));
+            return cast(l, co.type(), v);
+        } else if (o instanceof CoreOp.ArrayLengthOp) {
+            Object a = oc.getValue(o.operands().get(0));
+            return Array.getLength(a);
+        } else if (o instanceof CoreOp.ArrayAccessOp.ArrayLoadOp) {
+            Object a = oc.getValue(o.operands().get(0));
+            Object index = oc.getValue(o.operands().get(1));
+            return Array.get(a, (int) index);
+        } else if (o instanceof CoreOp.ArrayAccessOp.ArrayStoreOp) {
+            Object a = oc.getValue(o.operands().get(0));
+            Object index = oc.getValue(o.operands().get(1));
+            Object v = oc.getValue(o.operands().get(2));
+            Array.set(a, (int) index, v);
+            return null;
+        } else if (o instanceof CoreOp.ArithmeticOperation || o instanceof CoreOp.TestOperation) {
+            MethodHandle mh = opHandle(o.opName(), o.opType());
+            Object[] values = o.operands().stream().map(oc::getValue).toArray();
+            return invoke(mh, values);
+        } else if (o instanceof CoreOp.ConvOp) {
+            MethodHandle mh = opHandle(o.opName() + "_" + o.opType().returnType(), o.opType());
+            Object[] values = o.operands().stream().map(oc::getValue).toArray();
+            return invoke(mh, values);
+        } else if (o instanceof CoreOp.AssertOp _assert) {
+            //Note: The nature of asserts and munged bodies may require a re-visiting.
+            //This code seems to work without poisoning contexts. See TestAssert.java in tests for relevant test coverage.
+            Body testBody = _assert.bodies.get(0);
+            boolean testResult = (boolean) interpretBody(l, testBody, oc);
+            if (!testResult) {
+                if (_assert.bodies.size() > 1) {
+                    Body messageBody = _assert.bodies.get(1);
+                    String message = String.valueOf(interpretBody(l, messageBody, oc));
+                    throw new AssertionError(message);
+                } else {
+                    throw new AssertionError();
+                }
+            }
+            return null;
+        } else if (o instanceof CoreOp.ConcatOp) {
+            return o.operands().stream()
+                    .map(oc::getValue)
+                    .map(String::valueOf)
+                    .collect(Collectors.joining());
+        } else {
+            throw interpreterException(
+                    new UnsupportedOperationException("Unsupported operation: " + o.opName()));
+        }
+    }
+
+    static final MethodHandle INVOKE_LAMBDA_MH;
+    static {
+        try {
+            INVOKE_LAMBDA_MH = MethodHandles.lookup().findStatic(Interpreter.class, "invokeLambda",
+                    MethodType.methodType(Object.class, MethodHandles.Lookup.class,
+                            CoreOp.LambdaOp.class, Map.class, Object[].class));
+        } catch (Throwable t) {
+            throw new InternalError(t);
+        }
+    }
+
+    static Object invokeLambda(MethodHandles.Lookup l, CoreOp.LambdaOp op, Map<Value, Object> capturedValues, Object[] args) {
+        return invoke(l, op, capturedValues, args);
+    }
+
+    static MethodHandle opHandle(String opName, FunctionType ft) {
+        MethodType mt = resolveToMethodType(MethodHandles.lookup(), ft).erase();
+        try {
+            return MethodHandles.lookup().findStatic(InvokableLeafOps.class, opName, mt);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw interpreterException(e);
+        }
+    }
+
+    static MethodHandle methodStaticHandle(MethodHandles.Lookup l, MethodRef d) {
+        return resolveToMethodHandle(l, d);
+    }
+
+    static MethodHandle methodHandle(MethodHandles.Lookup l, MethodRef d) {
+        return resolveToMethodHandle(l, d);
+    }
+
+    static MethodHandle constructorHandle(MethodHandles.Lookup l, FunctionType ft) {
+        MethodType mt = resolveToMethodType(l, ft);
+
+        if (mt.returnType().isArray()) {
+            if (mt.parameterCount() != 1 || mt.parameterType(0) != int.class) {
+                throw interpreterException(new IllegalArgumentException("Bad constructor descriptor: " + ft));
+            }
+            return MethodHandles.arrayConstructor(mt.returnType());
+        } else {
+            try {
+                return l.findConstructor(mt.returnType(), mt.changeReturnType(void.class));
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                throw interpreterException(e);
+            }
+        }
+    }
+
+    static VarHandle fieldStaticHandle(MethodHandles.Lookup l, FieldRef d) {
+        return resolveToVarHandle(l, d);
+    }
+
+    static VarHandle fieldHandle(MethodHandles.Lookup l, FieldRef d) {
+        return resolveToVarHandle(l, d);
+    }
+
+    static Object isInstance(MethodHandles.Lookup l, TypeElement d, Object v) {
+        Class<?> c = resolveToClass(l, d);
+        return c.isInstance(v);
+    }
+
+    static Object cast(MethodHandles.Lookup l, TypeElement d, Object v) {
+        Class<?> c = resolveToClass(l, d);
+        return c.cast(v);
+    }
+
+    static MethodHandle resolveToMethodHandle(MethodHandles.Lookup l, MethodRef d) {
+        try {
+            return d.resolveToHandle(l);
+        } catch (ReflectiveOperationException e) {
+            throw interpreterException(e);
+        }
+    }
+
+    static VarHandle resolveToVarHandle(MethodHandles.Lookup l, FieldRef d) {
+        try {
+            return d.resolveToHandle(l);
+        } catch (ReflectiveOperationException e) {
+            throw interpreterException(e);
+        }
+    }
+
+    public static MethodType resolveToMethodType(MethodHandles.Lookup l, FunctionType ft) {
+        try {
+            return MethodRef.toNominalDescriptor(ft).resolveConstantDesc(l);
+        } catch (ReflectiveOperationException e) {
+            throw interpreterException(e);
+        }
+    }
+
+    public static Class<?> resolveToClass(MethodHandles.Lookup l, TypeElement d) {
+        try {
+            if (d instanceof JavaType jt) {
+                return (Class<?>)jt.erasure().resolve(l);
+            } else {
+                throw new ReflectiveOperationException();
+            }
+        } catch (ReflectiveOperationException e) {
+            throw interpreterException(e);
+        }
+    }
+
+    static Object invoke(MethodHandle m, Object... args) {
+        try {
+            return m.invokeWithArguments(args);
+        } catch (RuntimeException | Error e) {
+            throw e;
+        } catch (Throwable e) {
+            eraseAndThrow(e);
+            throw new InternalError("should not reach here");
+        }
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/interpreter/InvokableLeafOps.java b/src/java.base/share/classes/java/lang/reflect/code/interpreter/InvokableLeafOps.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/interpreter/InvokableLeafOps.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.interpreter;
+
+final class InvokableLeafOps {
+
+    public static String add(String a, String b) {
+        return a.concat(b);
+    }
+
+
+    public static boolean eq(Object a, Object b) {
+        return a == b;
+    }
+
+    public static boolean neq(Object a, Object b) {
+        return a != b;
+    }
+
+
+    public static boolean not(boolean l) {
+        return !l;
+    }
+
+    // int
+
+    public static int neg(int l) {
+        return -l;
+    }
+
+    public static int add(int l, int r) {
+        return l + r;
+    }
+
+    public static int sub(int l, int r) {
+        return l - r;
+    }
+
+    public static int mul(int l, int r) {
+        return l * r;
+    }
+
+    public static int div(int l, int r) {
+        return l / r;
+    }
+
+    public static int mod(int l, int r) {
+        return l % r;
+    }
+
+    public static int or(int l, int r) {
+        return l | r;
+    }
+
+    public static int and(int l, int r) {
+        return l & r;
+    }
+
+    public static int xor(int l, int r) {
+        return l ^ r;
+    }
+
+    public static int lshl(int l, int r) {
+        return l << r;
+    }
+
+    public static int ashr(int l, int r) {
+        return l >> r;
+    }
+
+    public static int lshr(int l, int r) {
+        return l >>> r;
+    }
+
+    public static int lshl(int l, long r) {
+        return l << r;
+    }
+
+    public static int ashr(int l, long r) {
+        return l >> r;
+    }
+
+    public static int lshr(int l, long r) {
+        return l >>> r;
+    }
+
+    public static boolean eq(int l, int r) {
+        return l == r;
+    }
+
+    public static boolean neq(int l, int r) {
+        return l != r;
+    }
+
+    public static boolean gt(int l, int r) {
+        return l > r;
+    }
+
+    public static boolean ge(int l, int r) {
+        return l >= r;
+    }
+
+    public static boolean lt(int l, int r) {
+        return l < r;
+    }
+
+    public static boolean le(int l, int r) {
+        return l <= r;
+    }
+
+    // byte
+
+    public static byte neg(byte l) {
+        return (byte) -l;
+    }
+
+    public static byte add(byte l, byte r) {
+        return (byte) (l + r);
+    }
+
+    public static byte sub(byte l, byte r) {
+        return (byte) (l - r);
+    }
+
+    public static byte mul(byte l, byte r) {
+        return (byte) (l * r);
+    }
+
+    public static byte div(byte l, byte r) {
+        return (byte) (l / r);
+    }
+
+    public static byte mod(byte l, byte r) {
+        return (byte) (l % r);
+    }
+
+    public static byte or(byte l, byte r) {
+        return (byte) (l | r);
+    }
+
+    public static byte and(byte l, byte r) {
+        return (byte) (l & r);
+    }
+
+    public static byte xor(byte l, byte r) {
+        return (byte) (l ^ r);
+    }
+
+    public static byte ashr(byte l, long r) {
+        return (byte) (l >> r);
+    }
+
+    public static byte lshr(byte l, long r) {
+        return (byte) (l >>> r);
+    }
+
+    public static byte lshl(byte l, int r) {
+        return (byte) (l << r);
+    }
+
+    public static byte ashr(byte l, int r) {
+        return (byte) (l >> r);
+    }
+
+    public static byte lshr(byte l, int r) {
+        return (byte) (l >>> r);
+    }
+
+    // short
+
+    public static short neg(short l) {
+        return (short) -l;
+    }
+
+    public static short add(short l, short r) {
+        return (short) (l + r);
+    }
+
+    public static short sub(short l, short r) {
+        return (short) (l - r);
+    }
+
+    public static short mul(short l, short r) {
+        return (short) (l * r);
+    }
+
+    public static short div(short l, short r) {
+        return (short) (l / r);
+    }
+
+    public static short mod(short l, short r) {
+        return (short) (l % r);
+    }
+
+    public static short or(short l, short r) {
+        return (short) (l | r);
+    }
+
+    public static short and(short l, short r) {
+        return (short) (l & r);
+    }
+
+    public static short xor(short l, short r) {
+        return (short) (l ^ r);
+    }
+
+    public static short ashr(short l, long r) {
+        return (short) (l >> r);
+    }
+
+    public static short lshr(short l, long r) {
+        return (short) (l >>> r);
+    }
+
+    public static short lshl(short l, int r) {
+        return (short) (l << r);
+    }
+
+    public static short ashr(short l, int r) {
+        return (short) (l >> r);
+    }
+
+    public static short lshr(short l, int r) {
+        return (short) (l >>> r);
+    }
+
+    // char
+
+    public static char neg(char l) {
+        return (char) -l;
+    }
+
+    public static char add(char l, char r) {
+        return (char) (l + r);
+    }
+
+    public static char sub(char l, char r) {
+        return (char) (l - r);
+    }
+
+    public static char mul(char l, char r) {
+        return (char) (l * r);
+    }
+
+    public static char div(char l, char r) {
+        return (char) (l / r);
+    }
+
+    public static char mod(char l, char r) {
+        return (char) (l % r);
+    }
+
+    public static char or(char l, char r) {
+        return (char) (l | r);
+    }
+
+    public static char and(char l, char r) {
+        return (char) (l & r);
+    }
+
+    public static char xor(char l, char r) {
+        return (char) (l ^ r);
+    }
+
+    public static char ashr(char l, long r) {
+        return (char) (l >> r);
+    }
+
+    public static char lshr(char l, long r) {
+        return (char) (l >>> r);
+    }
+
+    public static char lshl(char l, int r) {
+        return (char) (l << r);
+    }
+
+    public static char ashr(char l, int r) {
+        return (char) (l >> r);
+    }
+
+    public static char lshr(char l, int r) {
+        return (char) (l >>> r);
+    }
+
+   // long
+
+    public static long neg(long l) {
+        return -l;
+    }
+
+    public static long add(long l, long r) {
+        return l + r;
+    }
+
+    public static long sub(long l, long r) {
+        return l - r;
+    }
+
+    public static long mul(long l, long r) {
+        return l * r;
+    }
+
+    public static long div(long l, long r) {
+        return l / r;
+    }
+
+    public static long mod(long l, long r) {
+        return l % r;
+    }
+
+    public static long or(long l, long r) {
+        return l | r;
+    }
+
+    public static long and(long l, long r) {
+        return l & r;
+    }
+
+    public static long xor(long l, long r) {
+        return l ^ r;
+    }
+
+    public static long lshl(long l, long r) {
+        return l << r;
+    }
+
+    public static long ashr(long l, long r) {
+        return l >> r;
+    }
+
+    public static long lshr(long l, long r) {
+        return l >>> r;
+    }
+
+    public static long lshl(long l, int r) {
+        return l << r;
+    }
+
+    public static long ashr(long l, int r) {
+        return l >> r;
+    }
+
+    public static long lshr(long l, int r) {
+        return l >>> r;
+    }
+
+    public static boolean eq(long l, long r) {
+        return l == r;
+    }
+
+    public static boolean neq(long l, long r) {
+        return l != r;
+    }
+
+    public static boolean gt(long l, long r) {
+        return l > r;
+    }
+
+    public static boolean ge(long l, long r) {
+        return l >= r;
+    }
+
+    public static boolean lt(long l, long r) {
+        return l < r;
+    }
+
+    public static boolean le(long l, long r) {
+        return l <= r;
+    }
+
+
+
+    // float
+
+    static float neg(float l) {
+        return -l;
+    }
+
+    static float add(float l, float r) {
+        return l + r;
+    }
+
+    static float sub(float l, float r) {
+        return l - r;
+    }
+
+    static float mul(float l, float r) {
+        return l * r;
+    }
+
+    static float div(float l, float r) {
+        return l / r;
+    }
+
+    static float mod(float l, float r) {
+        return l % r;
+    }
+
+    public static boolean eq(float l, float r) {
+        return l == r;
+    }
+
+    public static boolean neq(float l, float r) {
+        return l != r;
+    }
+
+    public static boolean gt(float l, float r) {
+        return l > r;
+    }
+
+    public static boolean ge(float l, float r) {
+        return l >= r;
+    }
+
+    public static boolean lt(float l, float r) {
+        return l < r;
+    }
+
+    public static boolean le(float l, float r) {
+        return l <= r;
+    }
+
+
+
+    // double
+
+    static double neg(double l) {
+        return -l;
+    }
+
+    static double add(double l, double r) {
+        return l + r;
+    }
+
+    static double sub(double l, double r) {
+        return l - r;
+    }
+
+    static double mul(double l, double r) {
+        return l * r;
+    }
+
+    static double div(double l, double r) {
+        return l / r;
+    }
+
+    static double mod(double l, double r) {
+        return l % r;
+    }
+
+
+
+    // boolean
+
+    static boolean eq(boolean l, boolean r) {
+        return l == r;
+    }
+
+    static boolean neq(boolean l, boolean r) {
+        return l != r;
+    }
+
+    static boolean and(boolean l, boolean r) {
+        return l & r;
+    }
+
+    static boolean or(boolean l, boolean r) {
+        return l | r;
+    }
+
+    static boolean xor(boolean l, boolean r) {
+        return l ^ r;
+    }
+
+
+    // Primitive conversions
+
+    // double conversion
+    static double conv_double(double i) {
+        return i;
+    }
+    static float conv_float(double i) {
+        return (float) i;
+    }
+    static long conv_long(double i) {
+        return (long) i;
+    }
+    static int conv_int(double i) {
+        return (int) i;
+    }
+    static short conv_short(double i) {
+        return (short) i;
+    }
+    static char conv_char(double i) {
+        return (char) i;
+    }
+    static byte conv_byte(double i) {
+        return (byte) i;
+    }
+
+    // float conversion
+    static double conv_double(float i) {
+        return i;
+    }
+    static float conv_float(float i) {
+        return i;
+    }
+    static long conv_long(float i) {
+        return (long) i;
+    }
+    static int conv_int(float i) {
+        return (int) i;
+    }
+    static short conv_short(float i) {
+        return (short) i;
+    }
+    static char conv_char(float i) {
+        return (char) i;
+    }
+    static byte conv_byte(float i) {
+        return (byte) i;
+    }
+
+    // long conversion
+    static double conv_double(long i) {
+        return (double) i;
+    }
+    static float conv_float(long i) {
+        return (float) i;
+    }
+    static long conv_long(long i) {
+        return i;
+    }
+    static int conv_int(long i) {
+        return (int) i;
+    }
+    static short conv_short(long i) {
+        return (short) i;
+    }
+    static char conv_char(long i) {
+        return (char) i;
+    }
+    static byte conv_byte(long i) {
+        return (byte) i;
+    }
+
+    // int conversion
+    static double conv_double(int i) {
+        return (double) i;
+    }
+    static float conv_float(int i) {
+        return (float) i;
+    }
+    static long conv_long(int i) {
+        return i;
+    }
+    static int conv_int(int i) {
+        return i;
+    }
+    static short conv_short(int i) {
+        return (short) i;
+    }
+    static char conv_char(int i) {
+        return (char) i;
+    }
+    static byte conv_byte(int i) {
+        return (byte) i;
+    }
+
+    // short conversion
+    static double conv_double(short i) {
+        return i;
+    }
+    static float conv_float(short i) {
+        return i;
+    }
+    static long conv_long(short i) {
+        return i;
+    }
+    static int conv_int(short i) {
+        return i;
+    }
+    static short conv_short(short i) {
+        return i;
+    }
+    static char conv_char(short i) {
+        return (char) i;
+    }
+    static byte conv_byte(short i) {
+        return (byte) i;
+    }
+
+    // char conversion
+    static double conv_double(char i) {
+        return i;
+    }
+    static float conv_float(char i) {
+        return i;
+    }
+    static long conv_long(char i) {
+        return i;
+    }
+    static int conv_int(char i) {
+        return i;
+    }
+    static short conv_short(char i) {
+        return (short) i;
+    }
+    static char conv_char(char i) {
+        return i;
+    }
+    static byte conv_byte(char i) {
+        return (byte) i;
+    }
+
+    // byte conversion
+    static double conv_double(byte i) {
+        return i;
+    }
+    static float conv_float(byte i) {
+        return i;
+    }
+    static long conv_long(byte i) {
+        return i;
+    }
+    static int conv_int(byte i) {
+        return i;
+    }
+    static short conv_short(byte i) {
+        return i;
+    }
+    static char conv_char(byte i) {
+        return (char) i;
+    }
+    static byte conv_byte(byte i) {
+        return i;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/interpreter/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/interpreter/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/interpreter/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for interpreting code models.
+ */
+package java.lang.reflect.code.interpreter;
diff a/src/java.base/share/classes/java/lang/reflect/code/op/CoreOp.java b/src/java.base/share/classes/java/lang/reflect/code/op/CoreOp.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/op/CoreOp.java
@@ -0,0 +1,4043 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.op;
+
+import java.lang.constant.ClassDesc;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.*;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * The top-level operation class for the set of enclosed core operations.
+ * <p>
+ * A code model, produced by the Java compiler from Java program source, may consist of extended operations and core
+ * operations. Such a model represents the same Java program and preserves the program meaning as defined by the
+ * Java Language Specification
+ */
+public sealed abstract class CoreOp extends ExternalizableOp {
+    /**
+     * An operation that models a Java expression
+     */
+    sealed interface JavaExpression permits
+            ArithmeticOperation,
+            ArrayAccessOp.ArrayLoadOp,
+            ArrayAccessOp.ArrayStoreOp,
+            ArrayLengthOp,
+            CastOp,
+            ConvOp,
+            ClosureOp,
+            ConcatOp,
+            ConstantOp,
+            FieldAccessOp.FieldLoadOp,
+            FieldAccessOp.FieldStoreOp,
+            InstanceOfOp,
+            InvokeOp,
+            LambdaOp,
+            NewOp,
+            TestOperation,
+            VarAccessOp.VarLoadOp,
+            VarAccessOp.VarStoreOp,
+            ExtendedOp.JavaConditionalExpressionOp,
+            ExtendedOp.JavaConditionalOp,
+            ExtendedOp.JavaSwitchExpressionOp {
+    }
+
+    /**
+     * An operation that models a Java statement
+     */
+    sealed interface JavaStatement permits
+            ArrayAccessOp.ArrayStoreOp,
+            AssertOp,
+            FieldAccessOp.FieldStoreOp,
+            InvokeOp,
+            NewOp,
+            ReturnOp,
+            ThrowOp,
+            VarAccessOp.VarStoreOp,
+            VarOp,
+            ExtendedOp.JavaBlockOp,
+            ExtendedOp.JavaDoWhileOp,
+            ExtendedOp.JavaEnhancedForOp,
+            ExtendedOp.JavaForOp,
+            ExtendedOp.JavaIfOp,
+            ExtendedOp.JavaLabelOp,
+            ExtendedOp.JavaLabeledOp,
+            ExtendedOp.JavaTryOp,
+            ExtendedOp.JavaWhileOp,
+            ExtendedOp.JavaYieldOp {
+    }
+
+    // Split string to ensure the name does not get rewritten
+    // when the script copies this source to the jdk.compiler module
+    static final String PACKAGE_NAME = "java.lang" + ".reflect.code";
+
+    static final String CoreOp_CLASS_NAME = PACKAGE_NAME + "." + CoreOp.class.getSimpleName();
+
+    protected CoreOp(Op that, CopyContext cc) {
+        super(that, cc);
+    }
+
+    protected CoreOp(String name, List<? extends Value> operands) {
+        super(name, operands);
+    }
+
+    protected CoreOp(ExternalizedOp def) {
+        super(def);
+    }
+
+    /**
+     * The function operation, that can model a Java method declaration.
+     */
+    @OpFactory.OpDeclaration(FuncOp.NAME)
+    public static final class FuncOp extends CoreOp
+            implements Op.Invokable, Op.Isolated, Op.Lowerable {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final String funcName;
+            final FunctionType funcType;
+
+            Builder(Body.Builder ancestorBody, String funcName, FunctionType funcType) {
+                this.ancestorBody = ancestorBody;
+                this.funcName = funcName;
+                this.funcType = funcType;
+            }
+
+            public FuncOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, funcType);
+                c.accept(body.entryBlock());
+                return new FuncOp(funcName, body);
+            }
+        }
+
+        public static final String NAME = "func";
+        public static final String ATTRIBUTE_FUNC_NAME = NAME + ".name";
+
+        final String funcName;
+        final Body body;
+
+        public static FuncOp create(ExternalizedOp def) {
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Bad op " + def.name());
+            }
+
+            String funcName = def.extractAttributeValue(ATTRIBUTE_FUNC_NAME, true,
+                    v -> switch (v) {
+                        case String s -> s;
+                        default -> throw new UnsupportedOperationException("Unsupported func name value:" + v);
+                    });
+            return new FuncOp(def, funcName);
+        }
+
+        FuncOp(ExternalizedOp def, String funcName) {
+            super(def);
+
+            this.funcName = funcName;
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        FuncOp(FuncOp that, CopyContext cc, OpTransformer oa) {
+            this(that, that.funcName, cc, oa);
+        }
+
+        FuncOp(FuncOp that, String funcName, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.funcName = funcName;
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public FuncOp transform(CopyContext cc, OpTransformer ot) {
+            return new FuncOp(this, cc, ot);
+        }
+
+        public FuncOp transform(OpTransformer ot) {
+            return new FuncOp(this, CopyContext.create(), ot);
+        }
+
+        public FuncOp transform(String funcName, OpTransformer ot) {
+            return new FuncOp(this, funcName, CopyContext.create(), ot);
+        }
+
+        FuncOp(String funcName, Body.Builder bodyBuilder) {
+            super(NAME,
+                    List.of());
+
+            this.funcName = funcName;
+            this.body = bodyBuilder.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", funcName);
+            return Collections.unmodifiableMap(m);
+        }
+
+        @Override
+        public FunctionType invokableType() {
+            return body.bodyType();
+        }
+
+        public String funcName() {
+            return funcName;
+        }
+
+        @Override
+        public Body body() {
+            return body;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer _ignore) {
+            // Isolate body with respect to ancestor transformations
+            // and copy directly without lowering descendant operations
+            b.op(this, OpTransformer.COPYING_TRANSFORMER);
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The function call operation, that models a call to a function, by name, declared in the module op that is also an
+     * ancestor of this operation.
+     */
+    // @@@ stack effects equivalent to the call operation as if the function were a Java method?
+    @OpFactory.OpDeclaration(FuncCallOp.NAME)
+    public static final class FuncCallOp extends CoreOp {
+        public static final String NAME = "func.call";
+        public static final String ATTRIBUTE_FUNC_NAME = NAME + ".name";
+
+        final String funcName;
+        final TypeElement resultType;
+
+        public static FuncCallOp create(ExternalizedOp def) {
+            String funcName = def.extractAttributeValue(ATTRIBUTE_FUNC_NAME, true,
+                    v -> switch (v) {
+                        case String s -> s;
+                        default -> throw new UnsupportedOperationException("Unsupported func name value:" + v);
+                    });
+
+            return new FuncCallOp(def, funcName);
+        }
+
+        FuncCallOp(ExternalizedOp def, String funcName) {
+            super(def);
+
+            this.funcName = funcName;
+            this.resultType = def.resultType();
+        }
+
+        FuncCallOp(FuncCallOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.funcName = that.funcName;
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public FuncCallOp transform(CopyContext cc, OpTransformer ot) {
+            return new FuncCallOp(this, cc);
+        }
+
+        FuncCallOp(String funcName, TypeElement resultType, List<Value> args) {
+            super(NAME, args);
+
+            this.funcName = funcName;
+            this.resultType = resultType;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", funcName);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public String funcName() {
+            return funcName;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The module operation, modeling a collection of functions,
+     * and creating a symbol table of function name to function
+     */
+    @OpFactory.OpDeclaration(ModuleOp.NAME)
+    public static final class ModuleOp extends CoreOp
+            implements Op.Isolated {
+
+        public static final String NAME = "module";
+
+        final Map<String, FuncOp> table;
+        final Body body;
+
+        public static ModuleOp create(ExternalizedOp def) {
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Bad op " + def.name());
+            }
+
+            return new ModuleOp(def);
+        }
+
+        ModuleOp(ExternalizedOp def) {
+            super(def);
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+            this.table = createTable(body);
+        }
+
+        ModuleOp(ModuleOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.body = that.body.transform(cc, ot).build(this);
+            this.table = createTable(body);
+        }
+
+        static Map<String, FuncOp> createTable(Body body) {
+            Map<String, FuncOp> table = new HashMap<>();
+            for (var op : body.entryBlock().ops()) {
+                if (op instanceof FuncOp fop) {
+                    table.put(fop.funcName(), fop);
+                } else {
+                    throw new IllegalArgumentException("Bad operation in module: " + op);
+                }
+            }
+            return Collections.unmodifiableMap(table);
+        }
+
+        @Override
+        public ModuleOp transform(CopyContext cc, OpTransformer ot) {
+            return new ModuleOp(this, cc, ot);
+        }
+
+        public ModuleOp transform(OpTransformer ot) {
+            return new ModuleOp(this, CopyContext.create(), ot);
+        }
+
+        ModuleOp(List<FuncOp> functions) {
+            super(NAME,
+                    List.of());
+
+            Body.Builder bodyC = Body.Builder.of(null, FunctionType.VOID);
+            Block.Builder entryBlock = bodyC.entryBlock();
+            Map<String, FuncOp> table = new HashMap<>();
+            for (FuncOp f : functions) {
+                entryBlock.op(f);
+                table.put(f.funcName(), f);
+            }
+            this.table = Collections.unmodifiableMap(table);
+            this.body = bodyC.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        public Map<String, FuncOp> functionTable() {
+            return table;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The quoted operation, that models the quoting of an operation.
+     */
+    @OpFactory.OpDeclaration(QuotedOp.NAME)
+    public static final class QuotedOp extends CoreOp
+            implements Op.Nested, Op.Lowerable, Op.Pure {
+        public static final String NAME = "quoted";
+
+        // Type name must be the same in the java.base and jdk.compiler module
+        static final String Quoted_CLASS_NAME = PACKAGE_NAME +
+                "." + Quoted.class.getSimpleName();
+        public static final JavaType QUOTED_TYPE = JavaType.type(ClassDesc.of(Quoted_CLASS_NAME));
+
+        final Body quotedBody;
+
+        final Op quotedOp;
+
+        public QuotedOp(ExternalizedOp def) {
+            super(def);
+
+            this.quotedBody = def.bodyDefinitions().get(0).build(this);
+
+            if (quotedBody.entryBlock().terminatingOp() instanceof YieldOp brk &&
+                    brk.yieldValue() instanceof Result quotedOpResult) {
+                this.quotedOp = quotedOpResult.op();
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        QuotedOp(QuotedOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.quotedBody = that.quotedBody.transform(cc, ot).build(this);
+            this.quotedOp = that.quotedOp;
+        }
+
+        @Override
+        public QuotedOp transform(CopyContext cc, OpTransformer ot) {
+            return new QuotedOp(this, cc, ot);
+        }
+
+        QuotedOp(Body.Builder bodyC) {
+            super(NAME,
+                    List.of());
+
+            this.quotedBody = bodyC.build(this);
+            if (quotedBody.blocks().size() > 1) {
+                throw new IllegalArgumentException();
+            }
+            if (!(quotedBody.entryBlock().terminatingOp() instanceof YieldOp yop)) {
+                throw new IllegalArgumentException();
+            }
+            if (!(yop.yieldValue() instanceof Result r)) {
+                throw new IllegalArgumentException();
+            }
+            this.quotedOp = r.op();
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(quotedBody);
+        }
+
+        public Op quotedOp() {
+            return quotedOp;
+        }
+
+        @Override
+        public List<Value> capturedValues() {
+            return quotedBody.capturedValues();
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer _ignore) {
+            // Isolate body with respect to ancestor transformations
+            // and copy directly without lowering descendant operations
+            b.op(this, OpTransformer.COPYING_TRANSFORMER);
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return QUOTED_TYPE;
+        }
+    }
+
+    /**
+     * The lambda operation, that can model a Java lambda expression.
+     */
+    @OpFactory.OpDeclaration(LambdaOp.NAME)
+    public static final class LambdaOp extends CoreOp
+            implements Op.Invokable, Op.Lowerable, JavaExpression {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final FunctionType funcType;
+            final TypeElement functionalInterface;
+
+            Builder(Body.Builder ancestorBody, FunctionType funcType, TypeElement functionalInterface) {
+                this.ancestorBody = ancestorBody;
+                this.funcType = funcType;
+                this.functionalInterface = functionalInterface;
+            }
+
+            public LambdaOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, funcType);
+                c.accept(body.entryBlock());
+                return new LambdaOp(functionalInterface, body);
+            }
+        }
+
+        public static final String NAME = "lambda";
+
+        final TypeElement functionalInterface;
+        final Body body;
+
+        public LambdaOp(ExternalizedOp def) {
+            super(def);
+
+            this.functionalInterface = def.resultType();
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        LambdaOp(LambdaOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.functionalInterface = that.functionalInterface;
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public LambdaOp transform(CopyContext cc, OpTransformer ot) {
+            return new LambdaOp(this, cc, ot);
+        }
+
+        LambdaOp(TypeElement functionalInterface, Body.Builder bodyC) {
+            super(NAME,
+                    List.of());
+
+            this.functionalInterface = functionalInterface;
+            this.body = bodyC.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public FunctionType invokableType() {
+            return body.bodyType();
+        }
+
+        public TypeElement functionalInterface() {
+            return functionalInterface;
+        }
+
+        @Override
+        public Body body() {
+            return body;
+        }
+
+        @Override
+        public List<Value> capturedValues() {
+            return body.capturedValues();
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer _ignore) {
+            // Isolate body with respect to ancestor transformations
+            b.op(this, OpTransformer.LOWERING_TRANSFORMER);
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return functionalInterface();
+        }
+
+        /**
+         * Determines if this lambda operation could have originated from a
+         * method reference declared in Java source code.
+         * <p>
+         * Such a lambda operation is one with the following constraints:
+         * <ol>
+         *     <li>Zero or one captured value (assuming correspondence to the {@code this} variable).
+         *     <li>A body with only one (entry) block that contains only variable declaration
+         *     operations, variable load operations, invoke operations to box or unbox
+         *     primitive values, a single invoke operation to the method that is
+         *     referenced, and a return operation.
+         *     <li>if the return operation returns a non-void result then that result is,
+         *     or uniquely depends on, the result of the referencing invoke operation.
+         *     <li>If the lambda operation captures one value then the first operand corresponds
+         *     to captured the value, and subsequent operands of the referencing invocation
+         *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
+         *     Otherwise, the first and subsequent operands of the referencing invocation
+         *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
+         * </ol>
+         * A value, V2, uniquely depends on another value, V1, if the graph of what V2 depends on
+         * contains only nodes with single edges terminating in V1, and the graph of what depends on V1
+         * is bidirectionally equal to the graph of what V2 depends on.
+         *
+         * @return the invocation operation to the method referenced by the lambda
+         * operation, otherwise empty.
+         */
+        public Optional<InvokeOp> methodReference() {
+            // Single block
+            if (body().blocks().size() > 1) {
+                return Optional.empty();
+            }
+
+            // Zero or one (this) capture
+            List<Value> cvs = capturedValues();
+            if (cvs.size() > 1) {
+                return Optional.empty();
+            }
+
+            Map<Value, Value> valueMapping = new HashMap<>();
+            CoreOp.InvokeOp methodRefInvokeOp = extractMethodInvoke(valueMapping, body().entryBlock().ops());
+            if (methodRefInvokeOp == null) {
+                return Optional.empty();
+            }
+
+            // Lambda's parameters map in encounter order with the invocation's operands
+            List<Value> lambdaParameters = new ArrayList<>();
+            if (cvs.size() == 1) {
+                lambdaParameters.add(cvs.getFirst());
+            }
+            lambdaParameters.addAll(parameters());
+            List<Value> methodRefOperands = methodRefInvokeOp.operands().stream().map(valueMapping::get).toList();
+            if (!lambdaParameters.equals(methodRefOperands)) {
+                return Optional.empty();
+            }
+
+            return Optional.of(methodRefInvokeOp);
+        }
+
+        static CoreOp.InvokeOp extractMethodInvoke(Map<Value, Value> valueMapping, List<Op> ops) {
+            CoreOp.InvokeOp methodRefInvokeOp = null;
+            for (Op op : ops) {
+                switch (op) {
+                    case CoreOp.VarOp varOp -> {
+                        if (isValueUsedWithOp(varOp.result(), o -> o instanceof CoreOp.VarAccessOp.VarStoreOp)) {
+                            return null;
+                        }
+                    }
+                    case CoreOp.VarAccessOp.VarLoadOp varLoadOp -> {
+                        Value v = varLoadOp.varOp().operands().getFirst();
+                        valueMapping.put(varLoadOp.result(), valueMapping.getOrDefault(v, v));
+                    }
+                    case CoreOp.InvokeOp iop when isBoxOrUnboxInvocation(iop) -> {
+                        Value v = iop.operands().getFirst();
+                        valueMapping.put(iop.result(), valueMapping.getOrDefault(v, v));
+                    }
+                    case CoreOp.InvokeOp iop -> {
+                        if (methodRefInvokeOp != null) {
+                            return null;
+                        }
+
+                        for (Value o : iop.operands()) {
+                            valueMapping.put(o, valueMapping.getOrDefault(o, o));
+                        }
+                        methodRefInvokeOp = iop;
+                    }
+                    case CoreOp.ReturnOp rop -> {
+                        if (methodRefInvokeOp == null) {
+                            return null;
+                        }
+                        Value r = rop.returnValue();
+                        if (!(valueMapping.getOrDefault(r, r) instanceof Op.Result invokeResult)) {
+                            return null;
+                        }
+                        if (invokeResult.op() != methodRefInvokeOp) {
+                            return null;
+                        }
+                        assert methodRefInvokeOp.result().uses().size() == 1;
+                    }
+                    default -> {
+                        return null;
+                    }
+                }
+            }
+
+            return methodRefInvokeOp;
+        }
+
+        private static boolean isValueUsedWithOp(Value value, Predicate<Op> opPredicate) {
+            for (Op.Result user : value.uses()) {
+                if (opPredicate.test(user.op())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // @@@ Move to functionality on JavaType(s)
+        static final Set<String> UNBOX_NAMES = Set.of(
+                "byteValue",
+                "shortValue",
+                "charValue",
+                "intValue",
+                "longValue",
+                "floatValue",
+                "doubleValue",
+                "booleanValue");
+
+        private static boolean isBoxOrUnboxInvocation(CoreOp.InvokeOp iop) {
+            MethodRef mr = iop.invokeDescriptor();
+            return mr.refType() instanceof ClassType ct && ct.unbox().isPresent() &&
+                    (UNBOX_NAMES.contains(mr.name()) || mr.name().equals("valueOf"));
+        }
+    }
+
+    /**
+     * The closure operation, that can model a structured Java lambda expression
+     * that has no target type (a functional interface).
+     */
+    @OpFactory.OpDeclaration(ClosureOp.NAME)
+    public static final class ClosureOp extends CoreOp
+            implements Op.Invokable, Op.Lowerable, JavaExpression {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final FunctionType funcType;
+
+            Builder(Body.Builder ancestorBody, FunctionType funcType) {
+                this.ancestorBody = ancestorBody;
+                this.funcType = funcType;
+            }
+
+            public ClosureOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, funcType);
+                c.accept(body.entryBlock());
+                return new ClosureOp(body);
+            }
+        }
+
+        public static final String NAME = "closure";
+
+        final Body body;
+
+        public ClosureOp(ExternalizedOp def) {
+            super(def);
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        ClosureOp(ClosureOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public ClosureOp transform(CopyContext cc, OpTransformer ot) {
+            return new ClosureOp(this, cc, ot);
+        }
+
+        ClosureOp(Body.Builder bodyC) {
+            super(NAME,
+                    List.of());
+
+            this.body = bodyC.build(this);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        @Override
+        public FunctionType invokableType() {
+            return body.bodyType();
+        }
+
+        @Override
+        public Body body() {
+            return body;
+        }
+
+        @Override
+        public List<Value> capturedValues() {
+            return body.capturedValues();
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer _ignore) {
+            // Isolate body with respect to ancestor transformations
+            b.op(this, OpTransformer.LOWERING_TRANSFORMER);
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return body.bodyType();
+        }
+    }
+
+    /**
+     * The closure call operation, that models a call to a closure, by reference
+     */
+//  @@@ stack effects equivalent to the invocation of an SAM of on an instance of an anonymous functional interface
+//  that is the target of the closures lambda expression.
+    @OpFactory.OpDeclaration(ClosureCallOp.NAME)
+    public static final class ClosureCallOp extends CoreOp {
+        public static final String NAME = "closure.call";
+
+        public ClosureCallOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ClosureCallOp(ClosureCallOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ClosureCallOp transform(CopyContext cc, OpTransformer ot) {
+            return new ClosureCallOp(this, cc);
+        }
+
+        ClosureCallOp(List<Value> args) {
+            super(NAME, args);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            FunctionType ft = (FunctionType) operands().getFirst().type();
+            return ft.returnType();
+        }
+    }
+
+    /**
+     * The terminating return operation, that can model the Java language return statement.
+     * <p>
+     * This operation exits an isolated body.
+     */
+    @OpFactory.OpDeclaration(ReturnOp.NAME)
+    public static final class ReturnOp extends CoreOp
+            implements Op.BodyTerminating, JavaStatement {
+        public static final String NAME = "return";
+
+        public ReturnOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() > 1) {
+                throw new IllegalArgumentException("Operation must have zero or one operand " + def.name());
+            }
+        }
+
+        ReturnOp(ReturnOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ReturnOp transform(CopyContext cc, OpTransformer ot) {
+            return new ReturnOp(this, cc);
+        }
+
+        ReturnOp() {
+            super(NAME, List.of());
+        }
+
+        ReturnOp(Value operand) {
+            super(NAME, List.of(operand));
+        }
+
+        public Value returnValue() {
+            if (operands().size() == 1) {
+                return operands().get(0);
+            } else {
+                // @@@
+                return null;
+            }
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The terminating throw operation, that can model the Java language throw statement.
+     */
+    @OpFactory.OpDeclaration(ThrowOp.NAME)
+    public static final class ThrowOp extends CoreOp
+            implements Op.BodyTerminating, JavaStatement {
+        public static final String NAME = "throw";
+
+        public ThrowOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 1) {
+                throw new IllegalArgumentException("Operation must have one operand " + def.name());
+            }
+        }
+
+        ThrowOp(ThrowOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ThrowOp transform(CopyContext cc, OpTransformer ot) {
+            return new ThrowOp(this, cc);
+        }
+
+        ThrowOp(Value e) {
+            super(NAME, List.of(e));
+        }
+
+        public Value argument() {
+            return operands().get(0);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The assertion operation. Supporting assertions in statement form.
+     */
+    @OpFactory.OpDeclaration(AssertOp.NAME)
+    public static final class AssertOp extends CoreOp
+            implements Op.Nested, JavaStatement {
+        public static final String NAME = "assert";
+        public final List<Body> bodies;
+
+        public AssertOp(ExternalizedOp def) {
+            super(def);
+            var bodies = def.bodyDefinitions().stream().map(b -> b.build(this)).toList();
+            checkBodies(bodies);
+            this.bodies = bodies;
+        }
+
+        public AssertOp(List<Body.Builder> bodies) {
+            super(NAME, List.of());
+            checkBodies(bodies);
+            this.bodies = bodies.stream().map(b -> b.build(this)).toList();
+        }
+
+        AssertOp(AssertOp that, CopyContext cc, OpTransformer ot) {
+
+            super(that, cc);
+            this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
+        }
+
+        private void checkBodies(List<?> bodies) {
+            if (bodies.size() != 1 && bodies.size() != 2) {
+                throw new IllegalArgumentException("Assert must have one or two bodies.");
+            }
+        }
+
+        @Override
+        public Op transform(CopyContext cc, OpTransformer ot) {
+            return new AssertOp(this, cc, ot);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return this.bodies;
+        }
+    }
+
+    /**
+     * The terminating unreachable operation.
+     * <p>
+     * This operation models termination that is unreachable.
+     */
+    @OpFactory.OpDeclaration(UnreachableOp.NAME)
+    public static final class UnreachableOp extends CoreOp
+            implements Op.BodyTerminating {
+        public static final String NAME = "unreachable";
+
+        public UnreachableOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalArgumentException("Operation must zero operands " + def.name());
+            }
+        }
+
+        UnreachableOp(UnreachableOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public UnreachableOp transform(CopyContext cc, OpTransformer ot) {
+            return new UnreachableOp(this, cc);
+        }
+
+        UnreachableOp() {
+            super(NAME, List.of());
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The terminating yield operation.
+     * <p>
+     * This operation models exits from its parent body, yielding at most one value (zero value for yielding unit
+     * or void)
+     */
+    @OpFactory.OpDeclaration(YieldOp.NAME)
+    public static final class YieldOp extends CoreOp
+            implements Op.BodyTerminating {
+        public static final String NAME = "yield";
+
+        public YieldOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() > 1) {
+                throw new IllegalArgumentException("Operation must have zero or one operand " + def.name());
+            }
+        }
+
+        YieldOp(YieldOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public YieldOp transform(CopyContext cc, OpTransformer ot) {
+            return new YieldOp(this, cc);
+        }
+
+        YieldOp() {
+            super(NAME, List.of());
+        }
+
+        YieldOp(List<Value> operands) {
+            super(NAME, operands);
+        }
+
+        public Value yieldValue() {
+            if (operands().size() == 1) {
+                return operands().get(0);
+            } else {
+                // @@@
+                return null;
+            }
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The terminating unconditional branch operation.
+     * <p>
+     * This operation accepts a successor to the next block to branch to.
+     */
+    @OpFactory.OpDeclaration(BranchOp.NAME)
+    public static final class BranchOp extends CoreOp
+            implements Op.BlockTerminating {
+        public static final String NAME = "branch";
+
+        final Block.Reference b;
+
+        public BranchOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty() || def.successors().size() != 1) {
+                throw new IllegalArgumentException("Operation must have zero arguments and one successor" + def.name());
+            }
+
+            this.b = def.successors().get(0);
+        }
+
+        BranchOp(BranchOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.b = cc.getSuccessorOrCreate(that.b);
+        }
+
+        @Override
+        public BranchOp transform(CopyContext cc, OpTransformer ot) {
+            return new BranchOp(this, cc);
+        }
+
+        BranchOp(Block.Reference successor) {
+            super(NAME, List.of());
+
+            this.b = successor;
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return List.of(b);
+        }
+
+        public Block.Reference branch() {
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The terminating conditional branch operation.
+     * <p>
+     * This operation accepts a boolean operand and two successors, the true successor and false successor.
+     * When the operand is true the  true successor is selected, otherwise the false successor is selected.
+     * The selected successor refers to the next block to branch to.
+     */
+    @OpFactory.OpDeclaration(ConditionalBranchOp.NAME)
+    public static final class ConditionalBranchOp extends CoreOp
+            implements Op.BlockTerminating {
+        public static final String NAME = "cbranch";
+
+        final Block.Reference t;
+        final Block.Reference f;
+
+        public ConditionalBranchOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 1 || def.successors().size() != 2) {
+                throw new IllegalArgumentException("Operation must one operand and two successors" + def.name());
+            }
+
+            this.t = def.successors().get(0);
+            this.f = def.successors().get(1);
+        }
+
+        ConditionalBranchOp(ConditionalBranchOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.t = cc.getSuccessorOrCreate(that.t);
+            this.f = cc.getSuccessorOrCreate(that.f);
+        }
+
+        @Override
+        public ConditionalBranchOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConditionalBranchOp(this, cc);
+        }
+
+        ConditionalBranchOp(Value p, Block.Reference t, Block.Reference f) {
+            super(NAME, List.of(p));
+
+            this.t = t;
+            this.f = f;
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return List.of(t, f);
+        }
+
+        public Value predicate() {
+            return operands().get(0);
+        }
+
+        public Block.Reference trueBranch() {
+            return t;
+        }
+
+        public Block.Reference falseBranch() {
+            return f;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The constant operation, that can model Java language literal and constant expressions.
+     */
+    @OpFactory.OpDeclaration(ConstantOp.NAME)
+    public static final class ConstantOp extends CoreOp
+            implements Op.Pure, JavaExpression {
+        public static final String NAME = "constant";
+
+        public static final String ATTRIBUTE_CONSTANT_VALUE = NAME + ".value";
+
+        final Object value;
+        final TypeElement type;
+
+        public static ConstantOp create(ExternalizedOp def) {
+            if (!def.operands().isEmpty()) {
+                throw new IllegalArgumentException("Operation must have zero operands");
+            }
+
+            Object value = def.extractAttributeValue(ATTRIBUTE_CONSTANT_VALUE, true,
+                    v -> processConstantValue(def.resultType(), v));
+            return new ConstantOp(def, value);
+        }
+
+        static Object processConstantValue(TypeElement t, Object value) {
+            if (t.equals(JavaType.BOOLEAN)) {
+                if (value instanceof String s) {
+                    return Boolean.valueOf(s);
+                } else if (value instanceof Boolean) {
+                    return value;
+                }
+            } else if (t.equals(JavaType.BYTE)) {
+                if (value instanceof String s) {
+                    return Byte.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.byteValue();
+                }
+            } else if (t.equals(JavaType.SHORT)) {
+                if (value instanceof String s) {
+                    return Short.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.shortValue();
+                }
+            } else if (t.equals(JavaType.CHAR)) {
+                if (value instanceof String s) {
+                    return s.charAt(0);
+                } else if (value instanceof Character) {
+                    return value;
+                }
+            } else if (t.equals(JavaType.INT)) {
+                if (value instanceof String s) {
+                    return Integer.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.intValue();
+                }
+            } else if (t.equals(JavaType.LONG)) {
+                if (value instanceof String s) {
+                    return Long.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.longValue();
+                }
+            } else if (t.equals(JavaType.FLOAT)) {
+                if (value instanceof String s) {
+                    return Float.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.floatValue();
+                }
+            } else if (t.equals(JavaType.DOUBLE)) {
+                if (value instanceof String s) {
+                    return Double.valueOf(s);
+                } else if (value instanceof Number n) {
+                    return n.doubleValue();
+                }
+            } else if (t.equals(JavaType.J_L_STRING)) {
+                return value == NULL_ATTRIBUTE_VALUE ? null :
+                        value.toString();
+            } else if (t.equals(JavaType.J_L_CLASS)) {
+                return value == NULL_ATTRIBUTE_VALUE ? null : JavaType.ofString(value.toString());
+            } else if (value == NULL_ATTRIBUTE_VALUE) {
+                return null; // null constant
+            }
+
+            throw new UnsupportedOperationException("Unsupported constant type and value: " + t + " " + value);
+        }
+
+        ConstantOp(ExternalizedOp def, Object value) {
+            super(def);
+
+            this.type = def.resultType();
+            this.value = value;
+        }
+
+        ConstantOp(ConstantOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.type = that.type;
+            this.value = that.value;
+        }
+
+        @Override
+        public ConstantOp transform(CopyContext cc, OpTransformer ot) {
+            return new ConstantOp(this, cc);
+        }
+
+        ConstantOp(TypeElement type, Object value) {
+            super(NAME, List.of());
+
+            this.type = type;
+            this.value = value;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> attrs = new HashMap<>(super.attributes());
+            attrs.put("", value == null ? NULL_ATTRIBUTE_VALUE : value);
+            return attrs;
+        }
+
+        public Object value() {
+            return value;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return type;
+        }
+    }
+
+    /**
+     * An operation characteristic indicating the operation's behavior may be emulated using Java reflection.
+     * A descriptor is derived from or declared by the operation that can be resolved at runtime to
+     * an instance of a reflective handle or member. That handle or member can be operated on to
+     * emulate the operation's behavior, specifically as bytecode behavior.
+     */
+    public sealed interface ReflectiveOp {
+    }
+
+    /**
+     * The invoke operation, that can model Java language method invocation expressions.
+     */
+    @OpFactory.OpDeclaration(InvokeOp.NAME)
+    public static final class InvokeOp extends CoreOp
+            implements ReflectiveOp, JavaExpression, JavaStatement {
+        public static final String NAME = "invoke";
+        public static final String ATTRIBUTE_INVOKE_DESCRIPTOR = NAME + ".descriptor";
+
+        final MethodRef invokeDescriptor;
+        final TypeElement resultType;
+
+        public static InvokeOp create(ExternalizedOp def) {
+            MethodRef invokeDescriptor = def.extractAttributeValue(ATTRIBUTE_INVOKE_DESCRIPTOR,
+                    true, v -> switch (v) {
+                        case String s -> MethodRef.ofString(s);
+                        case MethodRef md -> md;
+                        default -> throw new UnsupportedOperationException("Unsupported invoke descriptor value:" + v);
+                    });
+
+            return new InvokeOp(def, invokeDescriptor);
+        }
+
+        InvokeOp(ExternalizedOp def, MethodRef invokeDescriptor) {
+            super(def);
+
+            this.invokeDescriptor = invokeDescriptor;
+            this.resultType = def.resultType();
+        }
+
+        InvokeOp(InvokeOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.invokeDescriptor = that.invokeDescriptor;
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public InvokeOp transform(CopyContext cc, OpTransformer ot) {
+            return new InvokeOp(this, cc);
+        }
+
+        InvokeOp(MethodRef invokeDescriptor, List<Value> args) {
+            this(invokeDescriptor.type().returnType(), invokeDescriptor, args);
+        }
+
+        InvokeOp(TypeElement resultType, MethodRef invokeDescriptor, List<Value> args) {
+            super(NAME, args);
+
+            this.invokeDescriptor = invokeDescriptor;
+            this.resultType = resultType;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", invokeDescriptor);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public MethodRef invokeDescriptor() {
+            return invokeDescriptor;
+        }
+
+        public boolean hasReceiver() {
+            return operands().size() != invokeDescriptor().type().parameterTypes().size();
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The conversion operation, that can model Java language cast expressions
+     * for numerical conversion, or such implicit conversion.
+     */
+    @OpFactory.OpDeclaration(ConvOp.NAME)
+    public static final class ConvOp extends CoreOp
+            implements Op.Pure, JavaExpression {
+        public static final String NAME = "conv";
+
+        final TypeElement resultType;
+
+        public ConvOp(ExternalizedOp def) {
+            super(def);
+
+            this.resultType = def.resultType();
+        }
+
+        ConvOp(ConvOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public Op transform(CopyContext cc, OpTransformer ot) {
+            return new ConvOp(this, cc);
+        }
+
+        ConvOp(TypeElement resultType, Value arg) {
+            super(NAME, List.of(arg));
+
+            this.resultType = resultType;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The new operation, that can models Java language instance creation expressions.
+     */
+    @OpFactory.OpDeclaration(NewOp.NAME)
+    public static final class NewOp extends CoreOp
+            implements ReflectiveOp, JavaExpression, JavaStatement {
+        public static final String NAME = "new";
+        public static final String ATTRIBUTE_NEW_DESCRIPTOR = NAME + ".descriptor";
+
+        final FunctionType constructorType;
+        final TypeElement resultType;
+
+        public static NewOp create(ExternalizedOp def) {
+            FunctionType constructorType = def.extractAttributeValue(ATTRIBUTE_NEW_DESCRIPTOR, true,
+                    v -> switch (v) {
+                        case String s -> {
+                            TypeElement te = CoreTypeFactory.CORE_TYPE_FACTORY
+                                    .constructType(TypeElement.ExternalizedTypeElement.ofString(s));
+                            if (!(te instanceof FunctionType ft)) {
+                                throw new UnsupportedOperationException("Unsupported new descriptor value:" + v);
+                            }
+                            yield ft;
+                        }
+                        case FunctionType ct -> ct;
+                        default -> throw new UnsupportedOperationException("Unsupported new descriptor value:" + v);
+                    });
+            return new NewOp(def, constructorType);
+        }
+
+        NewOp(ExternalizedOp def, FunctionType constructorType) {
+            super(def);
+
+            this.constructorType = constructorType;
+            this.resultType = def.resultType();
+        }
+
+        NewOp(NewOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.constructorType = that.constructorType;
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public NewOp transform(CopyContext cc, OpTransformer ot) {
+            return new NewOp(this, cc);
+        }
+
+        NewOp(FunctionType constructorType, List<Value> args) {
+            this(constructorType.returnType(), constructorType, args);
+        }
+
+        NewOp(TypeElement resultType, FunctionType constructorType, List<Value> args) {
+            super(NAME, args);
+
+            this.constructorType = constructorType;
+            this.resultType = resultType;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", constructorType);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public TypeElement type() {
+            return opType().returnType();
+        }
+
+        public FunctionType constructorType() {
+            return constructorType;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * An operation that performs access.
+     */
+    public sealed interface AccessOp {
+    }
+
+    /**
+     * A field access operation, that can model Java langauge field access expressions.
+     */
+    public sealed abstract static class FieldAccessOp extends CoreOp
+            implements AccessOp, ReflectiveOp {
+        public static final String ATTRIBUTE_FIELD_DESCRIPTOR = "field.descriptor";
+
+        final FieldRef fieldDescriptor;
+
+        FieldAccessOp(ExternalizedOp def, FieldRef fieldDescriptor) {
+            super(def);
+
+            this.fieldDescriptor = fieldDescriptor;
+        }
+
+        FieldAccessOp(FieldAccessOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.fieldDescriptor = that.fieldDescriptor;
+        }
+
+        FieldAccessOp(String name, List<Value> operands,
+                      FieldRef fieldDescriptor) {
+            super(name, operands);
+
+            this.fieldDescriptor = fieldDescriptor;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", fieldDescriptor);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public final FieldRef fieldDescriptor() {
+            return fieldDescriptor;
+        }
+
+        /**
+         * The field load operation, that can model Java language field access expressions combined with load access to
+         * field instance variables.
+         */
+        @OpFactory.OpDeclaration(FieldLoadOp.NAME)
+        public static final class FieldLoadOp extends FieldAccessOp
+                implements Op.Pure, JavaExpression {
+            public static final String NAME = "field.load";
+
+            final TypeElement resultType;
+
+            public static FieldLoadOp create(ExternalizedOp def) {
+                if (def.operands().size() > 1) {
+                    throw new IllegalArgumentException("Operation must accept zero or one operand");
+                }
+
+                FieldRef fieldDescriptor = def.extractAttributeValue(ATTRIBUTE_FIELD_DESCRIPTOR, true,
+                        v -> switch (v) {
+                            case String s -> FieldRef.ofString(s);
+                            case FieldRef fd -> fd;
+                            default ->
+                                    throw new UnsupportedOperationException("Unsupported field descriptor value:" + v);
+                        });
+                return new FieldLoadOp(def, fieldDescriptor);
+            }
+
+            FieldLoadOp(ExternalizedOp opdef, FieldRef fieldDescriptor) {
+                super(opdef, fieldDescriptor);
+
+                resultType = opdef.resultType();
+            }
+
+            FieldLoadOp(FieldLoadOp that, CopyContext cc) {
+                super(that, cc);
+
+                resultType = that.resultType();
+            }
+
+            @Override
+            public FieldLoadOp transform(CopyContext cc, OpTransformer ot) {
+                return new FieldLoadOp(this, cc);
+            }
+
+            // instance
+            FieldLoadOp(TypeElement resultType, FieldRef descriptor, Value receiver) {
+                super(NAME, List.of(receiver), descriptor);
+
+                this.resultType = resultType;
+            }
+
+            // static
+            FieldLoadOp(TypeElement resultType, FieldRef descriptor) {
+                super(NAME, List.of(), descriptor);
+
+                this.resultType = resultType;
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return resultType;
+            }
+        }
+
+        /**
+         * The field store operation, that can model Java language field access expressions combined with store access
+         * to field instance variables.
+         */
+        @OpFactory.OpDeclaration(FieldStoreOp.NAME)
+        public static final class FieldStoreOp extends FieldAccessOp
+                implements JavaExpression, JavaStatement {
+            public static final String NAME = "field.store";
+
+            public static FieldStoreOp create(ExternalizedOp def) {
+                if (def.operands().size() > 2) {
+                    throw new IllegalArgumentException("Operation must accept one or two operands");
+                }
+
+                FieldRef fieldDescriptor = def.extractAttributeValue(ATTRIBUTE_FIELD_DESCRIPTOR, true,
+                        v -> switch (v) {
+                            case String s -> FieldRef.ofString(s);
+                            case FieldRef fd -> fd;
+                            default ->
+                                    throw new UnsupportedOperationException("Unsupported field descriptor value:" + v);
+                        });
+                return new FieldStoreOp(def, fieldDescriptor);
+            }
+
+            FieldStoreOp(ExternalizedOp opdef, FieldRef fieldDescriptor) {
+                super(opdef, fieldDescriptor);
+            }
+
+            FieldStoreOp(FieldStoreOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            @Override
+            public FieldStoreOp transform(CopyContext cc, OpTransformer ot) {
+                return new FieldStoreOp(this, cc);
+            }
+
+            // instance
+            FieldStoreOp(FieldRef descriptor, Value receiver, Value v) {
+                super(NAME,
+                        List.of(receiver, v), descriptor);
+            }
+
+            // static
+            FieldStoreOp(FieldRef descriptor, Value v) {
+                super(NAME,
+                        List.of(v), descriptor);
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return JavaType.VOID;
+            }
+        }
+    }
+
+    /**
+     * The array length operation, that can model Java language field access expressions to the length field of an
+     * array.
+     */
+    @OpFactory.OpDeclaration(ArrayLengthOp.NAME)
+    public static final class ArrayLengthOp extends CoreOp
+            implements ReflectiveOp, JavaExpression {
+        public static final String NAME = "array.length";
+
+        public ArrayLengthOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        ArrayLengthOp(ArrayLengthOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ArrayLengthOp transform(CopyContext cc, OpTransformer ot) {
+            return new ArrayLengthOp(this, cc);
+        }
+
+        ArrayLengthOp(Value array) {
+            super(NAME, List.of(array));
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.INT;
+        }
+    }
+
+    /**
+     * The array access operation, that can model Java language array access expressions.
+     */
+    public sealed abstract static class ArrayAccessOp extends CoreOp
+            implements AccessOp, ReflectiveOp {
+        ArrayAccessOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 2 && def.operands().size() != 3) {
+                throw new IllegalArgumentException("Operation must have 2 or 3 operands");
+            }
+
+            // @@@ validate first operand is an array
+        }
+
+        ArrayAccessOp(ArrayAccessOp that, CopyContext cc) {
+            this(that, cc.getValues(that.operands()));
+        }
+
+        ArrayAccessOp(ArrayAccessOp that, List<Value> operands) {
+            super(that.opName(), operands);
+        }
+
+        ArrayAccessOp(String name,
+                      Value array, Value index, Value v) {
+            super(name, operands(array, index, v));
+        }
+
+        static List<Value> operands(Value array, Value index, Value v) {
+            return v == null
+                    ? List.of(array, index)
+                    : List.of(array, index, v);
+        }
+
+        static TypeElement resultType(Value array, Value v) {
+            if (!(array.type() instanceof ArrayType arrayType)) {
+                throw new IllegalArgumentException("Type is not an array type: " + array.type());
+            }
+
+            // @@@ restrict to indexes of int?
+            TypeElement componentType = arrayType.componentType();
+            if (v == null) {
+                return componentType;
+            } else {
+                return JavaType.VOID;
+            }
+        }
+
+        /**
+         * The array load operation, that can model Java language array expressions combined with load access to the
+         * components of an array.
+         */
+        @OpFactory.OpDeclaration(ArrayLoadOp.NAME)
+        public static final class ArrayLoadOp extends ArrayAccessOp
+                implements Op.Pure, JavaExpression {
+            public static final String NAME = "array.load";
+
+            public ArrayLoadOp(ExternalizedOp def) {
+                super(def);
+            }
+
+            ArrayLoadOp(ArrayLoadOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            @Override
+            public ArrayLoadOp transform(CopyContext cc, OpTransformer ot) {
+                return new ArrayLoadOp(this, cc);
+            }
+
+            ArrayLoadOp(Value array, Value index) {
+                super(NAME, array, index, null);
+            }
+
+            @Override
+            public TypeElement resultType() {
+                Value array = operands().get(0);
+                ArrayType t = (ArrayType) array.type();
+                return t.componentType();
+            }
+        }
+
+        /**
+         * The array store operation, that can model Java language array expressions combined with store access to the
+         * components of an array.
+         */
+        @OpFactory.OpDeclaration(ArrayStoreOp.NAME)
+        public static final class ArrayStoreOp extends ArrayAccessOp
+                implements JavaExpression, JavaStatement {
+            public static final String NAME = "array.store";
+
+            public ArrayStoreOp(ExternalizedOp def) {
+                super(def);
+            }
+
+            ArrayStoreOp(ArrayStoreOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            @Override
+            public ArrayStoreOp transform(CopyContext cc, OpTransformer ot) {
+                return new ArrayStoreOp(this, cc);
+            }
+
+            ArrayStoreOp(Value array, Value index, Value v) {
+                super(NAME, array, index, v);
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return JavaType.VOID;
+            }
+        }
+    }
+
+    /**
+     * The instanceof operation, that can model Java language instanceof expressions when the instanceof keyword is a
+     * type comparison operator.
+     */
+    @OpFactory.OpDeclaration(InstanceOfOp.NAME)
+    public static final class InstanceOfOp extends CoreOp
+            implements Op.Pure, ReflectiveOp, JavaExpression {
+        public static final String NAME = "instanceof";
+        public static final String ATTRIBUTE_TYPE_DESCRIPTOR = NAME + ".descriptor";
+
+        final TypeElement typeDescriptor;
+
+        public static InstanceOfOp create(ExternalizedOp def) {
+            if (def.operands().size() != 1) {
+                throw new IllegalArgumentException("Operation must have one operand " + def.name());
+            }
+
+            TypeElement typeDescriptor = def.extractAttributeValue(ATTRIBUTE_TYPE_DESCRIPTOR, true,
+                    v -> switch (v) {
+                        case String s -> JavaType.ofString(s);
+                        case JavaType td -> td;
+                        default -> throw new UnsupportedOperationException("Unsupported type descriptor value:" + v);
+                    });
+            return new InstanceOfOp(def, typeDescriptor);
+        }
+
+        InstanceOfOp(ExternalizedOp def, TypeElement typeDescriptor) {
+            super(def);
+
+            this.typeDescriptor = typeDescriptor;
+        }
+
+        InstanceOfOp(InstanceOfOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.typeDescriptor = that.typeDescriptor;
+        }
+
+        @Override
+        public InstanceOfOp transform(CopyContext cc, OpTransformer ot) {
+            return new InstanceOfOp(this, cc);
+        }
+
+        InstanceOfOp(TypeElement t, Value v) {
+            super(NAME,
+                    List.of(v));
+
+            this.typeDescriptor = t;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", typeDescriptor);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public TypeElement type() {
+            return typeDescriptor;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.BOOLEAN;
+        }
+    }
+
+    /**
+     * The cast operation, that can model Java language cast expressions for reference types.
+     */
+    @OpFactory.OpDeclaration(CastOp.NAME)
+    public static final class CastOp extends CoreOp
+            implements Op.Pure, ReflectiveOp, JavaExpression {
+        public static final String NAME = "cast";
+        public static final String ATTRIBUTE_TYPE_DESCRIPTOR = NAME + ".descriptor";
+
+        final TypeElement resultType;
+        final TypeElement typeDescriptor;
+
+        public static CastOp create(ExternalizedOp def) {
+            if (def.operands().size() != 1) {
+                throw new IllegalArgumentException("Operation must have one operand " + def.name());
+            }
+
+            TypeElement type = def.extractAttributeValue(ATTRIBUTE_TYPE_DESCRIPTOR, true,
+                    v -> switch (v) {
+                        case String s -> JavaType.ofString(s);
+                        case JavaType td -> td;
+                        default -> throw new UnsupportedOperationException("Unsupported type descriptor value:" + v);
+                    });
+            return new CastOp(def, type);
+        }
+
+        CastOp(ExternalizedOp def, TypeElement typeDescriptor) {
+            super(def);
+
+            this.resultType = def.resultType();
+            this.typeDescriptor = typeDescriptor;
+        }
+
+        CastOp(CastOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.resultType = that.resultType;
+            this.typeDescriptor = that.typeDescriptor;
+        }
+
+        @Override
+        public CastOp transform(CopyContext cc, OpTransformer ot) {
+            return new CastOp(this, cc);
+        }
+
+        CastOp(TypeElement resultType, TypeElement t, Value v) {
+            super(NAME, List.of(v));
+
+            this.resultType = resultType;
+            this.typeDescriptor = t;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", typeDescriptor);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public TypeElement type() {
+            return typeDescriptor;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+
+    /**
+     * A runtime representation of a variable.
+     *
+     * @param <T> the type of the var's value.
+     * @@@ Ideally should never be exposed
+     * @@@ Move to interpreter?
+     */
+    public interface Var<T> {
+        /**
+         * {@return the value of a var}
+         */
+        T value();
+
+        /**
+         * Constructs an instance of a var.
+         *
+         * @param value the initial value of the var.
+         * @param <T>   the type of the var's value.
+         * @return the var
+         */
+        static <T> Var<T> of(T value) {
+            return () -> value;
+        }
+    }
+
+    /**
+     * The variable operation, that can model declarations of Java language local variables, method parameters, or
+     * lambda parameters.
+     */
+    @OpFactory.OpDeclaration(VarOp.NAME)
+    public static final class VarOp extends CoreOp
+            implements JavaStatement {
+        public static final String NAME = "var";
+        public static final String ATTRIBUTE_NAME = NAME + ".name";
+
+        final String varName;
+        final TypeElement resultType;
+
+        public static VarOp create(ExternalizedOp def) {
+            if (def.operands().size() != 1) {
+                throw new IllegalStateException("Operation must have one operand");
+            }
+
+            String name = def.extractAttributeValue(ATTRIBUTE_NAME, true,
+                    v -> switch (v) {
+                        case String s -> s;
+                        default -> throw new UnsupportedOperationException("Unsupported var name value:" + v);
+                    });
+            return new VarOp(def, name);
+        }
+
+        VarOp(ExternalizedOp def, String varName) {
+            super(def);
+
+            this.varName = varName;
+            this.resultType = def.resultType();
+        }
+
+        VarOp(VarOp that, CopyContext cc) {
+            super(that, cc);
+
+            this.varName = that.varName;
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public VarOp transform(CopyContext cc, OpTransformer ot) {
+            return new VarOp(this, cc);
+        }
+
+        VarOp(String varName, Value init) {
+            this(varName, init.type(), init);
+        }
+
+        VarOp(String varName, TypeElement type, Value init) {
+            super(NAME, List.of(init));
+
+            this.varName = varName;
+            this.resultType = VarType.varType(type);
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            if (varName == null) {
+                return super.attributes();
+            }
+
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", varName);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public String varName() {
+            return varName;
+        }
+
+        public TypeElement varType() {
+            return ((VarType) resultType).valueType();
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The var access operation, that can model access to Java language local variables, method parameters, or
+     * lambda parameters.
+     */
+    public sealed abstract static class VarAccessOp extends CoreOp
+            implements AccessOp {
+        VarAccessOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        VarAccessOp(VarAccessOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        VarAccessOp(String name, List<Value> operands) {
+            super(name, operands);
+        }
+
+        public VarOp varOp() {
+            // @@@ At a high-level a Var value occur as a BlockArgument.
+            // Lowering should remove such cases and the var definition should emerge
+            // @@@ This method is used when transforming to pure SSA
+            Result variable = (Result) operands().get(0);
+            return (VarOp) variable.op();
+        }
+
+        static Value checkIsVarOp(Value varValue) {
+            if (!(varValue.type() instanceof VarType)) {
+                throw new IllegalArgumentException("Value's type is not a variable type: " + varValue);
+            }
+            return varValue;
+        }
+
+        /**
+         * The variable load operation, that models a reading variable.
+         */
+        @OpFactory.OpDeclaration(VarLoadOp.NAME)
+        public static final class VarLoadOp extends VarAccessOp
+                implements JavaExpression {
+            public static final String NAME = "var.load";
+
+            public VarLoadOp(ExternalizedOp opdef) {
+                super(opdef);
+
+                if (opdef.operands().size() != 1) {
+                    throw new IllegalArgumentException("Operation must have one operand");
+                }
+                checkIsVarOp(opdef.operands().get(0));
+            }
+
+            VarLoadOp(VarLoadOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            VarLoadOp(List<Value> varValue) {
+                super(NAME, varValue);
+            }
+
+            @Override
+            public VarLoadOp transform(CopyContext cc, OpTransformer ot) {
+                return new VarLoadOp(this, cc);
+            }
+
+            // (Variable)VarType
+            VarLoadOp(Value varValue) {
+                super(NAME, List.of(varValue));
+            }
+
+            @Override
+            public TypeElement resultType() {
+                VarType vt = (VarType) operands().get(0).type();
+                return vt.valueType();
+            }
+        }
+
+        /**
+         * The variable store operation, that can model a variable assignment.
+         */
+        @OpFactory.OpDeclaration(VarStoreOp.NAME)
+        public static final class VarStoreOp extends VarAccessOp
+                implements JavaExpression, JavaStatement {
+            public static final String NAME = "var.store";
+
+            public VarStoreOp(ExternalizedOp opdef) {
+                super(opdef);
+
+                if (opdef.operands().size() != 2) {
+                    throw new IllegalArgumentException("Operation must have two operands");
+                }
+                checkIsVarOp(opdef.operands().get(0));
+            }
+
+            VarStoreOp(VarStoreOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            VarStoreOp(List<Value> values) {
+                super(NAME,
+                        values);
+            }
+
+            @Override
+            public VarStoreOp transform(CopyContext cc, OpTransformer ot) {
+                return new VarStoreOp(this, cc);
+            }
+
+            // (Variable, VarType)void
+            VarStoreOp(Value varValue, Value v) {
+                super(NAME,
+                        List.of(varValue, v));
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return JavaType.VOID;
+            }
+        }
+    }
+
+    // Tuple operations, for modelling return with multiple values
+
+    /**
+     * The tuple operation. A tuple contain a fixed set of values accessible by their component index.
+     */
+    @OpFactory.OpDeclaration(TupleOp.NAME)
+    public static final class TupleOp extends CoreOp {
+        public static final String NAME = "tuple";
+
+        public TupleOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        TupleOp(TupleOp that, CopyContext cc) {
+            this(cc.getValues(that.operands()));
+        }
+
+        @Override
+        public TupleOp transform(CopyContext cc, OpTransformer ot) {
+            return new TupleOp(this, cc);
+        }
+
+        TupleOp(List<? extends Value> componentValues) {
+            super(NAME, componentValues);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return TupleType.tupleTypeFromValues(operands());
+        }
+    }
+
+    /**
+     * The tuple component load operation, that access the component of a tuple at a given, constant, component index.
+     */
+    @OpFactory.OpDeclaration(TupleLoadOp.NAME)
+    public static final class TupleLoadOp extends CoreOp {
+        public static final String NAME = "tuple.load";
+        public static final String ATTRIBUTE_INDEX = NAME + ".index";
+
+        final int index;
+
+        public static TupleLoadOp create(ExternalizedOp def) {
+            if (def.operands().size() != 1) {
+                throw new IllegalStateException("Operation must have one operand");
+            }
+
+            int index = def.extractAttributeValue(ATTRIBUTE_INDEX, true,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported tuple index value:" + v);
+                    });
+            return new TupleLoadOp(def, index);
+        }
+
+        TupleLoadOp(ExternalizedOp def, int index) {
+            super(def);
+
+            // @@@ Validate tuple type and index
+            this.index = index;
+        }
+
+        TupleLoadOp(TupleLoadOp that, CopyContext cc) {
+            this(that, cc.getValues(that.operands()));
+        }
+
+        TupleLoadOp(TupleLoadOp that, List<Value> values) {
+            super(NAME, values);
+
+            this.index = that.index;
+        }
+
+        @Override
+        public TupleLoadOp transform(CopyContext cc, OpTransformer ot) {
+            return new TupleLoadOp(this, cc);
+        }
+
+        TupleLoadOp(Value tupleValue, int index) {
+            super(NAME, List.of(tupleValue));
+
+            this.index = index;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", index);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public int index() {
+            return index;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            Value tupleValue = operands().get(0);
+            TupleType t = (TupleType) tupleValue.type();
+            return t.componentTypes().get(index);
+        }
+    }
+
+    /**
+     * The tuple component set operation, that access the component of a tuple at a given, constant, component index.
+     */
+    @OpFactory.OpDeclaration(TupleWithOp.NAME)
+    public static final class TupleWithOp extends CoreOp {
+        public static final String NAME = "tuple.with";
+        public static final String ATTRIBUTE_INDEX = NAME + ".index";
+
+        final int index;
+
+        public static TupleWithOp create(ExternalizedOp def) {
+            if (def.operands().size() != 2) {
+                throw new IllegalStateException("Operation must have two operands");
+            }
+
+            int index = def.extractAttributeValue(ATTRIBUTE_INDEX, true,
+                    v -> switch (v) {
+                        case String s -> Integer.valueOf(s);
+                        case Integer i -> i;
+                        default -> throw new UnsupportedOperationException("Unsupported tuple index value:" + v);
+                    });
+            return new TupleWithOp(def, index);
+        }
+
+        TupleWithOp(ExternalizedOp def, int index) {
+            super(def);
+
+            // @@@ Validate tuple type and index
+            this.index = index;
+        }
+
+        TupleWithOp(TupleWithOp that, CopyContext cc) {
+            this(that, cc.getValues(that.operands()));
+        }
+
+        TupleWithOp(TupleWithOp that, List<Value> values) {
+            super(NAME, values);
+
+            this.index = that.index;
+        }
+
+        @Override
+        public TupleWithOp transform(CopyContext cc, OpTransformer ot) {
+            return new TupleWithOp(this, cc);
+        }
+
+        TupleWithOp(Value tupleValue, int index, Value value) {
+            super(NAME, List.of(tupleValue, value));
+
+            // @@@ Validate tuple type and index
+            this.index = index;
+        }
+
+        @Override
+        public Map<String, Object> attributes() {
+            HashMap<String, Object> m = new HashMap<>(super.attributes());
+            m.put("", index);
+            return Collections.unmodifiableMap(m);
+        }
+
+        public int index() {
+            return index;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            Value tupleValue = operands().get(0);
+            TupleType tupleType = (TupleType) tupleValue.type();
+            Value value = operands().get(2);
+
+            List<TypeElement> tupleComponentTypes = new ArrayList<>(tupleType.componentTypes());
+            tupleComponentTypes.set(index, value.type());
+            return TupleType.tupleType(tupleComponentTypes);
+        }
+    }
+
+    // @@@ Sealed
+    // Synthetic/hidden type that is the result type of an ExceptionRegionStart operation
+    // and is an operand of an ExceptionRegionEnd operation
+
+    /**
+     * A synthetic exception region type, that is the operation result-type of an exception region
+     * start operation.
+     */
+    // @@@: Create as new type element
+    public interface ExceptionRegion {
+        TypeElement EXCEPTION_REGION_TYPE = JavaType.type(ExceptionRegion.class);
+    }
+
+    /**
+     * The exception region start operation.
+     */
+    @OpFactory.OpDeclaration(ExceptionRegionEnter.NAME)
+    public static final class ExceptionRegionEnter extends CoreOp
+            implements Op.BlockTerminating {
+        public static final String NAME = "exception.region.enter";
+
+        // First successor is the non-exceptional successor whose target indicates
+        // the first block in the exception region.
+        // One or more subsequent successors target the exception catching blocks
+        // each of which have one block argument whose type is an exception type.
+        final List<Block.Reference> s;
+
+        public ExceptionRegionEnter(ExternalizedOp def) {
+            super(def);
+
+            if (def.successors().size() < 2) {
+                throw new IllegalArgumentException("Operation must have two or more successors" + def.name());
+            }
+
+            this.s = List.copyOf(def.successors());
+        }
+
+        ExceptionRegionEnter(ExceptionRegionEnter that, CopyContext cc) {
+            super(that, cc);
+
+            this.s = that.s.stream().map(cc::getSuccessorOrCreate).toList();
+        }
+
+        @Override
+        public ExceptionRegionEnter transform(CopyContext cc, OpTransformer ot) {
+            return new ExceptionRegionEnter(this, cc);
+        }
+
+        ExceptionRegionEnter(List<Block.Reference> s) {
+            super(NAME, List.of());
+
+            if (s.size() < 2) {
+                throw new IllegalArgumentException("Operation must have two or more successors" + opName());
+            }
+
+            this.s = List.copyOf(s);
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return s;
+        }
+
+        public Block.Reference start() {
+            return s.get(0);
+        }
+
+        public List<Block.Reference> catchBlocks() {
+            return s.subList(1, s.size());
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return ExceptionRegion.EXCEPTION_REGION_TYPE;
+        }
+    }
+
+    /**
+     * The exception region end operation.
+     */
+    @OpFactory.OpDeclaration(ExceptionRegionExit.NAME)
+    public static final class ExceptionRegionExit extends CoreOp
+            implements Op.BlockTerminating {
+        public static final String NAME = "exception.region.exit";
+
+        final Block.Reference end;
+
+        public ExceptionRegionExit(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 1) {
+                throw new IllegalArgumentException("Operation must have one operand" + def.name());
+            }
+
+            if (def.successors().size() != 1) {
+                throw new IllegalArgumentException("Operation must have one successor" + def.name());
+            }
+
+            this.end = def.successors().get(0);
+        }
+
+        ExceptionRegionExit(ExceptionRegionExit that, CopyContext cc) {
+            super(that, cc);
+
+            this.end = cc.getSuccessorOrCreate(that.end);
+        }
+
+        @Override
+        public ExceptionRegionExit transform(CopyContext cc, OpTransformer ot) {
+            return new ExceptionRegionExit(this, cc);
+        }
+
+        ExceptionRegionExit(Value exceptionRegion, Block.Reference end) {
+            super(NAME, checkValue(exceptionRegion));
+
+            this.end = end;
+        }
+
+        static List<Value> checkValue(Value er) {
+            if (!(er instanceof Result or && or.op() instanceof ExceptionRegionEnter)) {
+                throw new IllegalArgumentException(
+                        "Operand not the result of an exception.region.start operation: " + er);
+            }
+
+            return List.of(er);
+        }
+
+        @Override
+        public List<Block.Reference> successors() {
+            return List.of(end);
+        }
+
+        public Block.Reference end() {
+            return end;
+        }
+
+        public ExceptionRegionEnter regionStart() {
+            if (operands().get(0) instanceof Result or &&
+                    or.op() instanceof ExceptionRegionEnter ers) {
+                return ers;
+            }
+            throw new InternalError("Should not reach here");
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.VOID;
+        }
+    }
+
+    /**
+     * The String Concatenation Operation
+     */
+
+    @OpFactory.OpDeclaration(ConcatOp.NAME)
+    public static final class ConcatOp extends CoreOp
+            implements Op.Pure, JavaExpression {
+        public static final String NAME = "concat";
+
+        public ConcatOp(ConcatOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        public ConcatOp(ExternalizedOp def) {
+            super(def);
+            if (def.operands().size() != 2) {
+                throw new IllegalArgumentException("Concatenation Operation must have two operands.");
+            }
+        }
+
+        public ConcatOp(Value lhs, Value rhs) {
+            super(ConcatOp.NAME, List.of(lhs, rhs));
+        }
+
+        @Override
+        public Op transform(CopyContext cc, OpTransformer ot) {
+            return new ConcatOp(this, cc);
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.J_L_STRING;
+        }
+    }
+
+    //
+    // Arithmetic ops
+
+    /**
+     * The arithmetic operation.
+     */
+    public sealed static abstract class ArithmeticOperation extends CoreOp
+            implements Op.Pure, JavaExpression {
+        protected ArithmeticOperation(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().isEmpty()) {
+                throw new IllegalArgumentException("Operation must have one or more operands");
+            }
+        }
+
+        protected ArithmeticOperation(ArithmeticOperation that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        protected ArithmeticOperation(String name, List<Value> operands) {
+            super(name, operands);
+        }
+    }
+
+    /**
+     * The test operation.
+     */
+    public sealed static abstract class TestOperation extends CoreOp
+            implements Op.Pure, JavaExpression {
+        protected TestOperation(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().isEmpty()) {
+                throw new IllegalArgumentException("Operation must have one or more operands");
+            }
+        }
+
+        protected TestOperation(TestOperation that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        protected TestOperation(String name, List<Value> operands) {
+            super(name, operands);
+        }
+    }
+
+    /**
+     * The binary arithmetic operation.
+     */
+    public sealed static abstract class BinaryOp extends ArithmeticOperation {
+        protected BinaryOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 2) {
+                throw new IllegalArgumentException("Number of operands must be 2: " + def.operands().size());
+            }
+        }
+
+        protected BinaryOp(BinaryOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        protected BinaryOp(String name, Value lhs, Value rhs) {
+            super(name, List.of(lhs, rhs));
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return operands().get(0).type();
+        }
+    }
+
+    /**
+     * The unary arithmetic operation.
+     */
+    public sealed static abstract class UnaryOp extends ArithmeticOperation {
+        protected UnaryOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 1) {
+                throw new IllegalArgumentException("Number of operands must be 1: " + def.operands().size());
+            }
+        }
+
+        protected UnaryOp(UnaryOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        protected UnaryOp(String name, Value v) {
+            super(name, List.of(v));
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return operands().get(0).type();
+        }
+    }
+
+    /**
+     * The binary test operation.
+     */
+    public sealed static abstract class BinaryTestOp extends TestOperation {
+        protected BinaryTestOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 2) {
+                throw new IllegalArgumentException("Number of operands must be 2: " + def.operands().size());
+            }
+        }
+
+        protected BinaryTestOp(BinaryTestOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        protected BinaryTestOp(String name, Value lhs, Value rhs) {
+            super(name, List.of(lhs, rhs));
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return JavaType.BOOLEAN;
+        }
+    }
+
+    /**
+     * The add operation, that can model the Java language binary {@code +} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(AddOp.NAME)
+    public static final class AddOp extends BinaryOp {
+        public static final String NAME = "add";
+
+        public AddOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        AddOp(AddOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AddOp transform(CopyContext cc, OpTransformer ot) {
+            return new AddOp(this, cc);
+        }
+
+        AddOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The sub operation, that can model the Java language binary {@code -} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(SubOp.NAME)
+    public static final class SubOp extends BinaryOp {
+        public static final String NAME = "sub";
+
+        public SubOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        SubOp(SubOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public SubOp transform(CopyContext cc, OpTransformer ot) {
+            return new SubOp(this, cc);
+        }
+
+        SubOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The mul operation, that can model the Java language binary {@code *} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(MulOp.NAME)
+    public static final class MulOp extends BinaryOp {
+        public static final String NAME = "mul";
+
+        public MulOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        MulOp(MulOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public MulOp transform(CopyContext cc, OpTransformer ot) {
+            return new MulOp(this, cc);
+        }
+
+        MulOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The div operation, that can model the Java language binary {@code /} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(DivOp.NAME)
+    public static final class DivOp extends BinaryOp {
+        public static final String NAME = "div";
+
+        public DivOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        DivOp(DivOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public DivOp transform(CopyContext cc, OpTransformer ot) {
+            return new DivOp(this, cc);
+        }
+
+        DivOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The mod operation, that can model the Java language binary {@code %} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(ModOp.NAME)
+    public static final class ModOp extends BinaryOp {
+        public static final String NAME = "mod";
+
+        public ModOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        ModOp(ModOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public ModOp transform(CopyContext cc, OpTransformer ot) {
+            return new ModOp(this, cc);
+        }
+
+        ModOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The bitwise/logical or operation, that can model the Java language binary {@code |} operator for integral types
+     * and booleans
+     */
+    @OpFactory.OpDeclaration(OrOp.NAME)
+    public static final class OrOp extends BinaryOp {
+        public static final String NAME = "or";
+
+        public OrOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        OrOp(OrOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public OrOp transform(CopyContext cc, OpTransformer ot) {
+            return new OrOp(this, cc);
+        }
+
+        OrOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The bitwise/logical and operation, that can model the Java language binary {@code &} operator for integral types
+     * and booleans
+     */
+    @OpFactory.OpDeclaration(AndOp.NAME)
+    public static final class AndOp extends BinaryOp {
+        public static final String NAME = "and";
+
+        public AndOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        AndOp(AndOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AndOp transform(CopyContext cc, OpTransformer ot) {
+            return new AndOp(this, cc);
+        }
+
+        AndOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The xor operation, that can model the Java language binary {@code ^} operator for integral types
+     * and booleans
+     */
+    @OpFactory.OpDeclaration(XorOp.NAME)
+    public static final class XorOp extends BinaryOp {
+        public static final String NAME = "xor";
+
+        public XorOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        XorOp(XorOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public XorOp transform(CopyContext cc, OpTransformer ot) {
+            return new XorOp(this, cc);
+        }
+
+        XorOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The (logical) shift left operation, that can model the Java language binary {@code <<} operator for integral types
+     */
+    @OpFactory.OpDeclaration(LshlOp.NAME)
+    public static final class LshlOp extends BinaryOp {
+        public static final String NAME = "lshl";
+
+        public LshlOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        LshlOp(LshlOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LshlOp transform(CopyContext cc, OpTransformer ot) {
+            return new LshlOp(this, cc);
+        }
+
+        LshlOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The (arithmetic) shift right operation, that can model the Java language binary {@code >>} operator for integral types
+     */
+    @OpFactory.OpDeclaration(AshrOp.NAME)
+    public static final class AshrOp extends CoreOp.BinaryOp {
+        public static final String NAME = "ashr";
+
+        public AshrOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        AshrOp(AshrOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public AshrOp transform(CopyContext cc, OpTransformer ot) {
+            return new AshrOp(this, cc);
+        }
+
+        AshrOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The unsigned (logical) shift right operation, that can model the Java language binary {@code >>>} operator for integral types
+     */
+    @OpFactory.OpDeclaration(LshrOp.NAME)
+    public static final class LshrOp extends CoreOp.BinaryOp {
+        public static final String NAME = "lshr";
+
+        public LshrOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        LshrOp(LshrOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LshrOp transform(CopyContext cc, OpTransformer ot) {
+            return new LshrOp(this, cc);
+        }
+
+        LshrOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The neg operation, that can model the Java language unary {@code -} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(NegOp.NAME)
+    public static final class NegOp extends UnaryOp {
+        public static final String NAME = "neg";
+
+        public NegOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        NegOp(NegOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public NegOp transform(CopyContext cc, OpTransformer ot) {
+            return new NegOp(this, cc);
+        }
+
+        NegOp(Value v) {
+            super(NAME, v);
+        }
+    }
+
+    /**
+     * The not operation, that can model the Java language unary {@code !} operator for boolean types
+     */
+    @OpFactory.OpDeclaration(NotOp.NAME)
+    public static final class NotOp extends UnaryOp {
+        public static final String NAME = "not";
+
+        public NotOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        NotOp(NotOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public NotOp transform(CopyContext cc, OpTransformer ot) {
+            return new NotOp(this, cc);
+        }
+
+        NotOp(Value v) {
+            super(NAME, v);
+        }
+    }
+
+    /**
+     * The equals operation, that can model the Java language equality {@code ==} operator for numeric, boolean
+     * and reference types
+     */
+    @OpFactory.OpDeclaration(EqOp.NAME)
+    public static final class EqOp extends BinaryTestOp {
+        public static final String NAME = "eq";
+
+        public EqOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        EqOp(EqOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public EqOp transform(CopyContext cc, OpTransformer ot) {
+            return new EqOp(this, cc);
+        }
+
+        EqOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The not equals operation, that can model the Java language equality {@code !=} operator for numeric, boolean
+     * and reference types
+     */
+    @OpFactory.OpDeclaration(NeqOp.NAME)
+    public static final class NeqOp extends BinaryTestOp {
+        public static final String NAME = "neq";
+
+        public NeqOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        NeqOp(NeqOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public NeqOp transform(CopyContext cc, OpTransformer ot) {
+            return new NeqOp(this, cc);
+        }
+
+        NeqOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The greater than operation, that can model the Java language relational {@code >} operator for numeric types
+     */
+    @OpFactory.OpDeclaration(GtOp.NAME)
+    public static final class GtOp extends BinaryTestOp {
+        public static final String NAME = "gt";
+
+        public GtOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        GtOp(GtOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public GtOp transform(CopyContext cc, OpTransformer ot) {
+            return new GtOp(this, cc);
+        }
+
+        GtOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The greater than or equal to operation, that can model the Java language relational {@code >=} operator for
+     * numeric types
+     */
+    @OpFactory.OpDeclaration(GeOp.NAME)
+    public static final class GeOp extends BinaryTestOp {
+        public static final String NAME = "ge";
+
+        public GeOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        GeOp(GeOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public GeOp transform(CopyContext cc, OpTransformer ot) {
+            return new GeOp(this, cc);
+        }
+
+        GeOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The less than operation, that can model the Java language relational {@code <} operator for
+     * numeric types
+     */
+    @OpFactory.OpDeclaration(LtOp.NAME)
+    public static final class LtOp extends BinaryTestOp {
+        public static final String NAME = "lt";
+
+        public LtOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        LtOp(LtOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LtOp transform(CopyContext cc, OpTransformer ot) {
+            return new LtOp(this, cc);
+        }
+
+        LtOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+    /**
+     * The less than or equal to operation, that can model the Java language relational {@code <=} operator for
+     * numeric types
+     */
+    @OpFactory.OpDeclaration(LeOp.NAME)
+    public static final class LeOp extends BinaryTestOp {
+        public static final String NAME = "le";
+
+        public LeOp(ExternalizedOp opdef) {
+            super(opdef);
+        }
+
+        LeOp(LeOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public LeOp transform(CopyContext cc, OpTransformer ot) {
+            return new LeOp(this, cc);
+        }
+
+        LeOp(Value lhs, Value rhs) {
+            super(NAME, lhs, rhs);
+        }
+    }
+
+
+    /**
+     * A factory for core operations.
+     */
+    // @@@ Compute lazily
+    public static final OpFactory FACTORY = OpFactory.OP_FACTORY.get(CoreOp.class);
+
+    /**
+     * Creates a function operation builder
+     *
+     * @param funcName the function name
+     * @param funcType the function type
+     * @return the function operation builder
+     */
+    public static FuncOp.Builder func(String funcName, FunctionType funcType) {
+        return new FuncOp.Builder(null, funcName, funcType);
+    }
+
+    /**
+     * Creates a function operation
+     *
+     * @param funcName the function name
+     * @param body     the function body
+     * @return the function operation
+     */
+    public static FuncOp func(String funcName, Body.Builder body) {
+        return new FuncOp(funcName, body);
+    }
+
+    /**
+     * Creates a function call operation
+     *
+     * @param funcName the name of the function operation
+     * @param funcType the function type
+     * @param args     the function arguments
+     * @return the function call operation
+     */
+    public static FuncCallOp funcCall(String funcName, FunctionType funcType, Value... args) {
+        return funcCall(funcName, funcType, List.of(args));
+    }
+
+    /**
+     * Creates a function call operation
+     *
+     * @param funcName the name of the function operation
+     * @param funcType the function type
+     * @param args     the function arguments
+     * @return the function call operation
+     */
+    public static FuncCallOp funcCall(String funcName, FunctionType funcType, List<Value> args) {
+        return new FuncCallOp(funcName, funcType.returnType(), args);
+    }
+
+    /**
+     * Creates a function call operation
+     *
+     * @param func the target function
+     * @param args the function arguments
+     * @return the function call operation
+     */
+    public static FuncCallOp funcCall(FuncOp func, Value... args) {
+        return funcCall(func, List.of(args));
+    }
+
+    /**
+     * Creates a function call operation
+     *
+     * @param func the target function
+     * @param args the function argments
+     * @return the function call operation
+     */
+    public static FuncCallOp funcCall(FuncOp func, List<Value> args) {
+        return new FuncCallOp(func.funcName(), func.invokableType().returnType(), args);
+    }
+
+    /**
+     * Creates a module operation.
+     *
+     * @param functions the functions of the module operation
+     * @return the module operation
+     */
+    public static ModuleOp module(FuncOp... functions) {
+        return module(List.of(functions));
+    }
+
+    /**
+     * Creates a module operation.
+     *
+     * @param functions the functions of the module operation
+     * @return the module operation
+     */
+    public static ModuleOp module(List<FuncOp> functions) {
+        return new ModuleOp(List.copyOf(functions));
+    }
+
+    /**
+     * Creates a quoted operation.
+     *
+     * @param ancestorBody the ancestor of the body of the quoted operation
+     * @param opFunc       a function that accepts the body of the quoted operation and returns the operation to be quoted
+     * @return the quoted operation
+     */
+    public static QuotedOp quoted(Body.Builder ancestorBody,
+                                  Function<Block.Builder, Op> opFunc) {
+        Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.VOID);
+        Block.Builder block = body.entryBlock();
+        block.op(_yield(
+                block.op(opFunc.apply(block))));
+        return new QuotedOp(body);
+    }
+
+    /**
+     * Creates a quoted operation.
+     *
+     * @param body quoted operation body
+     * @return the quoted operation
+     */
+    public static QuotedOp quoted(Body.Builder body) {
+        return new QuotedOp(body);
+    }
+
+    /**
+     * Creates a lambda operation.
+     *
+     * @param ancestorBody        the ancestor of the body of the lambda operation
+     * @param funcType            the lambda operation's function type
+     * @param functionalInterface the lambda operation's functional interface type
+     * @return the lambda operation
+     */
+    public static LambdaOp.Builder lambda(Body.Builder ancestorBody,
+                                          FunctionType funcType, TypeElement functionalInterface) {
+        return new LambdaOp.Builder(ancestorBody, funcType, functionalInterface);
+    }
+
+    /**
+     * Creates a lambda operation.
+     *
+     * @param functionalInterface the lambda operation's functional interface type
+     * @param body                the body of the lambda operation
+     * @return the lambda operation
+     */
+    public static LambdaOp lambda(TypeElement functionalInterface, Body.Builder body) {
+        return new LambdaOp(functionalInterface, body);
+    }
+
+    /**
+     * Creates a closure operation.
+     *
+     * @param ancestorBody the ancestor of the body of the closure operation
+     * @param funcType     the closure operation's function type
+     * @return the closure operation
+     */
+    public static ClosureOp.Builder closure(Body.Builder ancestorBody,
+                                            FunctionType funcType) {
+        return new ClosureOp.Builder(ancestorBody, funcType);
+    }
+
+    /**
+     * Creates a closure operation.
+     *
+     * @param body the body of the closure operation
+     * @return the closure operation
+     */
+    public static ClosureOp closure(Body.Builder body) {
+        return new ClosureOp(body);
+    }
+
+    /**
+     * Creates a closure call operation.
+     *
+     * @param args the closure arguments. The first argument is the closure operation to be called
+     * @return the closure call operation
+     */
+    // @@@: Is this the right signature?
+    public static ClosureCallOp closureCall(Value... args) {
+        return closureCall(List.of(args));
+    }
+
+    /**
+     * Creates a closure call operation.
+     *
+     * @param args the closure arguments. The first argument is the closure operation to be called
+     * @return the closure call operation
+     */
+    // @@@: Is this the right signature?
+    public static ClosureCallOp closureCall(List<Value> args) {
+        return new ClosureCallOp(args);
+    }
+
+    /**
+     * Creates an exception region enter operation
+     *
+     * @param start    the exception region block
+     * @param catchers the blocks handling exceptions thrown by the region block
+     * @return the exception region enter operation
+     */
+    public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, Block.Reference... catchers) {
+        return exceptionRegionEnter(start, List.of(catchers));
+    }
+
+    /**
+     * Creates an exception region enter operation
+     *
+     * @param start    the exception region block
+     * @param catchers the blocks handling exceptions thrown by the region block
+     * @return the exception region enter operation
+     */
+    public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, List<Block.Reference> catchers) {
+        List<Block.Reference> s = new ArrayList<>();
+        s.add(start);
+        s.addAll(catchers);
+        return new ExceptionRegionEnter(s);
+    }
+
+    /**
+     * Creates an exception region exit operation
+     *
+     * @param exceptionRegion the exception region to be exited
+     * @param end             the block to which control is transferred after the exception region is exited
+     * @return the exception region exit operation
+     */
+    public static ExceptionRegionExit exceptionRegionExit(Value exceptionRegion, Block.Reference end) {
+        return new ExceptionRegionExit(exceptionRegion, end);
+    }
+
+    /**
+     * Creates a return operation.
+     *
+     * @return the return operation
+     */
+    public static ReturnOp _return() {
+        return new ReturnOp();
+    }
+
+    /**
+     * Creates a return operation.
+     *
+     * @param returnValue the return value
+     * @return the return operation
+     */
+    public static ReturnOp _return(Value returnValue) {
+        return new ReturnOp(returnValue);
+    }
+
+    /**
+     * Creates a throw operation.
+     *
+     * @param exceptionValue the thrown value
+     * @return the throw operation
+     */
+    public static ThrowOp _throw(Value exceptionValue) {
+        return new ThrowOp(exceptionValue);
+    }
+
+    /**
+     * Creates an unreachable operation.
+     *
+     * @return the unreachable operation
+     */
+    public static UnreachableOp unreachable() {
+        return new UnreachableOp();
+    }
+
+    /**
+     * Creates a yield operation.
+     *
+     * @return the yield operation
+     */
+    public static YieldOp _yield() {
+        return new YieldOp();
+    }
+
+    /**
+     * Creates a yield operation.
+     *
+     * @param yieldValue the yielded value
+     * @return the yield operation
+     */
+    public static YieldOp _yield(Value yieldValue) {
+        return new YieldOp(List.of(yieldValue));
+    }
+
+    /**
+     * Creates an assert operation.
+     *
+     * @param bodies the nested bodies
+     * @return the assert operation
+     */
+    public static AssertOp _assert(List<Body.Builder> bodies) {
+        return new AssertOp(bodies);
+    }
+
+    /**
+     * Creates an unconditional break operation.
+     *
+     * @param target the jump target
+     * @return the unconditional break operation
+     */
+    public static BranchOp branch(Block.Reference target) {
+        return new BranchOp(target);
+    }
+
+    /**
+     * Creates a conditional break operation.
+     *
+     * @param condValue   the test value of the conditional break operation
+     * @param trueTarget  the jump target when the test value evaluates to true
+     * @param falseTarget the jump target when the test value evaluates to false
+     * @return the conditional break operation
+     */
+    public static ConditionalBranchOp conditionalBranch(Value condValue,
+                                                        Block.Reference trueTarget, Block.Reference falseTarget) {
+        return new ConditionalBranchOp(condValue, trueTarget, falseTarget);
+    }
+
+    /**
+     * Creates a constant operation.
+     *
+     * @param type  the constant type
+     * @param value the constant value
+     * @return the constant operation
+     */
+    public static ConstantOp constant(TypeElement type, Object value) {
+        return new ConstantOp(type, value);
+    }
+
+    /**
+     * Creates an invoke operation.
+     *
+     * @param invokeDescriptor the invocation descriptor
+     * @param args             the invoke parameters
+     * @return the invoke operation
+     */
+    public static InvokeOp invoke(MethodRef invokeDescriptor, Value... args) {
+        return new InvokeOp(invokeDescriptor, List.of(args));
+    }
+
+    /**
+     * Creates an invoke operation.
+     *
+     * @param invokeDescriptor the invocation descriptor
+     * @param args             the invoke parameters
+     * @return the invoke operation
+     */
+    public static InvokeOp invoke(MethodRef invokeDescriptor, List<Value> args) {
+        return new InvokeOp(invokeDescriptor, args);
+    }
+
+    /**
+     * Creates an invoke operation.
+     *
+     * @param returnType       the invocation return type
+     * @param invokeDescriptor the invocation descriptor
+     * @param args             the invoke parameters
+     * @return the invoke operation
+     */
+    public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, Value... args) {
+        return new InvokeOp(returnType, invokeDescriptor, List.of(args));
+    }
+
+    /**
+     * Creates an invoke operation.
+     *
+     * @param returnType       the invocation return type
+     * @param invokeDescriptor the invocation descriptor
+     * @param args             the invoke parameters
+     * @return the invoke operation
+     */
+    public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, List<Value> args) {
+        return new InvokeOp(returnType, invokeDescriptor, args);
+    }
+
+    /**
+     * Creates a conversion operation.
+     *
+     * @param to   the conversion target type
+     * @param from the value to be converted
+     * @return the conversion operation
+     */
+    public static ConvOp conv(TypeElement to, Value from) {
+        return new ConvOp(to, from);
+    }
+
+    /**
+     * Creates an instance creation operation.
+     *
+     * @param constructorType the constructor type
+     * @param args            the constructor arguments
+     * @return the instance creation operation
+     */
+    public static NewOp _new(FunctionType constructorType, Value... args) {
+        return _new(constructorType, List.of(args));
+    }
+
+    /**
+     * Creates an instance creation operation.
+     *
+     * @param constructorType the constructor type
+     * @param args            the constructor arguments
+     * @return the instance creation operation
+     */
+    public static NewOp _new(FunctionType constructorType, List<Value> args) {
+        return new NewOp(constructorType, args);
+    }
+
+    /**
+     * Creates an instance creation operation.
+     *
+     * @param returnType      the instance type
+     * @param constructorType the constructor type
+     * @param args            the constructor arguments
+     * @return the instance creation operation
+     */
+    public static NewOp _new(TypeElement returnType, FunctionType constructorType,
+                             Value... args) {
+        return _new(returnType, constructorType, List.of(args));
+    }
+
+    /**
+     * Creates an instance creation operation.
+     *
+     * @param returnType      the instance type
+     * @param constructorType the constructor type
+     * @param args            the constructor arguments
+     * @return the instance creation operation
+     */
+    public static NewOp _new(TypeElement returnType, FunctionType constructorType,
+                             List<Value> args) {
+        return new NewOp(returnType, constructorType, args);
+    }
+
+    /**
+     * Creates an array creation operation.
+     *
+     * @param arrayType the array type
+     * @param length    the array size
+     * @return the array creation operation
+     */
+    public static NewOp newArray(TypeElement arrayType, Value length) {
+        return _new(FunctionType.functionType(arrayType, JavaType.INT), length);
+    }
+
+    // @@@ Add field load/store overload with explicit fieldType
+
+    /**
+     * Creates a field load operation to a non-static field.
+     *
+     * @param descriptor the field descriptor
+     * @param receiver   the receiver value
+     * @return the field load operation
+     */
+    public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor, Value receiver) {
+        return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor, receiver);
+    }
+
+    /**
+     * Creates a field load operation to a non-static field.
+     *
+     * @param resultType the result type of the operation
+     * @param descriptor the field descriptor
+     * @param receiver   the receiver value
+     * @return the field load operation
+     */
+    public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor, Value receiver) {
+        return new FieldAccessOp.FieldLoadOp(resultType, descriptor, receiver);
+    }
+
+    /**
+     * Creates a field load operation to a static field.
+     *
+     * @param descriptor the field descriptor
+     * @return the field load operation
+     */
+    public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor) {
+        return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor);
+    }
+
+    /**
+     * Creates a field load operation to a static field.
+     *
+     * @param resultType the result type of the operation
+     * @param descriptor the field descriptor
+     * @return the field load operation
+     */
+    public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor) {
+        return new FieldAccessOp.FieldLoadOp(resultType, descriptor);
+    }
+
+    /**
+     * Creates a field store operation to a non-static field.
+     *
+     * @param descriptor the field descriptor
+     * @param receiver   the receiver value
+     * @param v          the value to store
+     * @return the field store operation
+     */
+    public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value receiver, Value v) {
+        return new FieldAccessOp.FieldStoreOp(descriptor, receiver, v);
+    }
+
+    /**
+     * Creates a field load operation to a static field.
+     *
+     * @param descriptor the field descriptor
+     * @param v          the value to store
+     * @return the field store operation
+     */
+    public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value v) {
+        return new FieldAccessOp.FieldStoreOp(descriptor, v);
+    }
+
+    /**
+     * Creates an array length operation.
+     *
+     * @param array the array value
+     * @return the array length operation
+     */
+    public static ArrayLengthOp arrayLength(Value array) {
+        return new ArrayLengthOp(array);
+    }
+
+    /**
+     * Creates an array load operation.
+     *
+     * @param array the array value
+     * @param index the index value
+     * @return the array load operation
+     */
+    public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index) {
+        return new ArrayAccessOp.ArrayLoadOp(array, index);
+    }
+
+    /**
+     * Creates an array store operation.
+     *
+     * @param array the array value
+     * @param index the index value
+     * @param v     the value to store
+     * @return the array store operation
+     */
+    public static ArrayAccessOp.ArrayStoreOp arrayStoreOp(Value array, Value index, Value v) {
+        return new ArrayAccessOp.ArrayStoreOp(array, index, v);
+    }
+
+    /**
+     * Creates an instanceof operation.
+     *
+     * @param t the type to test against
+     * @param v the value to test
+     * @return the instanceof operation
+     */
+    public static InstanceOfOp instanceOf(TypeElement t, Value v) {
+        return new InstanceOfOp(t, v);
+    }
+
+    /**
+     * Creates a cast operation.
+     *
+     * @param resultType the result type of the operation
+     * @param v          the value to cast
+     * @return the cast operation
+     */
+    public static CastOp cast(TypeElement resultType, Value v) {
+        return new CastOp(resultType, resultType, v);
+    }
+
+    /**
+     * Creates a cast operation.
+     *
+     * @param resultType the result type of the operation
+     * @param t          the type to cast to
+     * @param v          the value to cast
+     * @return the cast operation
+     */
+    public static CastOp cast(TypeElement resultType, JavaType t, Value v) {
+        return new CastOp(resultType, t, v);
+    }
+
+    /**
+     * Creates a var operation.
+     *
+     * @param init the initial value of the var
+     * @return the var operation
+     */
+    public static VarOp var(Value init) {
+        return var(null, init);
+    }
+
+    /**
+     * Creates a var operation.
+     *
+     * @param name the name of the var
+     * @param init the initial value of the var
+     * @return the var operation
+     */
+    public static VarOp var(String name, Value init) {
+        return new VarOp(name, init);
+    }
+
+    /**
+     * Creates a var operation.
+     *
+     * @param name the name of the var
+     * @param type the type of the var's value
+     * @param init the initial value of the var
+     * @return the var operation
+     */
+    public static VarOp var(String name, TypeElement type, Value init) {
+        return new VarOp(name, type, init);
+    }
+
+    /**
+     * Creates a var load operation.
+     *
+     * @param varValue the var value
+     * @return the var load operation
+     */
+    public static VarAccessOp.VarLoadOp varLoad(Value varValue) {
+        return new VarAccessOp.VarLoadOp(varValue);
+    }
+
+    /**
+     * Creates a var store operation.
+     *
+     * @param varValue the var value
+     * @param v        the value to store in the var
+     * @return the var store operation
+     */
+    public static VarAccessOp.VarStoreOp varStore(Value varValue, Value v) {
+        return new VarAccessOp.VarStoreOp(varValue, v);
+    }
+
+    /**
+     * Creates a tuple operation.
+     *
+     * @param componentValues the values of tuple (in order)
+     * @return the tuple operation
+     */
+    public static TupleOp tuple(Value... componentValues) {
+        return tuple(List.of(componentValues));
+    }
+
+    /**
+     * Creates a tuple operation.
+     *
+     * @param componentValues the values of tuple (in order)
+     * @return the tuple operation
+     */
+    public static TupleOp tuple(List<? extends Value> componentValues) {
+        return new TupleOp(componentValues);
+    }
+
+    /**
+     * Creates a tuple load operation.
+     *
+     * @param tuple the tuple value
+     * @param index the component index value
+     * @return the tuple load operation
+     */
+    public static TupleLoadOp tupleLoad(Value tuple, int index) {
+        return new TupleLoadOp(tuple, index);
+    }
+
+    /**
+     * Creates a tuple with operation.
+     *
+     * @param tuple the tuple value
+     * @param index the component index value
+     * @param value the component value
+     * @return the tuple with operation
+     */
+    public static TupleWithOp tupleWith(Value tuple, int index, Value value) {
+        return new TupleWithOp(tuple, index, value);
+    }
+
+    //
+    // Arithmetic ops
+
+    /**
+     * Creates an add operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the add operation
+     */
+    public static BinaryOp add(Value lhs, Value rhs) {
+        return new AddOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a sub operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the sub operation
+     */
+    public static BinaryOp sub(Value lhs, Value rhs) {
+        return new SubOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a mul operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the mul operation
+     */
+    public static BinaryOp mul(Value lhs, Value rhs) {
+        return new MulOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a div operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the div operation
+     */
+    public static BinaryOp div(Value lhs, Value rhs) {
+        return new DivOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a mod operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the mod operation
+     */
+    public static BinaryOp mod(Value lhs, Value rhs) {
+        return new ModOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a bitwise/logical or operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the or operation
+     */
+    public static BinaryOp or(Value lhs, Value rhs) {
+        return new OrOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a bitwise/logical and operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the and operation
+     */
+    public static BinaryOp and(Value lhs, Value rhs) {
+        return new AndOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a bitwise/logical xor operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the xor operation
+     */
+    public static BinaryOp xor(Value lhs, Value rhs) {
+        return new XorOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a left shift operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the xor operation
+     */
+    public static BinaryOp lshl(Value lhs, Value rhs) {
+        return new LshlOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a right shift operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the xor operation
+     */
+    public static BinaryOp ashr(Value lhs, Value rhs) {
+        return new AshrOp(lhs, rhs);
+    }
+
+    /**
+     * Creates an unsigned right shift operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the xor operation
+     */
+    public static BinaryOp lshr(Value lhs, Value rhs) {
+        return new LshrOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a neg operation.
+     *
+     * @param v the operand
+     * @return the neg operation
+     */
+    public static UnaryOp neg(Value v) {
+        return new NegOp(v);
+    }
+
+    /**
+     * Creates a not operation.
+     *
+     * @param v the operand
+     * @return the not operation
+     */
+    public static UnaryOp not(Value v) {
+        return new NotOp(v);
+    }
+
+
+    /**
+     * Creates an equals comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the equals comparison operation
+     */
+    public static BinaryTestOp eq(Value lhs, Value rhs) {
+        return new EqOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a not equals comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the not equals comparison operation
+     */
+    public static BinaryTestOp neq(Value lhs, Value rhs) {
+        return new NeqOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a greater than comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the greater than comparison operation
+     */
+    public static BinaryTestOp gt(Value lhs, Value rhs) {
+        return new GtOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a greater than or equals to comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the greater than or equals to comparison operation
+     */
+    public static BinaryTestOp ge(Value lhs, Value rhs) {
+        return new GeOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a less than comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the less than comparison operation
+     */
+    public static BinaryTestOp lt(Value lhs, Value rhs) {
+        return new LtOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a less than or equals to comparison operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the less than or equals to comparison operation
+     */
+    public static BinaryTestOp le(Value lhs, Value rhs) {
+        return new LeOp(lhs, rhs);
+    }
+
+    /**
+     * Creates a string concatenation operation.
+     *
+     * @param lhs the first operand
+     * @param rhs the second operand
+     * @return the string concatenation operation
+     */
+    public static ConcatOp concat(Value lhs, Value rhs) {
+        return new ConcatOp(lhs, rhs);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/op/ExtendedOp.java b/src/java.base/share/classes/java/lang/reflect/code/op/ExtendedOp.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/op/ExtendedOp.java
@@ -0,0 +1,3212 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.op;
+
+import java.lang.constant.ClassDesc;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.type.ArrayType;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.RecordTypeRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.type.TupleType;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.VarType;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.JavaType.*;
+
+/**
+ * The top-level operation class for the enclosed set of extended operations.
+ * <p>
+ * A code model, produced by the Java compiler from Java program source, may consist of extended operations and core
+ * operations. Such a model represents the same Java program and preserves the program meaning as defined by the
+ * Java Language Specification
+ * <p>
+ * Extended operations model specific Java language constructs, often those with structured control flow and nested
+ * code. Each operation is transformable into a sequence of core operations, commonly referred to as lowering. Those
+ * that implement {@link Op.Lowerable} can transform themselves and will transform associated extended operations
+ * that are not explicitly lowerable.
+ * <p>
+ * A code model, produced by the Java compiler from source, and consisting of extended operations and core operations
+ * can be transformed to one consisting only of core operations, where all extended operations are lowered. This
+ * transformation preserves programing meaning. The resulting lowered code model also represents the same Java program.
+ */
+public sealed abstract class ExtendedOp extends ExternalizableOp {
+    // Split string to ensure the name does not get rewritten
+    // when the script copies this source to the jdk.compiler module
+    static final String PACKAGE_NAME = "java.lang" + ".reflect.code";
+
+    static final String ExtendedOp_CLASS_NAME = PACKAGE_NAME + "." + ExtendedOp.class.getSimpleName();
+
+    protected ExtendedOp(Op that, CopyContext cc) {
+        super(that, cc);
+    }
+
+    protected ExtendedOp(String name, List<? extends Value> operands) {
+        super(name, operands);
+    }
+
+    protected ExtendedOp(ExternalizableOp.ExternalizedOp def) {
+        super(def);
+    }
+
+
+    /**
+     * The label operation, that can model Java language statements with label identifiers.
+     */
+    public sealed static abstract class JavaLabelOp extends ExtendedOp
+            implements Op.Lowerable, Op.BodyTerminating, JavaStatement {
+        JavaLabelOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() > 1) {
+                throw new IllegalArgumentException("Operation must have zero or one operand " + def.name());
+            }
+        }
+
+        JavaLabelOp(JavaLabelOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        JavaLabelOp(String name, Value label) {
+            super(name, checkLabel(label));
+        }
+
+        static List<Value> checkLabel(Value label) {
+            return label == null ? List.of() : List.of(label);
+        }
+
+        Op innerMostEnclosingTarget() {
+            /*
+                A break statement with no label attempts to transfer control to the
+                innermost enclosing switch, while, do, or for statement; this enclosing statement,
+                which is called the break target, then immediately completes normally.
+
+                A break statement with label Identifier attempts to transfer control to the
+                enclosing labeled statement (14.7) that has the same Identifier as its label;
+                this enclosing statement, which is called the break target, then immediately completes normally.
+                In this case, the break target need not be a switch, while, do, or for statement.
+             */
+
+            // No label
+            // Get innermost enclosing loop operation
+            // @@@ expand to support innermost enclosing switch operation
+            Op op = this;
+            Body b;
+            do {
+                b = op.ancestorBody();
+                op = b.parentOp();
+                if (op == null) {
+                    throw new IllegalStateException("No enclosing loop");
+                }
+            } while (!(op instanceof Op.Loop));
+            // } while (!(op instanceof Op.Loop lop));
+            // error: variable lop might not have been initialized
+            Op.Loop lop = (Op.Loop) op;
+            return lop.loopBody() == b ? op : null;
+        }
+
+        boolean isUnlabeled() {
+            return operands().isEmpty();
+        }
+
+        Op target() {
+            // If unlabeled then find the nearest enclosing op
+            // Otherwise obtain the label target
+            if (isUnlabeled()) {
+                return innerMostEnclosingTarget();
+            }
+
+            Value value = operands().get(0);
+            if (value instanceof Result r && r.op().ancestorBody().parentOp() instanceof JavaLabeledOp lop) {
+                return lop.target();
+            } else {
+                throw new IllegalStateException("Bad label value: " + value + " " + ((Result) value).op());
+            }
+        }
+
+        Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
+            Op opt = target();
+            BranchTarget t = getBranchTarget(b.context(), opt);
+            if (t != null) {
+                b.op(branch(f.apply(t).successor()));
+            } else {
+                throw new IllegalStateException("No branch target for operation: " + opt);
+            }
+            return b;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The break operation, that can model Java language break statements with label identifiers.
+     */
+    @OpFactory.OpDeclaration(JavaBreakOp.NAME)
+    public static final class JavaBreakOp extends JavaLabelOp {
+        public static final String NAME = "java.break";
+
+        public JavaBreakOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaBreakOp(JavaBreakOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public JavaBreakOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaBreakOp(this, cc);
+        }
+
+        JavaBreakOp(Value label) {
+            super(NAME, label);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            return lower(b, BranchTarget::breakBlock);
+        }
+    }
+
+    /**
+     * The break operation, that can model Java language continue statements with label identifiers.
+     */
+    @OpFactory.OpDeclaration(JavaContinueOp.NAME)
+    public static final class JavaContinueOp extends JavaLabelOp {
+        public static final String NAME = "java.continue";
+
+        public JavaContinueOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaContinueOp(JavaContinueOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public JavaContinueOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaContinueOp(this, cc);
+        }
+
+        JavaContinueOp(Value label) {
+            super(NAME, label);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            return lower(b, BranchTarget::continueBlock);
+        }
+    }
+
+    record BranchTarget(Block.Builder breakBlock, Block.Builder continueBlock) {
+    }
+
+    static final String BRANCH_TARGET_MAP_PROPERTY_KEY = "BRANCH_TARGET_MAP";
+
+    static BranchTarget getBranchTarget(CopyContext cc, Op op) {
+        @SuppressWarnings("unchecked")
+        Map<Op, BranchTarget> m = (Map<Op, BranchTarget>) cc.getProperty(BRANCH_TARGET_MAP_PROPERTY_KEY);
+        if (m != null) {
+            return m.get(op);
+        }
+
+        return null;
+    }
+
+    static void setBranchTarget(CopyContext cc, Op label, BranchTarget t) {
+        @SuppressWarnings("unchecked")
+        Map<Op, BranchTarget> x = (Map<Op, BranchTarget>) cc.computePropertyIfAbsent(
+                BRANCH_TARGET_MAP_PROPERTY_KEY, k -> new HashMap<>());
+        x.put(label, t);
+    }
+
+    /**
+     * The yield operation, that can model Java language yield statements.
+     */
+    @OpFactory.OpDeclaration(JavaYieldOp.NAME)
+    public static final class JavaYieldOp extends ExtendedOp
+            implements Op.BodyTerminating, JavaStatement {
+        public static final String NAME = "java.yield";
+
+        public JavaYieldOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaYieldOp(JavaYieldOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public JavaYieldOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaYieldOp(this, cc);
+        }
+
+        JavaYieldOp() {
+            super(NAME,
+                    List.of());
+        }
+
+        JavaYieldOp(Value operand) {
+            super(NAME, List.of(operand));
+        }
+
+        public Value yieldValue() {
+            if (operands().size() == 1) {
+                return operands().get(0);
+            } else {
+                // @@@
+                return null;
+            }
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The block operation, that can model Java language blocks.
+     */
+    @OpFactory.OpDeclaration(JavaBlockOp.NAME)
+    // @@@ Support synchronized attribute
+    public static final class JavaBlockOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaStatement {
+        public static final String NAME = "java.block";
+
+        final Body body;
+
+        public JavaBlockOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Operation must have no operands");
+            }
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        JavaBlockOp(JavaBlockOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public JavaBlockOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaBlockOp(this, cc, ot);
+        }
+
+        // @@@ Support non-void result type
+        JavaBlockOp(Body.Builder bodyC) {
+            super(NAME, List.of());
+
+            this.body = bodyC.build(this);
+            if (!body.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Body should return void: " + body.bodyType());
+            }
+            if (!body.bodyType().parameterTypes().isEmpty()) {
+                throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        public Body body() {
+            return body;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder exit = b.block();
+            setBranchTarget(b.context(), this, new BranchTarget(exit, null));
+
+            b.transformBody(body, List.of(), opT.andThen((block, op) -> {
+                if (op instanceof YieldOp) {
+                    block.op(branch(exit.successor()));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    if (op instanceof Lowerable lop) {
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                }
+                return block;
+            }));
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The labeled operation, that can model Java language labeled statements.
+     */
+    @OpFactory.OpDeclaration(JavaLabeledOp.NAME)
+    public static final class JavaLabeledOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaStatement {
+        public static final String NAME = "java.labeled";
+
+        final Body body;
+
+        public JavaLabeledOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Operation must have no operands");
+            }
+
+            this.body = def.bodyDefinitions().get(0).build(this);
+        }
+
+        JavaLabeledOp(JavaLabeledOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public JavaLabeledOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaLabeledOp(this, cc, ot);
+        }
+
+        JavaLabeledOp(Body.Builder bodyC) {
+            super(NAME, List.of());
+
+            this.body = bodyC.build(this);
+            if (!body.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Body should return void: " + body.bodyType());
+            }
+            if (!body.bodyType().parameterTypes().isEmpty()) {
+                throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(body);
+        }
+
+        public Op label() {
+            return body.entryBlock().firstOp();
+        }
+
+        public Op target() {
+            return body.entryBlock().nextOp(label());
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder exit = b.block();
+            setBranchTarget(b.context(), this, new BranchTarget(exit, null));
+
+            AtomicBoolean first = new AtomicBoolean();
+            b.transformBody(body, List.of(), opT.andThen((block, op) -> {
+                // Drop first operation that corresponds to the label
+                if (!first.get()) {
+                    first.set(true);
+                    return block;
+                }
+
+                if (op instanceof YieldOp) {
+                    block.op(branch(exit.successor()));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    if (op instanceof Lowerable lop) {
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                }
+                return block;
+            }));
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The if operation, that can model Java language if, if-then, and if-then-else statements.
+     */
+    @OpFactory.OpDeclaration(JavaIfOp.NAME)
+    public static final class JavaIfOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaStatement {
+
+        static final FunctionType PREDICATE_TYPE = FunctionType.functionType(BOOLEAN);
+
+        static final FunctionType ACTION_TYPE = FunctionType.VOID;
+
+        public static class IfBuilder {
+            final Body.Builder ancestorBody;
+            final List<Body.Builder> bodies;
+
+            IfBuilder(Body.Builder ancestorBody) {
+                this.ancestorBody = ancestorBody;
+                this.bodies = new ArrayList<>();
+            }
+
+            public ThenBuilder _if(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return new ThenBuilder(ancestorBody, bodies);
+            }
+        }
+
+        public static class ThenBuilder {
+            final Body.Builder ancestorBody;
+            final List<Body.Builder> bodies;
+
+            public ThenBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
+                this.ancestorBody = ancestorBody;
+                this.bodies = bodies;
+            }
+
+            public ElseIfBuilder then(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return new ElseIfBuilder(ancestorBody, bodies);
+            }
+
+            public ElseIfBuilder then() {
+                Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
+                body.entryBlock().op(_yield());
+                bodies.add(body);
+
+                return new ElseIfBuilder(ancestorBody, bodies);
+            }
+        }
+
+        public static class ElseIfBuilder {
+            final Body.Builder ancestorBody;
+            final List<Body.Builder> bodies;
+
+            public ElseIfBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
+                this.ancestorBody = ancestorBody;
+                this.bodies = bodies;
+            }
+
+            public ThenBuilder elseif(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return new ThenBuilder(ancestorBody, bodies);
+            }
+
+            public JavaIfOp _else(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return new JavaIfOp(bodies);
+            }
+
+            public JavaIfOp _else() {
+                Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
+                body.entryBlock().op(_yield());
+                bodies.add(body);
+
+                return new JavaIfOp(bodies);
+            }
+        }
+
+        public static final String NAME = "java.if";
+
+        final List<Body> bodies;
+
+        public JavaIfOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Operation must have no operands");
+            }
+
+            // @@@ Validate
+
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+        }
+
+        JavaIfOp(JavaIfOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.bodies = that.bodies.stream()
+                    .map(b -> b.transform(cc, ot).build(this)).toList();
+        }
+
+        @Override
+        public JavaIfOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaIfOp(this, cc, ot);
+        }
+
+        JavaIfOp(List<Body.Builder> bodyCs) {
+            super(NAME, List.of());
+
+            // Normalize by adding an empty else action
+            // @@@ Is this needed?
+            if (bodyCs.size() % 2 == 0) {
+                bodyCs = new ArrayList<>(bodyCs);
+                Body.Builder end = Body.Builder.of(bodyCs.get(0).ancestorBody(),
+                        FunctionType.VOID);
+                end.entryBlock().op(_yield());
+                bodyCs.add(end);
+            }
+
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+
+            if (bodies.size() < 2) {
+                throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
+            }
+            for (int i = 0; i < bodies.size(); i += 2) {
+                Body action;
+                if (i == bodies.size() - 1) {
+                    action = bodies.get(i);
+                } else {
+                    action = bodies.get(i + 1);
+                    Body fromPred = bodies.get(i);
+                    if (!fromPred.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
+                        throw new IllegalArgumentException("Illegal predicate body descriptor: " + fromPred.bodyType());
+                    }
+                }
+                if (!action.bodyType().equals(FunctionType.VOID)) {
+                    throw new IllegalArgumentException("Illegal action body descriptor: " + action.bodyType());
+                }
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder exit = b.block();
+            setBranchTarget(b.context(), this, new BranchTarget(exit, null));
+
+            // Create predicate and action blocks
+            List<Block.Builder> builders = new ArrayList<>();
+            for (int i = 0; i < bodies.size(); i += 2) {
+                if (i == bodies.size() - 1) {
+                    builders.add(b.block());
+                } else {
+                    builders.add(i == 0 ? b : b.block());
+                    builders.add(b.block());
+                }
+            }
+
+            for (int i = 0; i < bodies.size(); i += 2) {
+                Body actionBody;
+                Block.Builder action;
+                if (i == bodies.size() - 1) {
+                    actionBody = bodies.get(i);
+                    action = builders.get(i);
+                } else {
+                    Body predBody = bodies.get(i);
+                    actionBody = bodies.get(i + 1);
+
+                    Block.Builder pred = builders.get(i);
+                    action = builders.get(i + 1);
+                    Block.Builder next = builders.get(i + 2);
+
+                    pred.transformBody(predBody, List.of(), opT.andThen((block, op) -> {
+                        if (op instanceof YieldOp yo) {
+                            block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
+                                    action.successor(), next.successor()));
+                        } else if (op instanceof Lowerable lop) {
+                            // @@@ Composition of lowerable ops
+                            block = lop.lower(block, opT);
+                        } else {
+                            block.op(op);
+                        }
+                        return block;
+                    }));
+                }
+
+                action.transformBody(actionBody, List.of(), opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp) {
+                        block.op(branch(exit.successor()));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        if (op instanceof Lowerable lop) {
+                            block = lop.lower(block, opT);
+                        } else {
+                            block.op(op);
+                        }
+                    }
+                    return block;
+                }));
+            }
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The switch expression operation, that can model Java language switch expressions.
+     */
+    @OpFactory.OpDeclaration(JavaSwitchExpressionOp.NAME)
+    public static final class JavaSwitchExpressionOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaExpression {
+        public static final String NAME = "java.switch.expression";
+
+        final TypeElement resultType;
+        final List<Body> bodies;
+
+        public JavaSwitchExpressionOp(ExternalizedOp def) {
+            super(def);
+
+            if (def.operands().size() != 1) {
+                throw new IllegalStateException("Operation must have one operand");
+            }
+
+            // @@@ Validate
+
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+            this.resultType = def.resultType();
+        }
+
+        JavaSwitchExpressionOp(JavaSwitchExpressionOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.bodies = that.bodies.stream()
+                    .map(b -> b.transform(cc, ot).build(this)).toList();
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public JavaSwitchExpressionOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaSwitchExpressionOp(this, cc, ot);
+        }
+
+        JavaSwitchExpressionOp(TypeElement resultType, Value target, List<Body.Builder> bodyCs) {
+            super(NAME, List.of(target));
+
+            // Each case is modelled as a contiguous pair of bodies
+            // The first body models the case labels, and the second models the case expression or statements
+            // The labels body has a parameter whose type is target operand's type and returns a boolean value
+            // The statements/expression body has no parameters and returns the result whose type is the result of
+            // the switch expression
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+            // @@@ when resultType is null, we assume statements/expressions bodies have the same yieldType
+            this.resultType = resultType == null ? bodies.get(1).yieldType() : resultType;
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The switch fall-through operation, that can model fall-through to the next statement in the switch block after
+     * the last statement of the current switch label.
+     */
+    @OpFactory.OpDeclaration(JavaSwitchFallthroughOp.NAME)
+    public static final class JavaSwitchFallthroughOp extends ExtendedOp
+            implements Op.BodyTerminating {
+        public static final String NAME = "java.switch.fallthrough";
+
+        public JavaSwitchFallthroughOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaSwitchFallthroughOp(JavaSwitchFallthroughOp that, CopyContext cc) {
+            super(that, cc);
+        }
+
+        @Override
+        public JavaSwitchFallthroughOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaSwitchFallthroughOp(this, cc);
+        }
+
+        JavaSwitchFallthroughOp() {
+            super(NAME, List.of());
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The for operation, that can model a Java language for statement.
+     */
+    @OpFactory.OpDeclaration(JavaForOp.NAME)
+    public static final class JavaForOp extends ExtendedOp
+            implements Op.Loop, Op.Lowerable, JavaStatement {
+
+        public static final class InitBuilder {
+            final Body.Builder ancestorBody;
+            final List<? extends TypeElement> initTypes;
+
+            InitBuilder(Body.Builder ancestorBody,
+                        List<? extends TypeElement> initTypes) {
+                this.ancestorBody = ancestorBody;
+                this.initTypes = initTypes.stream().map(VarType::varType).toList();
+            }
+
+            public JavaForOp.CondBuilder init(Consumer<Block.Builder> c) {
+                Body.Builder init = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(TupleType.tupleType(initTypes)));
+                c.accept(init.entryBlock());
+
+                return new CondBuilder(ancestorBody, initTypes, init);
+            }
+        }
+
+        public static final class CondBuilder {
+            final Body.Builder ancestorBody;
+            final List<? extends TypeElement> initTypes;
+            final Body.Builder init;
+
+            public CondBuilder(Body.Builder ancestorBody,
+                               List<? extends TypeElement> initTypes,
+                               Body.Builder init) {
+                this.ancestorBody = ancestorBody;
+                this.initTypes = initTypes;
+                this.init = init;
+            }
+
+            public JavaForOp.UpdateBuilder cond(Consumer<Block.Builder> c) {
+                Body.Builder cond = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(BOOLEAN, initTypes));
+                c.accept(cond.entryBlock());
+
+                return new UpdateBuilder(ancestorBody, initTypes, init, cond);
+            }
+        }
+
+        public static final class UpdateBuilder {
+            final Body.Builder ancestorBody;
+            final List<? extends TypeElement> initTypes;
+            final Body.Builder init;
+            final Body.Builder cond;
+
+            public UpdateBuilder(Body.Builder ancestorBody,
+                                 List<? extends TypeElement> initTypes,
+                                 Body.Builder init, Body.Builder cond) {
+                this.ancestorBody = ancestorBody;
+                this.initTypes = initTypes;
+                this.init = init;
+                this.cond = cond;
+            }
+
+            public JavaForOp.BodyBuilder cond(Consumer<Block.Builder> c) {
+                Body.Builder update = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(VOID, initTypes));
+                c.accept(update.entryBlock());
+
+                return new BodyBuilder(ancestorBody, initTypes, init, cond, update);
+            }
+
+        }
+
+        public static final class BodyBuilder {
+            final Body.Builder ancestorBody;
+            final List<? extends TypeElement> initTypes;
+            final Body.Builder init;
+            final Body.Builder cond;
+            final Body.Builder update;
+
+            public BodyBuilder(Body.Builder ancestorBody,
+                               List<? extends TypeElement> initTypes,
+                               Body.Builder init, Body.Builder cond, Body.Builder update) {
+                this.ancestorBody = ancestorBody;
+                this.initTypes = initTypes;
+                this.init = init;
+                this.cond = cond;
+                this.update = update;
+            }
+
+            public JavaForOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(VOID, initTypes));
+                c.accept(body.entryBlock());
+
+                return new JavaForOp(init, cond, update, body);
+            }
+        }
+
+        static final String NAME = "java.for";
+
+        final Body init;
+        final Body cond;
+        final Body update;
+        final Body body;
+
+        public static JavaForOp create(ExternalizedOp def) {
+            return new JavaForOp(def);
+        }
+
+        public JavaForOp(ExternalizedOp def) {
+            super(def);
+
+            this.init = def.bodyDefinitions().get(0).build(this);
+            this.cond = def.bodyDefinitions().get(1).build(this);
+            this.update = def.bodyDefinitions().get(2).build(this);
+            this.body = def.bodyDefinitions().get(3).build(this);
+        }
+
+        JavaForOp(JavaForOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.init = that.init.transform(cc, ot).build(this);
+            this.cond = that.cond.transform(cc, ot).build(this);
+            this.update = that.update.transform(cc, ot).build(this);
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public JavaForOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaForOp(this, cc, ot);
+        }
+
+        JavaForOp(Body.Builder initC,
+                  Body.Builder condC,
+                  Body.Builder updateC,
+                  Body.Builder bodyC) {
+            super(NAME, List.of());
+
+            this.init = initC.build(this);
+
+            this.cond = condC.build(this);
+
+            this.update = updateC.build(this);
+            if (!update.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Update should return void: " + update.bodyType());
+            }
+
+            this.body = bodyC.build(this);
+            if (!body.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Body should return void: " + body.bodyType());
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(init, cond, update, body);
+        }
+
+        public Body init() {
+            return init;
+        }
+
+        public Body cond() {
+            return cond;
+        }
+
+        public Body update() {
+            return update;
+        }
+
+        @Override
+        public Body loopBody() {
+            return body;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder header = b.block();
+            Block.Builder body = b.block();
+            Block.Builder update = b.block();
+            Block.Builder exit = b.block();
+
+            List<Value> initValues = new ArrayList<>();
+            // @@@ Init body has one yield operation yielding
+            //  void, a single variable, or a tuple of one or more variables
+            b.transformBody(init, List.of(), opT.andThen((block, op) -> {
+                if (op instanceof CoreOp.TupleOp) {
+                    // Drop Tuple if a yielded
+                    boolean isResult = op.result().uses().size() == 1 &&
+                            op.result().uses().stream().allMatch(r -> r.op() instanceof YieldOp);
+                    if (!isResult) {
+                        block.op(op);
+                    }
+                } else if (op instanceof YieldOp yop) {
+                    if (yop.yieldValue() == null) {
+                        block.op(branch(header.successor()));
+                        return block;
+                    } else if (yop.yieldValue() instanceof Result or) {
+                        if (or.op() instanceof CoreOp.TupleOp top) {
+                            initValues.addAll(block.context().getValues(top.operands()));
+                        } else {
+                            initValues.addAll(block.context().getValues(yop.operands()));
+                        }
+                        block.op(branch(header.successor()));
+                        return block;
+                    }
+
+                    throw new IllegalStateException("Bad yield operation");
+                } else {
+                    // @@@ Composition of lowerable ops
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            header.transformBody(cond, initValues, opT.andThen((block, op) -> {
+                if (op instanceof YieldOp yo) {
+                    block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
+                            body.successor(), exit.successor()));
+                } else if (op instanceof Lowerable lop) {
+                    // @@@ Composition of lowerable ops
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            setBranchTarget(b.context(), this, new BranchTarget(exit, update));
+
+            body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
+                // @@@ Composition of lowerable ops
+                if (op instanceof Lowerable lop) {
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            update.transformBody(this.update, initValues, opT.andThen((block, op) -> {
+                if (op instanceof YieldOp) {
+                    block.op(branch(header.successor()));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The enhanced for operation, that can model a Java language enhanced for statement.
+     */
+    @OpFactory.OpDeclaration(JavaEnhancedForOp.NAME)
+    public static final class JavaEnhancedForOp extends ExtendedOp
+            implements Op.Loop, Op.Lowerable, JavaStatement {
+
+        public static final class ExpressionBuilder {
+            final Body.Builder ancestorBody;
+            final TypeElement iterableType;
+            final TypeElement elementType;
+
+            ExpressionBuilder(Body.Builder ancestorBody,
+                              TypeElement iterableType, TypeElement elementType) {
+                this.ancestorBody = ancestorBody;
+                this.iterableType = iterableType;
+                this.elementType = elementType;
+            }
+
+            public DefinitionBuilder expression(Consumer<Block.Builder> c) {
+                Body.Builder expression = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(iterableType));
+                c.accept(expression.entryBlock());
+
+                return new DefinitionBuilder(ancestorBody, elementType, expression);
+            }
+        }
+
+        public static final class DefinitionBuilder {
+            final Body.Builder ancestorBody;
+            final TypeElement elementType;
+            final Body.Builder expression;
+
+            DefinitionBuilder(Body.Builder ancestorBody,
+                              TypeElement elementType, Body.Builder expression) {
+                this.ancestorBody = ancestorBody;
+                this.elementType = elementType;
+                this.expression = expression;
+            }
+
+            public BodyBuilder definition(Consumer<Block.Builder> c) {
+                return definition(elementType, c);
+            }
+
+            public BodyBuilder definition(TypeElement bodyElementType, Consumer<Block.Builder> c) {
+                Body.Builder definition = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(bodyElementType, elementType));
+                c.accept(definition.entryBlock());
+
+                return new BodyBuilder(ancestorBody, elementType, expression, definition);
+            }
+        }
+
+        public static final class BodyBuilder {
+            final Body.Builder ancestorBody;
+            final TypeElement elementType;
+            final Body.Builder expression;
+            final Body.Builder definition;
+
+            BodyBuilder(Body.Builder ancestorBody,
+                        TypeElement elementType, Body.Builder expression, Body.Builder definition) {
+                this.ancestorBody = ancestorBody;
+                this.elementType = elementType;
+                this.expression = expression;
+                this.definition = definition;
+            }
+
+            public JavaEnhancedForOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(VOID, elementType));
+                c.accept(body.entryBlock());
+
+                return new JavaEnhancedForOp(expression, definition, body);
+            }
+        }
+
+        static final String NAME = "java.enhancedFor";
+
+        final Body expression;
+        final Body init;
+        final Body body;
+
+        public static JavaEnhancedForOp create(ExternalizedOp def) {
+            return new JavaEnhancedForOp(def);
+        }
+
+        public JavaEnhancedForOp(ExternalizedOp def) {
+            super(def);
+
+            this.expression = def.bodyDefinitions().get(0).build(this);
+            this.init = def.bodyDefinitions().get(1).build(this);
+            this.body = def.bodyDefinitions().get(2).build(this);
+        }
+
+        JavaEnhancedForOp(JavaEnhancedForOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.expression = that.expression.transform(cc, ot).build(this);
+            this.init = that.init.transform(cc, ot).build(this);
+            this.body = that.body.transform(cc, ot).build(this);
+        }
+
+        @Override
+        public JavaEnhancedForOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaEnhancedForOp(this, cc, ot);
+        }
+
+        JavaEnhancedForOp(Body.Builder expressionC, Body.Builder initC, Body.Builder bodyC) {
+            super(NAME, List.of());
+
+            this.expression = expressionC.build(this);
+            if (expression.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Expression should return non-void value: " + expression.bodyType());
+            }
+            if (!expression.bodyType().parameterTypes().isEmpty()) {
+                throw new IllegalArgumentException("Expression should have zero parameters: " + expression.bodyType());
+            }
+
+            this.init = initC.build(this);
+            if (init.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Initialization should return non-void value: " + init.bodyType());
+            }
+            if (init.bodyType().parameterTypes().size() != 1) {
+                throw new IllegalArgumentException("Initialization should have one parameter: " + init.bodyType());
+            }
+
+            this.body = bodyC.build(this);
+            if (!body.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Body should return void: " + body.bodyType());
+            }
+            if (body.bodyType().parameterTypes().size() != 1) {
+                throw new IllegalArgumentException("Body should have one parameter: " + body.bodyType());
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return List.of(expression, init, body);
+        }
+
+        public Body expression() {
+            return expression;
+        }
+
+        public Body initialization() {
+            return init;
+        }
+
+        @Override
+        public Body loopBody() {
+            return body;
+        }
+
+        static final MethodRef ITERABLE_ITERATOR = MethodRef.method(Iterable.class, "iterator", Iterator.class);
+        static final MethodRef ITERATOR_HAS_NEXT = MethodRef.method(Iterator.class, "hasNext", boolean.class);
+        static final MethodRef ITERATOR_NEXT = MethodRef.method(Iterator.class, "next", Object.class);
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            JavaType elementType = (JavaType) init.entryBlock().parameters().get(0).type();
+            boolean isArray = expression.bodyType().returnType() instanceof ArrayType;
+
+            Block.Builder preHeader = b.block(expression.bodyType().returnType());
+            Block.Builder header = b.block(isArray ? List.of(INT) : List.of());
+            Block.Builder init = b.block();
+            Block.Builder body = b.block();
+            Block.Builder exit = b.block();
+
+            b.transformBody(expression, List.of(), opT.andThen((block, op) -> {
+                if (op instanceof YieldOp yop) {
+                    Value loopSource = block.context().getValue(yop.yieldValue());
+                    block.op(branch(preHeader.successor(loopSource)));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            if (isArray) {
+                Value array = preHeader.parameters().get(0);
+                Value arrayLength = preHeader.op(arrayLength(array));
+                Value i = preHeader.op(constant(INT, 0));
+                preHeader.op(branch(header.successor(i)));
+
+                i = header.parameters().get(0);
+                Value p = header.op(lt(i, arrayLength));
+                header.op(conditionalBranch(p, init.successor(), exit.successor()));
+
+                Value e = init.op(arrayLoadOp(array, i));
+                List<Value> initValues = new ArrayList<>();
+                // @@@ Init body has one yield operation yielding a single variable
+                init.transformBody(this.init, List.of(e), (block, op) -> {
+                    if (op instanceof YieldOp yop) {
+                        initValues.addAll(block.context().getValues(yop.operands()));
+                        block.op(branch(body.successor()));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        block.op(op);
+                    }
+                    return block;
+                });
+
+                Block.Builder update = b.block();
+                setBranchTarget(b.context(), this, new BranchTarget(exit, update));
+
+                body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
+                    // @@@ Composition of lowerable ops
+                    if (op instanceof Lowerable lop) {
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                    return block;
+                }));
+
+                i = update.op(add(i, update.op(constant(INT, 1))));
+                update.op(branch(header.successor(i)));
+            } else {
+                JavaType iterable = parameterized(type(Iterator.class), elementType);
+                Value iterator = preHeader.op(CoreOp.invoke(iterable, ITERABLE_ITERATOR, preHeader.parameters().get(0)));
+                preHeader.op(branch(header.successor()));
+
+                Value p = header.op(CoreOp.invoke(ITERATOR_HAS_NEXT, iterator));
+                header.op(conditionalBranch(p, init.successor(), exit.successor()));
+
+                Value e = init.op(CoreOp.invoke(elementType, ITERATOR_NEXT, iterator));
+                List<Value> initValues = new ArrayList<>();
+                init.transformBody(this.init, List.of(e), opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp yop) {
+                        initValues.addAll(block.context().getValues(yop.operands()));
+                        block.op(branch(body.successor()));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        block.op(op);
+                    }
+                    return block;
+                }));
+
+                setBranchTarget(b.context(), this, new BranchTarget(exit, header));
+
+                body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
+                    // @@@ Composition of lowerable ops
+                    if (op instanceof Lowerable lop) {
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                    return block;
+                }));
+            }
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The while operation, that can model a Java language while statement.
+     */
+    @OpFactory.OpDeclaration(JavaWhileOp.NAME)
+    public static final class JavaWhileOp extends ExtendedOp
+            implements Op.Loop, Op.Lowerable, JavaStatement {
+
+        public static class PredicateBuilder {
+            final Body.Builder ancestorBody;
+
+            PredicateBuilder(Body.Builder ancestorBody) {
+                this.ancestorBody = ancestorBody;
+            }
+
+            public JavaWhileOp.BodyBuilder predicate(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
+                c.accept(body.entryBlock());
+
+                return new JavaWhileOp.BodyBuilder(ancestorBody, body);
+            }
+        }
+
+        public static class BodyBuilder {
+            final Body.Builder ancestorBody;
+            private final Body.Builder predicate;
+
+            BodyBuilder(Body.Builder ancestorBody, Body.Builder predicate) {
+                this.ancestorBody = ancestorBody;
+                this.predicate = predicate;
+            }
+
+            public JavaWhileOp body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.VOID);
+                c.accept(body.entryBlock());
+
+                return new JavaWhileOp(List.of(predicate, body));
+            }
+        }
+
+        private static final String NAME = "java.while";
+
+        private final List<Body> bodies;
+
+        public JavaWhileOp(ExternalizedOp def) {
+            super(def);
+
+            // @@@ Validate
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+        }
+
+        JavaWhileOp(List<Body.Builder> bodyCs) {
+            super(NAME, List.of());
+
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+        }
+
+        JavaWhileOp(Body.Builder predicate, Body.Builder body) {
+            super(NAME, List.of());
+
+            Objects.requireNonNull(body);
+
+            this.bodies = Stream.of(predicate, body).filter(Objects::nonNull)
+                    .map(bc -> bc.build(this)).toList();
+
+            // @@@ This will change with pattern bindings
+            if (!bodies.get(0).bodyType().equals(FunctionType.functionType(BOOLEAN))) {
+                throw new IllegalArgumentException(
+                        "Predicate body descriptor should be " + FunctionType.functionType(BOOLEAN) +
+                                " but is " + bodies.get(0).bodyType());
+            }
+            if (!bodies.get(1).bodyType().equals(FunctionType.VOID)) {
+                throw new IllegalArgumentException(
+                        "Body descriptor should be " + FunctionType.functionType(VOID) +
+                                " but is " + bodies.get(1).bodyType());
+            }
+        }
+
+        JavaWhileOp(JavaWhileOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.bodies = that.bodies.stream()
+                    .map(b -> b.transform(cc, ot).build(this)).toList();
+        }
+
+        @Override
+        public JavaWhileOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaWhileOp(this, cc, ot);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        public Body predicateBody() {
+            return bodies.get(0);
+        }
+
+        @Override
+        public Body loopBody() {
+            return bodies.get(1);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder header = b.block();
+            Block.Builder body = b.block();
+            Block.Builder exit = b.block();
+
+            b.op(branch(header.successor()));
+
+            header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
+                if (op instanceof CoreOp.YieldOp yo) {
+                    block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
+                            body.successor(), exit.successor()));
+                } else if (op instanceof Lowerable lop) {
+                    // @@@ Composition of lowerable ops
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            setBranchTarget(b.context(), this, new BranchTarget(exit, header));
+
+            body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
+                // @@@ Composition of lowerable ops
+                if (op instanceof Lowerable lop) {
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The do-while operation, that can model a Java language do statement.
+     */
+    // @@@ Unify JavaDoWhileOp and JavaWhileOp with common abstract superclass
+    @OpFactory.OpDeclaration(JavaDoWhileOp.NAME)
+    public static final class JavaDoWhileOp extends ExtendedOp
+            implements Op.Loop, Op.Lowerable, JavaStatement {
+
+        public static class PredicateBuilder {
+            final Body.Builder ancestorBody;
+            private final Body.Builder body;
+
+            PredicateBuilder(Body.Builder ancestorBody, Body.Builder body) {
+                this.ancestorBody = ancestorBody;
+                this.body = body;
+            }
+
+            public JavaDoWhileOp predicate(Consumer<Block.Builder> c) {
+                Body.Builder predicate = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
+                c.accept(predicate.entryBlock());
+
+                return new JavaDoWhileOp(List.of(body, predicate));
+            }
+        }
+
+        public static class BodyBuilder {
+            final Body.Builder ancestorBody;
+
+            BodyBuilder(Body.Builder ancestorBody) {
+                this.ancestorBody = ancestorBody;
+            }
+
+            public JavaDoWhileOp.PredicateBuilder body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.VOID);
+                c.accept(body.entryBlock());
+
+                return new JavaDoWhileOp.PredicateBuilder(ancestorBody, body);
+            }
+        }
+
+        private static final String NAME = "java.do.while";
+
+        private final List<Body> bodies;
+
+        public JavaDoWhileOp(ExternalizedOp def) {
+            super(def);
+
+            // @@@ Validate
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+        }
+
+        JavaDoWhileOp(List<Body.Builder> bodyCs) {
+            super(NAME, List.of());
+
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+        }
+
+        JavaDoWhileOp(Body.Builder body, Body.Builder predicate) {
+            super(NAME, List.of());
+
+            Objects.requireNonNull(body);
+
+            this.bodies = Stream.of(body, predicate).filter(Objects::nonNull)
+                    .map(bc -> bc.build(this)).toList();
+
+            if (!bodies.get(0).bodyType().equals(FunctionType.VOID)) {
+                throw new IllegalArgumentException(
+                        "Body descriptor should be " + FunctionType.functionType(VOID) +
+                                " but is " + bodies.get(1).bodyType());
+            }
+            if (!bodies.get(1).bodyType().equals(FunctionType.functionType(BOOLEAN))) {
+                throw new IllegalArgumentException(
+                        "Predicate body descriptor should be " + FunctionType.functionType(BOOLEAN) +
+                                " but is " + bodies.get(0).bodyType());
+            }
+        }
+
+        JavaDoWhileOp(JavaDoWhileOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            this.bodies = that.bodies.stream()
+                    .map(b -> b.transform(cc, ot).build(this)).toList();
+        }
+
+        @Override
+        public JavaDoWhileOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaDoWhileOp(this, cc, ot);
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        public Body predicateBody() {
+            return bodies.get(1);
+        }
+
+        @Override
+        public Body loopBody() {
+            return bodies.get(0);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder body = b.block();
+            Block.Builder header = b.block();
+            Block.Builder exit = b.block();
+
+            b.op(branch(body.successor()));
+
+            setBranchTarget(b.context(), this, new BranchTarget(exit, header));
+
+            body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
+                // @@@ Composition of lowerable ops
+                if (op instanceof Lowerable lop) {
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
+                if (op instanceof CoreOp.YieldOp yo) {
+                    block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
+                            body.successor(), exit.successor()));
+                } else if (op instanceof Lowerable lop) {
+                    // @@@ Composition of lowerable ops
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    /**
+     * The conditional-and-or operation, that can model Java language condition-or or conditional-and expressions.
+     */
+    public sealed static abstract class JavaConditionalOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaExpression {
+        final List<Body> bodies;
+
+        public JavaConditionalOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Operation must have no operands");
+            }
+
+            // @@@ Validate
+
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+        }
+
+        JavaConditionalOp(JavaConditionalOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
+        }
+
+        JavaConditionalOp(String name, List<Body.Builder> bodyCs) {
+            super(name, List.of());
+
+            if (bodyCs.isEmpty()) {
+                throw new IllegalArgumentException();
+            }
+
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+            for (Body b : bodies) {
+                if (!b.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
+                    throw new IllegalArgumentException("Body conditional body descriptor: " + b.bodyType());
+                }
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        static Block.Builder lower(Block.Builder startBlock, OpTransformer opT, JavaConditionalOp cop) {
+            List<Body> bodies = cop.bodies();
+
+            Block.Builder exit = startBlock.block();
+            TypeElement oprType = cop.result().type();
+            Block.Parameter arg = exit.parameter(oprType);
+            startBlock.context().mapValue(cop.result(), arg);
+
+            // Transform bodies in reverse order
+            // This makes available the blocks to be referenced as successors in prior blocks
+
+            Block.Builder pred = null;
+            for (int i = bodies.size() - 1; i >= 0; i--) {
+                OpTransformer opt;
+                if (i == bodies.size() - 1) {
+                    opt = (block, op) -> {
+                        if (op instanceof CoreOp.YieldOp yop) {
+                            Value p = block.context().getValue(yop.yieldValue());
+                            block.op(branch(exit.successor(p)));
+                        } else if (op instanceof Lowerable lop) {
+                            // @@@ Composition of lowerable ops
+                            block = lop.lower(block, opT);
+                        } else {
+                            // Copy
+                            block.apply(op);
+                        }
+                        return block;
+                    };
+                } else {
+                    Block.Builder nextPred = pred;
+                    opt = (block, op) -> {
+                        if (op instanceof CoreOp.YieldOp yop) {
+                            Value p = block.context().getValue(yop.yieldValue());
+                            if (cop instanceof JavaConditionalAndOp) {
+                                block.op(conditionalBranch(p, nextPred.successor(), exit.successor(p)));
+                            } else {
+                                block.op(conditionalBranch(p, exit.successor(p), nextPred.successor()));
+                            }
+                        } else if (op instanceof Lowerable lop) {
+                            // @@@ Composition of lowerable ops
+                            block = lop.lower(block, opT);
+                        } else {
+                            // Copy
+                            block.apply(op);
+                        }
+                        return block;
+                    };
+                }
+
+                Body fromPred = bodies.get(i);
+                if (i == 0) {
+                    startBlock.transformBody(fromPred, List.of(), opt);
+                } else {
+                    pred = startBlock.block(fromPred.bodyType().parameterTypes());
+                    pred.transformBody(fromPred, pred.parameters(), opT.andThen(opt));
+                }
+            }
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return BOOLEAN;
+        }
+    }
+
+    /**
+     * The conditional-and operation, that can model Java language conditional-and expressions.
+     */
+    @OpFactory.OpDeclaration(JavaConditionalAndOp.NAME)
+    public static final class JavaConditionalAndOp extends JavaConditionalOp {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final List<Body.Builder> bodies;
+
+            Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
+                this.ancestorBody = ancestorBody;
+                this.bodies = new ArrayList<>();
+                and(lhs);
+                and(rhs);
+            }
+
+            public Builder and(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return this;
+            }
+
+            public JavaConditionalAndOp build() {
+                return new JavaConditionalAndOp(bodies);
+            }
+        }
+
+        public static final String NAME = "java.cand";
+
+        public JavaConditionalAndOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaConditionalAndOp(JavaConditionalAndOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc, ot);
+        }
+
+        @Override
+        public JavaConditionalAndOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaConditionalAndOp(this, cc, ot);
+        }
+
+        JavaConditionalAndOp(List<Body.Builder> bodyCs) {
+            super(NAME, bodyCs);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            return lower(b, opT, this);
+        }
+    }
+
+    /**
+     * The conditional-or operation, that can model Java language conditional-or expressions.
+     */
+    @OpFactory.OpDeclaration(JavaConditionalOrOp.NAME)
+    public static final class JavaConditionalOrOp extends JavaConditionalOp {
+
+        public static class Builder {
+            final Body.Builder ancestorBody;
+            final List<Body.Builder> bodies;
+
+            Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
+                this.ancestorBody = ancestorBody;
+                this.bodies = new ArrayList<>();
+                or(lhs);
+                or(rhs);
+            }
+
+            public Builder or(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
+                c.accept(body.entryBlock());
+                bodies.add(body);
+
+                return this;
+            }
+
+            public JavaConditionalOrOp build() {
+                return new JavaConditionalOrOp(bodies);
+            }
+        }
+
+        public static final String NAME = "java.cor";
+
+        public JavaConditionalOrOp(ExternalizedOp def) {
+            super(def);
+        }
+
+        JavaConditionalOrOp(JavaConditionalOrOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc, ot);
+        }
+
+        @Override
+        public JavaConditionalOrOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaConditionalOrOp(this, cc, ot);
+        }
+
+        JavaConditionalOrOp(List<Body.Builder> bodyCs) {
+            super(NAME, bodyCs);
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            return lower(b, opT, this);
+        }
+    }
+
+    /**
+     * The conditional operation, that can model Java language conditional operator {@code ?} expressions.
+     */
+    @OpFactory.OpDeclaration(JavaConditionalExpressionOp.NAME)
+    public static final class JavaConditionalExpressionOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaExpression {
+
+        public static final String NAME = "java.cexpression";
+
+        final TypeElement resultType;
+        // {cond, truepart, falsepart}
+        final List<Body> bodies;
+
+        public JavaConditionalExpressionOp(ExternalizedOp def) {
+            super(def);
+
+            if (!def.operands().isEmpty()) {
+                throw new IllegalStateException("Operation must have no operands");
+            }
+
+            // @@@ Validate
+
+            this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
+            this.resultType = def.resultType();
+        }
+
+        JavaConditionalExpressionOp(JavaConditionalExpressionOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            // Copy body
+            this.bodies = that.bodies.stream()
+                    .map(b -> b.transform(cc, ot).build(this)).toList();
+            this.resultType = that.resultType;
+        }
+
+        @Override
+        public JavaConditionalExpressionOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaConditionalExpressionOp(this, cc, ot);
+        }
+
+        JavaConditionalExpressionOp(TypeElement expressionType, List<Body.Builder> bodyCs) {
+            super(NAME, List.of());
+
+            this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
+            // @@@ when expressionType is null, we assume truepart and falsepart have the same yieldType
+            this.resultType = expressionType == null ? bodies.get(1).yieldType() : expressionType;
+
+            if (bodies.size() < 3) {
+                throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
+            }
+
+            Body cond = bodies.get(0);
+            if (!cond.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
+                throw new IllegalArgumentException("Illegal cond body descriptor: " + cond.bodyType());
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            return bodies;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            Block.Builder exit = b.block(resultType());
+            exit.context().mapValue(result(), exit.parameters().get(0));
+
+            setBranchTarget(b.context(), this, new BranchTarget(exit, null));
+
+            List<Block.Builder> builders = List.of(b.block(), b.block());
+            b.transformBody(bodies.get(0), List.of(), opT.andThen((block, op) -> {
+                if (op instanceof YieldOp yo) {
+                    block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
+                            builders.get(0).successor(), builders.get(1).successor()));
+                } else if (op instanceof Lowerable lop) {
+                    // @@@ Composition of lowerable ops
+                    block = lop.lower(block, opT);
+                } else {
+                    block.op(op);
+                }
+                return block;
+            }));
+
+            for (int i = 0; i < 2; i++) {
+                builders.get(i).transformBody(bodies.get(i + 1), List.of(), opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp yop) {
+                        block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
+                    } else if (op instanceof Lowerable lop) {
+                        // @@@ Composition of lowerable ops
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                    return block;
+                }));
+            }
+
+            return exit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return resultType;
+        }
+    }
+
+    /**
+     * The try operation, that can model Java language try statements.
+     */
+    @OpFactory.OpDeclaration(JavaTryOp.NAME)
+    public static final class JavaTryOp extends ExtendedOp
+            implements Op.Nested, Op.Lowerable, JavaStatement {
+
+        public static final class BodyBuilder {
+            final Body.Builder ancestorBody;
+            final List<? extends TypeElement> resourceTypes;
+            final Body.Builder resources;
+
+            BodyBuilder(Body.Builder ancestorBody, List<? extends TypeElement> resourceTypes, Body.Builder resources) {
+                this.ancestorBody = ancestorBody;
+                this.resourceTypes = resourceTypes;
+                this.resources = resources;
+            }
+
+            public CatchBuilder body(Consumer<Block.Builder> c) {
+                Body.Builder body = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(VOID, resourceTypes));
+                c.accept(body.entryBlock());
+
+                return new CatchBuilder(ancestorBody, resources, body);
+            }
+        }
+
+        public static final class CatchBuilder {
+            final Body.Builder ancestorBody;
+            final Body.Builder resources;
+            final Body.Builder body;
+            final List<Body.Builder> catchers;
+
+            CatchBuilder(Body.Builder ancestorBody, Body.Builder resources, Body.Builder body) {
+                this.ancestorBody = ancestorBody;
+                this.resources = resources;
+                this.body = body;
+                this.catchers = new ArrayList<>();
+            }
+
+            // @@@ multi-catch
+            public CatchBuilder _catch(TypeElement exceptionType, Consumer<Block.Builder> c) {
+                Body.Builder _catch = Body.Builder.of(ancestorBody,
+                        FunctionType.functionType(VOID, exceptionType));
+                c.accept(_catch.entryBlock());
+                catchers.add(_catch);
+
+                return this;
+            }
+
+            public JavaTryOp _finally(Consumer<Block.Builder> c) {
+                Body.Builder _finally = Body.Builder.of(ancestorBody, FunctionType.VOID);
+                c.accept(_finally.entryBlock());
+
+                return new JavaTryOp(resources, body, catchers, _finally);
+            }
+
+            public JavaTryOp noFinalizer() {
+                return new JavaTryOp(resources, body, catchers, null);
+            }
+        }
+
+        static final String NAME = "java.try";
+
+        final Body resources;
+        final Body body;
+        final List<Body> catchers;
+        final Body finalizer;
+
+        public static JavaTryOp create(ExternalizedOp def) {
+            return new JavaTryOp(def);
+        }
+
+        public JavaTryOp(ExternalizedOp def) {
+            super(def);
+
+            List<Body> bodies = def.bodyDefinitions().stream().map(b -> b.build(this)).toList();
+            Body first = bodies.get(0);
+            if (first.bodyType().returnType().equals(VOID)) {
+                this.resources = null;
+                this.body = first;
+            } else {
+                this.resources = first;
+                this.body = bodies.get(1);
+            }
+
+            Body last = bodies.get(bodies.size() - 1);
+            if (last.bodyType().parameterTypes().isEmpty()) {
+                this.finalizer = last;
+            } else {
+                this.finalizer = null;
+            }
+            this.catchers = bodies.subList(
+                    resources == null ? 1 : 2,
+                    bodies.size() - (finalizer == null ? 0 : 1));
+        }
+
+        JavaTryOp(JavaTryOp that, CopyContext cc, OpTransformer ot) {
+            super(that, cc);
+
+            if (that.resources != null) {
+                this.resources = that.resources.transform(cc, ot).build(this);
+            } else {
+                this.resources = null;
+            }
+            this.body = that.body.transform(cc, ot).build(this);
+            this.catchers = that.catchers.stream()
+                    .map(b -> b.transform(cc, ot).build(this))
+                    .toList();
+            if (that.finalizer != null) {
+                this.finalizer = that.finalizer.transform(cc, ot).build(this);
+            } else {
+                this.finalizer = null;
+            }
+        }
+
+        @Override
+        public JavaTryOp transform(CopyContext cc, OpTransformer ot) {
+            return new JavaTryOp(this, cc, ot);
+        }
+
+        JavaTryOp(Body.Builder resourcesC,
+                  Body.Builder bodyC,
+                  List<Body.Builder> catchersC,
+                  Body.Builder finalizerC) {
+            super(NAME, List.of());
+
+            if (resourcesC != null) {
+                this.resources = resourcesC.build(this);
+                if (resources.bodyType().returnType().equals(VOID)) {
+                    throw new IllegalArgumentException("Resources should not return void: " + resources.bodyType());
+                }
+                if (!resources.bodyType().parameterTypes().isEmpty()) {
+                    throw new IllegalArgumentException("Resources should have zero parameters: " + resources.bodyType());
+                }
+            } else {
+                this.resources = null;
+            }
+
+            this.body = bodyC.build(this);
+            if (!body.bodyType().returnType().equals(VOID)) {
+                throw new IllegalArgumentException("Try should return void: " + body.bodyType());
+            }
+
+            this.catchers = catchersC.stream().map(c -> c.build(this)).toList();
+            for (Body _catch : catchers) {
+                if (!_catch.bodyType().returnType().equals(VOID)) {
+                    throw new IllegalArgumentException("Catch should return void: " + _catch.bodyType());
+                }
+                if (_catch.bodyType().parameterTypes().size() != 1) {
+                    throw new IllegalArgumentException("Catch should have zero parameters: " + _catch.bodyType());
+                }
+            }
+
+            if (finalizerC != null) {
+                this.finalizer = finalizerC.build(this);
+                if (!finalizer.bodyType().returnType().equals(VOID)) {
+                    throw new IllegalArgumentException("Finally should return void: " + finalizer.bodyType());
+                }
+                if (!finalizer.bodyType().parameterTypes().isEmpty()) {
+                    throw new IllegalArgumentException("Finally should have zero parameters: " + finalizer.bodyType());
+                }
+            } else {
+                this.finalizer = null;
+            }
+        }
+
+        @Override
+        public List<Body> bodies() {
+            ArrayList<Body> bodies = new ArrayList<>();
+            if (resources != null) {
+                bodies.add(resources);
+            }
+            bodies.add(body);
+            bodies.addAll(catchers);
+            if (finalizer != null) {
+                bodies.add(finalizer);
+            }
+            return bodies;
+        }
+
+        public Body resources() {
+            return resources;
+        }
+
+        public Body body() {
+            return body;
+        }
+
+        public List<Body> catchers() {
+            return catchers;
+        }
+
+        public Body finalizer() {
+            return finalizer;
+        }
+
+        @Override
+        public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+            if (resources != null) {
+                throw new UnsupportedOperationException("Lowering of try-with-resources is unsupported");
+            }
+
+            Block.Builder exit = b.block();
+            setBranchTarget(b.context(), this, new BranchTarget(exit, null));
+
+            // Simple case with no catch and finally bodies
+            if (catchers.isEmpty() && finalizer == null) {
+                b.transformBody(body, List.of(), (block, op) -> {
+                    if (op instanceof YieldOp) {
+                        block.op(branch(exit.successor()));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        if (op instanceof Lowerable lop) {
+                            block = lop.lower(block, opT);
+                        } else {
+                            block.op(op);
+                        }
+                    }
+                    return block;
+                });
+                return exit;
+            }
+
+            Block.Builder tryRegionEnter = b.block();
+            Block.Builder tryRegionExit = b.block();
+
+            // Construct the catcher block builders
+            List<Block.Builder> catchers = catchers().stream()
+                    .map(catcher -> b.block())
+                    .toList();
+            Block.Builder catcherFinally = null;
+            if (finalizer != null) {
+                catcherFinally = b.block();
+                catchers = new ArrayList<>(catchers);
+                catchers.add(catcherFinally);
+            }
+
+            // Enter the try exception region
+            Result tryExceptionRegion = b.op(exceptionRegionEnter(tryRegionEnter.successor(), catchers.stream()
+                    .map(Block.Builder::successor)
+                    .toList()));
+
+            OpTransformer tryExitTransformer;
+            if (finalizer != null) {
+                tryExitTransformer = opT.compose((block, op) -> {
+                    if (op instanceof CoreOp.ReturnOp) {
+                        return inlineFinalizer(block, tryExceptionRegion, opT);
+                    } else if (op instanceof ExtendedOp.JavaLabelOp lop && ifExitFromTry(lop)) {
+                        return inlineFinalizer(block, tryExceptionRegion, opT);
+                    } else {
+                        return block;
+                    }
+                });
+            } else {
+                tryExitTransformer = opT.compose((block, op) -> {
+                    // @@@ break and continue
+                    // when target break/continue is enclosing the try
+                    if (op instanceof CoreOp.ReturnOp) {
+                        Block.Builder tryRegionReturnExit = block.block();
+                        block.op(exceptionRegionExit(tryExceptionRegion, tryRegionReturnExit.successor()));
+                        return tryRegionReturnExit;
+                    } else {
+                        return block;
+                    }
+                });
+            }
+            // Inline the try body
+            AtomicBoolean hasTryRegionExit = new AtomicBoolean();
+            tryRegionEnter.transformBody(body, List.of(), tryExitTransformer.andThen((block, op) -> {
+                if (op instanceof YieldOp) {
+                    hasTryRegionExit.set(true);
+                    block.op(branch(tryRegionExit.successor()));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    if (op instanceof Lowerable lop) {
+                        block = lop.lower(block, tryExitTransformer);
+                    } else {
+                        block.op(op);
+                    }
+                }
+                return block;
+            }));
+
+            Block.Builder finallyEnter = null;
+            if (finalizer != null) {
+                finallyEnter = b.block();
+                if (hasTryRegionExit.get()) {
+                    // Exit the try exception region
+                    tryRegionExit.op(exceptionRegionExit(tryExceptionRegion, finallyEnter.successor()));
+                }
+            } else if (hasTryRegionExit.get()) {
+                // Exit the try exception region
+                tryRegionExit.op(exceptionRegionExit(tryExceptionRegion, exit.successor()));
+            }
+
+            // Inline the catch bodies
+            for (int i = 0; i < this.catchers.size(); i++) {
+                Block.Builder catcher = catchers.get(i);
+                Body catcherBody = this.catchers.get(i);
+                // Create the throwable argument
+                Block.Parameter t = catcher.parameter(catcherBody.bodyType().parameterTypes().get(0));
+
+                if (finalizer != null) {
+                    Block.Builder catchRegionEnter = b.block();
+                    Block.Builder catchRegionExit = b.block();
+
+                    // Enter the catch exception region
+                    Result catchExceptionRegion = catcher.op(
+                            exceptionRegionEnter(catchRegionEnter.successor(), catcherFinally.successor()));
+
+                    OpTransformer catchExitTransformer = opT.compose((block, op) -> {
+                        if (op instanceof CoreOp.ReturnOp) {
+                            return inlineFinalizer(block, catchExceptionRegion, opT);
+                        } else if (op instanceof ExtendedOp.JavaLabelOp lop && ifExitFromTry(lop)) {
+                            return inlineFinalizer(block, catchExceptionRegion, opT);
+                        } else {
+                            return block;
+                        }
+                    });
+                    // Inline the catch body
+                    AtomicBoolean hasCatchRegionExit = new AtomicBoolean();
+                    catchRegionEnter.transformBody(catcherBody, List.of(t), catchExitTransformer.andThen((block, op) -> {
+                        if (op instanceof YieldOp) {
+                            hasCatchRegionExit.set(true);
+                            block.op(branch(catchRegionExit.successor()));
+                        } else {
+                            // @@@ Composition of lowerable ops
+                            if (op instanceof Lowerable lop) {
+                                block = lop.lower(block, catchExitTransformer);
+                            } else {
+                                block.op(op);
+                            }
+                        }
+                        return block;
+                    }));
+
+                    // Exit the catch exception region
+                    if (hasCatchRegionExit.get()) {
+                        hasTryRegionExit.set(true);
+                        catchRegionExit.op(exceptionRegionExit(catchExceptionRegion, finallyEnter.successor()));
+                    }
+                } else {
+                    // Inline the catch body
+                    catcher.transformBody(catcherBody, List.of(t), opT.andThen((block, op) -> {
+                        if (op instanceof YieldOp) {
+                            block.op(branch(exit.successor()));
+                        } else {
+                            // @@@ Composition of lowerable ops
+                            if (op instanceof Lowerable lop) {
+                                block = lop.lower(block, opT);
+                            } else {
+                                block.op(op);
+                            }
+                        }
+                        return block;
+                    }));
+                }
+            }
+
+            if (finalizer != null && hasTryRegionExit.get()) {
+                // Inline the finally body
+                finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp) {
+                        block.op(branch(exit.successor()));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        if (op instanceof Lowerable lop) {
+                            block = lop.lower(block, opT);
+                        } else {
+                            block.op(op);
+                        }
+                    }
+                    return block;
+                }));
+            }
+
+            // Inline the finally body as a catcher of Throwable and adjusting to throw
+            if (finalizer != null) {
+                // Create the throwable argument
+                Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
+
+                catcherFinally.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp) {
+                        block.op(_throw(t));
+                    } else {
+                        // @@@ Composition of lowerable ops
+                        if (op instanceof Lowerable lop) {
+                            block = lop.lower(block, opT);
+                        } else {
+                            block.op(op);
+                        }
+                    }
+                    return block;
+                }));
+            }
+            return exit;
+        }
+
+        boolean ifExitFromTry(JavaLabelOp lop) {
+            Op target = lop.target();
+            return target == this || ifAncestorOp(target, this);
+        }
+
+        static boolean ifAncestorOp(Op ancestor, Op op) {
+            while (op.ancestorBody() != null) {
+                op = op.ancestorBody().parentOp();
+                if (op == ancestor) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        Block.Builder inlineFinalizer(Block.Builder block1, Value exceptionRegion, OpTransformer opT) {
+            Block.Builder finallyEnter = block1.block();
+            Block.Builder finallyExit = block1.block();
+
+            block1.op(exceptionRegionExit(exceptionRegion, finallyEnter.successor()));
+
+            // Inline the finally body
+            finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block2, op2) -> {
+                if (op2 instanceof YieldOp) {
+                    block2.op(branch(finallyExit.successor()));
+                } else {
+                    // @@@ Composition of lowerable ops
+                    if (op2 instanceof Lowerable lop2) {
+                        block2 = lop2.lower(block2, opT);
+                    } else {
+                        block2.op(op2);
+                    }
+                }
+                return block2;
+            }));
+
+            return finallyExit;
+        }
+
+        @Override
+        public TypeElement resultType() {
+            return VOID;
+        }
+    }
+
+    //
+    // Patterns
+
+    static final String Pattern_CLASS_NAME = ExtendedOp_CLASS_NAME + "$" + Pattern.class.getSimpleName();
+
+    // Reified pattern nodes
+
+    /**
+     * Synthetic pattern types
+     * // @@@ Replace with types extending from TypeElement
+     */
+    public sealed interface Pattern {
+
+        /**
+         * Synthetic binding pattern type.
+         *
+         * @param <T> the type of values that are bound
+         */
+        final class Binding<T> implements Pattern {
+            Binding() {
+            }
+        }
+
+        /**
+         * Synthetic record pattern type.
+         *
+         * @param <T> the type of records that are bound
+         */
+        final class Record<T> implements Pattern {
+            Record() {
+            }
+        }
+
+        // @@@ Pattern types
+
+        JavaType PATTERN_BINDING_TYPE = JavaType.type(ClassDesc.of(Pattern_CLASS_NAME +
+                "$" + Binding.class.getSimpleName()));
+        JavaType PATTERN_RECORD_TYPE = JavaType.type(ClassDesc.of(Pattern_CLASS_NAME +
+                "$" + Pattern.Record.class.getSimpleName()));
+
+        static JavaType bindingType(TypeElement t) {
+            return parameterized(PATTERN_BINDING_TYPE, (JavaType) t);
+        }
+
+        static JavaType recordType(TypeElement t) {
+            return parameterized(PATTERN_RECORD_TYPE, (JavaType) t);
+        }
+
+        static TypeElement targetType(TypeElement t) {
+            return ((ClassType) t).typeArguments().get(0);
+        }
+    }
+
+    /**
+     * Pattern operations.
+     */
+    public static final class PatternOps {
+        PatternOps() {
+        }
+
+        /**
+         * The pattern operation.
+         */
+        public sealed static abstract class PatternOp extends ExtendedOp implements Op.Pure {
+            PatternOp(ExternalizedOp def) {
+                super(def);
+            }
+
+            PatternOp(PatternOp that, CopyContext cc) {
+                super(that, cc);
+            }
+
+            PatternOp(String name, List<Value> operands) {
+                super(name, operands);
+            }
+        }
+
+        /**
+         * The binding pattern operation, that can model Java language type patterns.
+         */
+        @OpFactory.OpDeclaration(BindingPatternOp.NAME)
+        public static final class BindingPatternOp extends PatternOp {
+            public static final String NAME = "pattern.binding";
+
+            public static final String ATTRIBUTE_BINDING_NAME = NAME + ".binding.name";
+
+            final TypeElement resultType;
+            final String bindingName;
+
+            public static BindingPatternOp create(ExternalizedOp def) {
+                String name = def.extractAttributeValue(ATTRIBUTE_BINDING_NAME, true,
+                        v -> switch (v) {
+                            case String s -> s;
+                            default ->
+                                    throw new UnsupportedOperationException("Unsupported pattern binding name value:" + v);
+                        });
+                return new BindingPatternOp(def, name);
+            }
+
+            BindingPatternOp(ExternalizedOp def, String bindingName) {
+                super(def);
+
+                this.bindingName = bindingName;
+                this.resultType = def.resultType();
+            }
+
+            BindingPatternOp(BindingPatternOp that, CopyContext cc) {
+                super(that, cc);
+
+                this.bindingName = that.bindingName;
+                this.resultType = that.resultType;
+            }
+
+            @Override
+            public BindingPatternOp transform(CopyContext cc, OpTransformer ot) {
+                return new BindingPatternOp(this, cc);
+            }
+
+            BindingPatternOp(TypeElement targetType, String bindingName) {
+                super(NAME, List.of());
+
+                this.bindingName = bindingName;
+                this.resultType = Pattern.bindingType(targetType);
+            }
+
+            @Override
+            public Map<String, Object> attributes() {
+                HashMap<String, Object> attrs = new HashMap<>(super.attributes());
+                attrs.put("", bindingName);
+                return attrs;
+            }
+
+            public String bindingName() {
+                return bindingName;
+            }
+
+            public TypeElement targetType() {
+                return Pattern.targetType(resultType());
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return resultType;
+            }
+        }
+
+        /**
+         * The record pattern operation, that can model Java language record patterns.
+         */
+        @OpFactory.OpDeclaration(RecordPatternOp.NAME)
+        public static final class RecordPatternOp extends PatternOp {
+            public static final String NAME = "pattern.record";
+
+            public static final String ATTRIBUTE_RECORD_DESCRIPTOR = NAME + ".descriptor";
+
+            final RecordTypeRef recordDescriptor;
+
+            public static RecordPatternOp create(ExternalizedOp def) {
+                RecordTypeRef recordDescriptor = def.extractAttributeValue(ATTRIBUTE_RECORD_DESCRIPTOR, true,
+                        v -> switch (v) {
+                            case String s -> RecordTypeRef.ofString(s);
+                            case RecordTypeRef rtd -> rtd;
+                            default ->
+                                    throw new UnsupportedOperationException("Unsupported record type descriptor value:" + v);
+                        });
+
+                return new RecordPatternOp(def, recordDescriptor);
+            }
+
+            RecordPatternOp(ExternalizedOp def, RecordTypeRef recordDescriptor) {
+                super(def);
+
+                this.recordDescriptor = recordDescriptor;
+            }
+
+            RecordPatternOp(RecordPatternOp that, CopyContext cc) {
+                super(that, cc);
+
+                this.recordDescriptor = that.recordDescriptor;
+            }
+
+            @Override
+            public RecordPatternOp transform(CopyContext cc, OpTransformer ot) {
+                return new RecordPatternOp(this, cc);
+            }
+
+            RecordPatternOp(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
+                // The type of each value is a subtype of Pattern
+                // The number of values corresponds to the number of components of the record
+                super(NAME, List.copyOf(nestedPatterns));
+
+                this.recordDescriptor = recordDescriptor;
+            }
+
+            @Override
+            public Map<String, Object> attributes() {
+                HashMap<String, Object> m = new HashMap<>(super.attributes());
+                m.put("", recordDescriptor);
+                return Collections.unmodifiableMap(m);
+            }
+
+            public RecordTypeRef recordDescriptor() {
+                return recordDescriptor;
+            }
+
+            public TypeElement targetType() {
+                return Pattern.targetType(resultType());
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return Pattern.recordType(recordDescriptor.recordType());
+            }
+        }
+
+        /**
+         * The match operation, that can model Java language pattern matching.
+         */
+        @OpFactory.OpDeclaration(MatchOp.NAME)
+        public static final class MatchOp extends ExtendedOp implements Op.Isolated, Op.Lowerable {
+            public static final String NAME = "pattern.match";
+
+            final Body pattern;
+            final Body match;
+
+            public MatchOp(ExternalizedOp def) {
+                super(def);
+
+                this.pattern = def.bodyDefinitions().get(0).build(this);
+                this.match = def.bodyDefinitions().get(1).build(this);
+            }
+
+            MatchOp(MatchOp that, CopyContext cc, OpTransformer ot) {
+                super(that, cc);
+
+                this.pattern = that.pattern.transform(cc, ot).build(this);
+                this.match = that.match.transform(cc, ot).build(this);
+            }
+
+            @Override
+            public MatchOp transform(CopyContext cc, OpTransformer ot) {
+                return new MatchOp(this, cc, ot);
+            }
+
+            MatchOp(Value target, Body.Builder patternC, Body.Builder matchC) {
+                super(NAME,
+                        List.of(target));
+
+                this.pattern = patternC.build(this);
+                this.match = matchC.build(this);
+            }
+
+            @Override
+            public List<Body> bodies() {
+                return List.of(pattern, match);
+            }
+
+            public Body pattern() {
+                return pattern;
+            }
+
+            public Body match() {
+                return match;
+            }
+
+            public Value target() {
+                return operands().get(0);
+            }
+
+            @Override
+            public Block.Builder lower(Block.Builder b, OpTransformer opT) {
+                // No match block
+                Block.Builder endNoMatchBlock = b.block();
+                // Match block
+                Block.Builder endMatchBlock = b.block();
+                // End block
+                Block.Builder endBlock = b.block();
+                Block.Parameter matchResult = endBlock.parameter(resultType());
+                // Map match operation result
+                b.context().mapValue(result(), matchResult);
+
+                List<Value> patternValues = new ArrayList<>();
+                Op patternYieldOp = pattern.entryBlock().terminatingOp();
+                Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0);
+                Block.Builder currentBlock = lower(endNoMatchBlock, b,
+                        patternValues,
+                        rootPatternValue.op(),
+                        b.context().getValue(target()));
+                currentBlock.op(branch(endMatchBlock.successor()));
+
+                // No match block
+                // Pass false
+                endNoMatchBlock.op(branch(endBlock.successor(
+                        endNoMatchBlock.op(constant(BOOLEAN, false)))));
+
+                // Match block
+                // Lower match body and pass true
+                endMatchBlock.transformBody(match, patternValues, opT.andThen((block, op) -> {
+                    if (op instanceof YieldOp) {
+                        block.op(branch(endBlock.successor(
+                                block.op(constant(BOOLEAN, true)))));
+                    } else if (op instanceof Lowerable lop) {
+                        // @@@ Composition of lowerable ops
+                        block = lop.lower(block, opT);
+                    } else {
+                        block.op(op);
+                    }
+                    return block;
+                }));
+
+                return endBlock;
+            }
+
+            static Block.Builder lower(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
+                                       List<Value> bindings,
+                                       Op pattern, Value target) {
+                if (pattern instanceof ExtendedOp.PatternOps.RecordPatternOp rp) {
+                    return lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target);
+                } else if (pattern instanceof ExtendedOp.PatternOps.BindingPatternOp bp) {
+                    return lowerBindingPattern(endNoMatchBlock, currentBlock, bindings, bp, target);
+                } else {
+                    throw new UnsupportedOperationException("Unknown pattern op: " + pattern);
+                }
+            }
+
+            static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
+                                                    List<Value> bindings,
+                                                    ExtendedOp.PatternOps.RecordPatternOp rpOp, Value target) {
+                TypeElement targetType = rpOp.targetType();
+
+                Block.Builder nextBlock = currentBlock.block();
+
+                // Check if instance of target type
+                Op.Result isInstance = currentBlock.op(CoreOp.instanceOf(targetType, target));
+                currentBlock.op(conditionalBranch(isInstance, nextBlock.successor(), endNoMatchBlock.successor()));
+
+                currentBlock = nextBlock;
+
+                target = currentBlock.op(CoreOp.cast(targetType, target));
+
+                // Access component values of record and match on each as nested target
+                List<Value> dArgs = rpOp.operands();
+                for (int i = 0; i < dArgs.size(); i++) {
+                    Op.Result nestedPattern = (Op.Result) dArgs.get(i);
+                    // @@@ Handle exceptions?
+                    Value nestedTarget = currentBlock.op(CoreOp.invoke(rpOp.recordDescriptor().methodForComponent(i), target));
+
+                    currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget);
+                }
+
+                return currentBlock;
+            }
+
+            static Block.Builder lowerBindingPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
+                                                     List<Value> bindings,
+                                                     ExtendedOp.PatternOps.BindingPatternOp bpOp, Value target) {
+                TypeElement targetType = bpOp.targetType();
+
+                Block.Builder nextBlock = currentBlock.block();
+
+                // Check if instance of target type
+                currentBlock.op(conditionalBranch(currentBlock.op(CoreOp.instanceOf(targetType, target)),
+                        nextBlock.successor(), endNoMatchBlock.successor()));
+
+                currentBlock = nextBlock;
+
+                target = currentBlock.op(CoreOp.cast(targetType, target));
+                bindings.add(target);
+
+                return currentBlock;
+            }
+
+            @Override
+            public TypeElement resultType() {
+                return BOOLEAN;
+            }
+        }
+    }
+
+
+    /**
+     * A factory for extended and core operations.
+     */
+    // @@@ Compute lazily
+    public static final OpFactory FACTORY = CoreOp.FACTORY.andThen(OpFactory.OP_FACTORY.get(ExtendedOp.class));
+
+
+    /**
+     * Creates a continue operation.
+     *
+     * @return the continue operation
+     */
+    public static JavaContinueOp _continue() {
+        return _continue(null);
+    }
+
+    /**
+     * Creates a continue operation.
+     *
+     * @param label the value associated with where to continue from
+     * @return the continue operation
+     */
+    public static JavaContinueOp _continue(Value label) {
+        return new JavaContinueOp(label);
+    }
+
+    /**
+     * Creates a break operation.
+     *
+     * @return the break operation
+     */
+    public static JavaBreakOp _break() {
+        return _break(null);
+    }
+
+    /**
+     * Creates a break operation.
+     *
+     * @param label the value associated with where to continue from
+     * @return the break operation
+     */
+    public static JavaBreakOp _break(Value label) {
+        return new JavaBreakOp(label);
+    }
+
+    /**
+     * Creates a yield operation.
+     *
+     * @return the yield operation
+     */
+    public static JavaYieldOp java_yield() {
+        return new JavaYieldOp();
+    }
+
+    /**
+     * Creates a yield operation.
+     *
+     * @param operand the value to yield
+     * @return the yield operation
+     */
+    public static JavaYieldOp java_yield(Value operand) {
+        return new JavaYieldOp(operand);
+    }
+
+    /**
+     * Creates a block operation.
+     *
+     * @param body the body builder of the operation to be built and become its child
+     * @return the block operation
+     */
+    public static JavaBlockOp block(Body.Builder body) {
+        return new JavaBlockOp(body);
+    }
+
+    /**
+     * Creates a labeled operation.
+     *
+     * @param body the body builder of the operation to be built and become its child
+     * @return the block operation
+     */
+    public static JavaLabeledOp labeled(Body.Builder body) {
+        return new JavaLabeledOp(body);
+    }
+
+    /**
+     * Creates an if operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @return the if operation builder
+     */
+    public static JavaIfOp.IfBuilder _if(Body.Builder ancestorBody) {
+        return new JavaIfOp.IfBuilder(ancestorBody);
+    }
+
+    // Pairs of
+    //   predicate ()boolean, body ()void
+    // And one optional body ()void at the end
+
+    /**
+     * Creates an if operation.
+     *
+     * @param bodies the body builders of operation to be built and become its children
+     * @return the if operation
+     */
+    public static JavaIfOp _if(List<Body.Builder> bodies) {
+        return new JavaIfOp(bodies);
+    }
+
+    /**
+     * Creates a switch expression operation.
+     * <p>
+     * The result type of the operation will be derived from the yield type of the second body
+     *
+     * @param target the switch target value
+     * @param bodies the body builders of the operation to be built and become its children
+     * @return the switch expression operation
+     */
+    public static JavaSwitchExpressionOp switchExpression(Value target, List<Body.Builder> bodies) {
+        return new JavaSwitchExpressionOp(null, target, bodies);
+    }
+
+    /**
+     * Creates a switch expression operation.
+     *
+     * @param resultType the result type of the expression
+     * @param target     the switch target value
+     * @param bodies     the body builders of the operation to be built and become its children
+     * @return the switch expression operation
+     */
+    public static JavaSwitchExpressionOp switchExpression(TypeElement resultType, Value target,
+                                                          List<Body.Builder> bodies) {
+        Objects.requireNonNull(resultType);
+        return new JavaSwitchExpressionOp(resultType, target, bodies);
+    }
+
+    /**
+     * Creates a switch fallthrough operation.
+     *
+     * @return the switch fallthrough operation
+     */
+    public static JavaSwitchFallthroughOp switchFallthroughOp() {
+        return new JavaSwitchFallthroughOp();
+    }
+
+    /**
+     * Creates a for operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param initTypes    the types of initialized variables
+     * @return the for operation builder
+     */
+    public static JavaForOp.InitBuilder _for(Body.Builder ancestorBody, TypeElement... initTypes) {
+        return _for(ancestorBody, List.of(initTypes));
+    }
+
+    /**
+     * Creates a for operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param initTypes    the types of initialized variables
+     * @return the for operation builder
+     */
+    public static JavaForOp.InitBuilder _for(Body.Builder ancestorBody, List<? extends TypeElement> initTypes) {
+        return new JavaForOp.InitBuilder(ancestorBody, initTypes);
+    }
+
+
+    /**
+     * Creates a for operation.
+     *
+     * @param init   the init body builder of the operation to be built and become its child
+     * @param cond   the cond body builder of the operation to be built and become its child
+     * @param update the update body builder of the operation to be built and become its child
+     * @param body   the main body builder of the operation to be built and become its child
+     * @return the for operation
+     */
+    // init ()Tuple<Var<T1>, Var<T2>, ..., Var<TN>>, or init ()void
+    // cond (Var<T1>, Var<T2>, ..., Var<TN>)boolean
+    // update (Var<T1>, Var<T2>, ..., Var<TN>)void
+    // body (Var<T1>, Var<T2>, ..., Var<TN>)void
+    public static JavaForOp _for(Body.Builder init,
+                                 Body.Builder cond,
+                                 Body.Builder update,
+                                 Body.Builder body) {
+        return new JavaForOp(init, cond, update, body);
+    }
+
+    /**
+     * Creates an enhanced for operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param iterableType the iterable type
+     * @param elementType  the element type
+     * @return the enhanced for operation builder
+     */
+    public static JavaEnhancedForOp.ExpressionBuilder enhancedFor(Body.Builder ancestorBody,
+                                                                  TypeElement iterableType, TypeElement elementType) {
+        return new JavaEnhancedForOp.ExpressionBuilder(ancestorBody, iterableType, elementType);
+    }
+
+    // expression ()I<E>
+    // init (E )Var<T>
+    // body (Var<T> )void
+
+    /**
+     * Creates an enhanced for operation.
+     *
+     * @param expression the expression body builder of the operation to be built and become its child
+     * @param init       the init body builder of the operation to be built and become its child
+     * @param body       the main body builder of the operation to be built and become its child
+     * @return the enhanced for operation
+     */
+    public static JavaEnhancedForOp enhancedFor(Body.Builder expression,
+                                                Body.Builder init,
+                                                Body.Builder body) {
+        return new JavaEnhancedForOp(expression, init, body);
+    }
+
+    /**
+     * Creates a while operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @return the while operation builder
+     */
+    public static JavaWhileOp.PredicateBuilder _while(Body.Builder ancestorBody) {
+        return new JavaWhileOp.PredicateBuilder(ancestorBody);
+    }
+
+    /**
+     * Creates a while operation.
+     *
+     * @param predicate the predicate body builder of the operation to be built and become its child
+     * @param body      the main body builder of the operation to be built and become its child
+     * @return the while operation
+     */
+    // predicate, ()boolean, may be null for predicate returning true
+    // body, ()void
+    public static JavaWhileOp _while(Body.Builder predicate, Body.Builder body) {
+        return new JavaWhileOp(predicate, body);
+    }
+
+    /**
+     * Creates a do operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @return the do operation builder
+     */
+    public static JavaDoWhileOp.BodyBuilder doWhile(Body.Builder ancestorBody) {
+        return new JavaDoWhileOp.BodyBuilder(ancestorBody);
+    }
+
+    /**
+     * Creates a do operation.
+     *
+     * @param predicate the predicate body builder of the operation to be built and become its child
+     * @param body      the main body builder of the operation to be built and become its child
+     * @return the do operation
+     */
+    public static JavaDoWhileOp doWhile(Body.Builder body, Body.Builder predicate) {
+        return new JavaDoWhileOp(body, predicate);
+    }
+
+    /**
+     * Creates a conditional-and operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param lhs          a consumer that builds the left-hand side body
+     * @param rhs          a consumer that builds the right-hand side body
+     * @return the conditional-and operation builder
+     */
+    public static JavaConditionalAndOp.Builder conditionalAnd(Body.Builder ancestorBody,
+                                                              Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
+        return new JavaConditionalAndOp.Builder(ancestorBody, lhs, rhs);
+    }
+
+    /**
+     * Creates a conditional-or operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param lhs          a consumer that builds the left-hand side body
+     * @param rhs          a consumer that builds the right-hand side body
+     * @return the conditional-or operation builder
+     */
+    public static JavaConditionalOrOp.Builder conditionalOr(Body.Builder ancestorBody,
+                                                            Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
+        return new JavaConditionalOrOp.Builder(ancestorBody, lhs, rhs);
+    }
+
+    /**
+     * Creates a conditional-and operation
+     *
+     * @param bodies the body builders of operation to be built and become its children
+     * @return the conditional-and operation
+     */
+    // predicates, ()boolean
+    public static JavaConditionalAndOp conditionalAnd(List<Body.Builder> bodies) {
+        return new JavaConditionalAndOp(bodies);
+    }
+
+    /**
+     * Creates a conditional-or operation
+     *
+     * @param bodies the body builders of operation to be built and become its children
+     * @return the conditional-or operation
+     */
+    // predicates, ()boolean
+    public static JavaConditionalOrOp conditionalOr(List<Body.Builder> bodies) {
+        return new JavaConditionalOrOp(bodies);
+    }
+
+    /**
+     * Creates a conditional operation
+     *
+     * @param expressionType the result type of the expression
+     * @param bodies         the body builders of operation to be built and become its children
+     * @return the conditional operation
+     */
+    public static JavaConditionalExpressionOp conditionalExpression(TypeElement expressionType,
+                                                                    List<Body.Builder> bodies) {
+        Objects.requireNonNull(expressionType);
+        return new JavaConditionalExpressionOp(expressionType, bodies);
+    }
+
+    /**
+     * Creates a conditional operation
+     * <p>
+     * The result type of the operation will be derived from the yield type of the second body
+     *
+     * @param bodies the body builders of operation to be built and become its children
+     * @return the conditional operation
+     */
+    public static JavaConditionalExpressionOp conditionalExpression(List<Body.Builder> bodies) {
+        return new JavaConditionalExpressionOp(null, bodies);
+    }
+
+    /**
+     * Creates try operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param c            a consumer that builds the try body
+     * @return the try operation builder
+     */
+    public static JavaTryOp.CatchBuilder _try(Body.Builder ancestorBody, Consumer<Block.Builder> c) {
+        Body.Builder _try = Body.Builder.of(ancestorBody, FunctionType.VOID);
+        c.accept(_try.entryBlock());
+        return new JavaTryOp.CatchBuilder(ancestorBody, null, _try);
+    }
+
+    /**
+     * Creates try-with-resources operation builder.
+     *
+     * @param ancestorBody the nearest ancestor body builder from which to construct
+     *                     body builders for this operation
+     * @param c            a consumer that builds the resources body
+     * @return the try-with-resources operation builder
+     */
+    public static JavaTryOp.BodyBuilder tryWithResources(Body.Builder ancestorBody,
+                                                         List<? extends TypeElement> resourceTypes,
+                                                         Consumer<Block.Builder> c) {
+        resourceTypes = resourceTypes.stream().map(VarType::varType).toList();
+        Body.Builder resources = Body.Builder.of(ancestorBody,
+                FunctionType.functionType(TupleType.tupleType(resourceTypes)));
+        c.accept(resources.entryBlock());
+        return new JavaTryOp.BodyBuilder(ancestorBody, resourceTypes, resources);
+    }
+
+    // resources ()Tuple<Var<R1>, Var<R2>, ..., Var<RN>>, or null
+    // try (Var<R1>, Var<R2>, ..., Var<RN>)void, or try ()void
+    // catch (E )void, where E <: Throwable
+    // finally ()void, or null
+
+    /**
+     * Creates a try or try-with-resources operation.
+     *
+     * @param resources the try body builder of the operation to be built and become its child,
+     *                  may be null
+     * @param body      the try body builder of the operation to be built and become its child
+     * @param catchers  the catch body builders of the operation to be built and become its children
+     * @param finalizer the finalizer body builder of the operation to be built and become its child
+     * @return the try or try-with-resources operation
+     */
+    public static JavaTryOp _try(Body.Builder resources,
+                                 Body.Builder body,
+                                 List<Body.Builder> catchers,
+                                 Body.Builder finalizer) {
+        return new JavaTryOp(resources, body, catchers, finalizer);
+    }
+
+    //
+    // Patterns
+
+    /**
+     * Creates a pattern match operation.
+     *
+     * @param target  the target value
+     * @param pattern the pattern body builder of the operation to be built and become its child
+     * @param match   the match body builder of the operation to be built and become its child
+     * @return the pattern match operation
+     */
+    public static PatternOps.MatchOp match(Value target,
+                                           Body.Builder pattern, Body.Builder match) {
+        return new PatternOps.MatchOp(target, pattern, match);
+    }
+
+    /**
+     * Creates a pattern binding operation.
+     *
+     * @param type        the type of value to be bound
+     * @param bindingName the binding name
+     * @return the pattern binding operation
+     */
+    public static PatternOps.BindingPatternOp bindingPattern(TypeElement type, String bindingName) {
+        return new PatternOps.BindingPatternOp(type, bindingName);
+    }
+
+    /**
+     * Creates a record pattern operation.
+     *
+     * @param recordDescriptor the record descriptor
+     * @param nestedPatterns   the nested pattern values
+     * @return the record pattern operation
+     */
+    public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, Value... nestedPatterns) {
+        return recordPattern(recordDescriptor, List.of(nestedPatterns));
+    }
+
+    /**
+     * Creates a record pattern operation.
+     *
+     * @param recordDescriptor the record descriptor
+     * @param nestedPatterns   the nested pattern values
+     * @return the record pattern operation
+     */
+    public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
+        return new PatternOps.RecordPatternOp(recordDescriptor, nestedPatterns);
+    }
+
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/op/ExternalizableOp.java b/src/java.base/share/classes/java/lang/reflect/code/op/ExternalizableOp.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/op/ExternalizableOp.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.op;
+
+import java.lang.reflect.code.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * An operation that supports externalization of its content and reconstruction
+ * via an instance of {@link ExternalizedOp}.
+ * <p>
+ * The specific content of an externalizable operation can be externalized to a
+ * map of {@link #attributes attributes}, and is reconstructed from the
+ * attributes component of an instance of {@link ExternalizedOp}.
+ * <p>
+ * An externalizable operation could be externalized via serialization to
+ * a textual representation. That textual representation could then be deserialized,
+ * via parsing, into an instance of {@link ExternalizedOp} from which a new
+ * externalizable operation can be reconstructed that is identical to one that
+ * was serialized.
+ */
+public abstract class ExternalizableOp extends Op {
+
+    /**
+     * An operation's externalized content (a record) that can be utilized to construct an instance
+     * of an {@link ExternalizableOp} associated with the operation's name.
+     *
+     * @param name            the operation name
+     * @param operands        the list of operands
+     * @param successors      the list of successors
+     * @param resultType      the operation result type
+     * @param attributes      the operation's specific content as an attributes map, modifiable
+     * @param bodyDefinitions the list of body builders for building the operation's bodies
+     * @apiNote Deserializers of operations may utilize this record to construct operations,
+     * thereby separating the specifics of deserializing from construction.
+     */
+    public record ExternalizedOp(String name,
+                                 List<Value> operands,
+                                 List<Block.Reference> successors,
+                                 TypeElement resultType,
+                                 Map<String, Object> attributes,
+                                 List<Body.Builder> bodyDefinitions) {
+
+        /**
+         * Removes an attribute value from the attributes map, converts the value by applying it
+         * to mapping function, and returns the result.
+         *
+         * <p>If the attribute is a default attribute then this method first attempts to
+         * remove the attribute whose name is the empty string, otherwise if there is no such
+         * attribute present or the attribute is not a default attribute then this method
+         * attempts to remove the attribute with the given name.
+         *
+         * <p>On successful removal of the attribute its value is converted by applying the value
+         * to the mapping function.
+         *
+         * @param name      the attribute name.
+         * @param isDefault true if the attribute is a default attribute
+         * @param <T>       the converted attribute value type
+         * @return the converted attribute value
+         * @throws IllegalArgumentException if there is no attribute present
+         */
+        public <T> T extractAttributeValue(String name, boolean isDefault, Function<Object, T> mapper) {
+            Object value = attributes.remove(isDefault ? "" : name);
+            if (value == null) {
+                if (!isDefault) {
+                    throw new IllegalArgumentException("Required attribute not present: "
+                            + name);
+                }
+
+                value = attributes.remove(name);
+            }
+
+            return mapper.apply(value);
+        }
+
+        /**
+         * Externalizes an operation's content.
+         * <p>
+         * If the operation is an instanceof {@code ExternalizableOp} then the operation's
+         * specific content is externalized to an attribute map, otherwise the attribute map
+         * is empty.
+         *
+         * @param cc the copy context
+         * @param op the operation
+         * @return the operation's content.
+         */
+        public static ExternalizedOp externalizeOp(CopyContext cc, Op op) {
+            return new ExternalizedOp(
+                    op.opName(),
+                    cc.getValues(op.operands()),
+                    op.successors().stream().map(cc::getSuccessorOrCreate).toList(),
+                    op.resultType(),
+                    op instanceof ExternalizableOp exop ? new HashMap<>(exop.attributes()) : new HashMap<>(),
+                    op.bodies().stream().map(b -> b.copy(cc)).toList()
+            );
+        }
+    }
+
+    /**
+     * The attribute name associated with the location attribute.
+     */
+    public static final String ATTRIBUTE_LOCATION = "loc";
+
+    /**
+     * The attribute value that represents the external null value.
+     */
+    public static final Object NULL_ATTRIBUTE_VALUE = new Object();
+
+    /**
+     * Constructs an operation by copying given operation.
+     *
+     * @param that the operation to copy.
+     * @param cc   the copy context.
+     * @implSpec The default implementation calls the constructor with the operation's name, result type, and a list
+     * values computed, in order, by mapping the operation's operands using the copy context.
+     */
+    protected ExternalizableOp(Op that, CopyContext cc) {
+        super(that, cc);
+    }
+
+    /**
+     * Constructs an operation with a name, operation result type, and list of operands.
+     *
+     * @param name     the operation name.
+     * @param operands the list of operands, a copy of the list is performed if required.
+     */
+    protected ExternalizableOp(String name, List<? extends Value> operands) {
+        super(name, operands);
+    }
+
+    /**
+     * Constructs an operation from its external content.
+     *
+     * @param def the operation's external content.
+     * @implSpec This implementation invokes the {@link Op#Op(String, List) constructor}
+     * accepting the non-optional components of the operation's content, {@code name},
+     * and {@code operands}:
+     * <pre> {@code
+     *  this(def.name(), def.operands());
+     * }</pre>
+     */
+    @SuppressWarnings("this-escape")
+    protected ExternalizableOp(ExternalizedOp def) {
+        super(def.name(), def.operands());
+        setLocation(extractLocation(def));
+    }
+
+    static Location extractLocation(ExternalizedOp def) {
+        Object v = def.attributes().get(ATTRIBUTE_LOCATION);
+        return switch (v) {
+            case String s -> Location.fromString(s);
+            case Location loc -> loc;
+            case null -> null;
+            default -> throw new UnsupportedOperationException("Unsupported location value:" + v);
+        };
+    }
+
+    /**
+     * Externalizes the operation's specific content as a map of attributes.
+     *
+     * <p>A null attribute value is represented by the constant
+     * value {@link #NULL_ATTRIBUTE_VALUE}.
+     *
+     * @return the operation's attributes, as an unmodifiable map
+     */
+    public Map<String, Object> attributes() {
+        Location l = location();
+        return l == null ? Map.of() : Map.of(ATTRIBUTE_LOCATION, l);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/op/OpFactory.java b/src/java.base/share/classes/java/lang/reflect/code/op/OpFactory.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/op/OpFactory.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.op;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.code.Op;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * An operation factory for constructing an {@link Op operation} from its
+ * {@link ExternalizableOp.ExternalizedOp external content}.
+ */
+@FunctionalInterface
+public interface OpFactory {
+
+    /**
+     * An operation declaration annotation.
+     * <p>
+     * This annotation may be declared on a concrete class implementing an {@link Op operation} whose name is a constant
+     * that can be declared as this attribute's value.
+     * <p>
+     * Tooling can process declarations of this annotation to build a factory for constructing operations from their name.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.TYPE)
+    @interface OpDeclaration {
+        /**
+         * {@return the operation name}
+         */
+        String value();
+    }
+
+    /**
+     * A class value for lazily computing an operation factory for {@link Op operation} classes
+     * annotated with {@link OpDeclaration} and enclosed within a given class to compute over.
+     * <p>
+     * Each enclosed class annotated with {@code OpDeclaration} must declare a public static method named {@code create}
+     * with one parameter type of {@link ExternalizableOp.ExternalizedOp} and return type that is the concrete class type.
+     * Alternatively, the concrete class must declare public constructor with one parameter type of
+     * {@link ExternalizableOp.ExternalizedOp}.
+     */
+    ClassValue<OpFactory> OP_FACTORY = new ClassValue<>() {
+        @Override
+        protected OpFactory computeValue(Class<?> c) {
+            // @@@ See https://bugs.openjdk.org/browse/JDK-8321207
+            final Map<String, Class<? extends Op>> opMapping = createOpMapping(c);
+
+            return def -> {
+                var opClass = opMapping.get(def.name());
+                if (opClass == null) {
+                    return null;
+                }
+
+                return constructOp(opClass, def);
+            };
+        }
+    };
+
+    /**
+     * Constructs an {@link Op operation} from its external content.
+     * <p>
+     * If there is no mapping from the operation's name to a concrete
+     * class of an {@code Op} then this method returns null.
+     *
+     * @param def the operation's external content
+     * @return the operation, otherwise null
+     */
+    Op constructOp(ExternalizableOp.ExternalizedOp def);
+
+    /**
+     * Constructs an {@link Op operation} from its external content.
+     * <p>
+     * If there is no mapping from the operation's name to a concrete
+     * class of an {@code Op} then this method throws UnsupportedOperationException.
+     *
+     * @param def the operation's external content
+     * @return the operation, otherwise null
+     * @throws UnsupportedOperationException if there is no mapping from the operation's
+     *                                       name to a concrete class of an {@code Op}
+     */
+    default Op constructOpOrFail(ExternalizableOp.ExternalizedOp def) {
+        Op op = constructOp(def);
+        if (op == null) {
+            throw new UnsupportedOperationException("Unsupported operation: " + def.name());
+        }
+
+        return op;
+    }
+
+    /**
+     * Compose this operation factory with another operation factory.
+     * <p>
+     * If there is no mapping in this operation factory then the result
+     * of the other operation factory is returned.
+     *
+     * @param after the other operation factory.
+     * @return the composed operation factory.
+     */
+    default OpFactory andThen(OpFactory after) {
+        return def -> {
+            Op op = constructOp(def);
+            return op != null ? op : after.constructOp(def);
+        };
+    }
+
+    private static Map<String, Class<? extends Op>> createOpMapping(Class<?> opClasses) {
+        Map<String, Class<? extends Op>> mapping = new HashMap<>();
+        for (Class<?> opClass : opClasses.getNestMembers()) {
+            if (opClass.isAnnotationPresent(OpDeclaration.class)) {
+                if (!Modifier.isPublic(opClass.getModifiers())) {
+                    throw new InternalError("Operation class not public: " + opClass.getName());
+                }
+
+                if (!Op.class.isAssignableFrom(opClass)) {
+                    throw new InternalError("Operation class is not assignable to Op: " + opClass);
+                }
+
+                MethodHandle handle = getOpConstructorMethodHandle(opClass);
+                if (handle == null) {
+                    throw new InternalError("Operation constructor for operation class not found: " + opClass.getName());
+                }
+
+                if (!Op.class.isAssignableFrom(handle.type().returnType())) {
+                    throw new InternalError("Operation constructor does not return an Op: " + handle);
+                }
+
+                String opName = opClass.getAnnotation(OpDeclaration.class).value();
+                @SuppressWarnings("unchecked")
+                var opClassCast = (Class<Op>) opClass;
+                mapping.put(opName, opClassCast);
+            }
+        }
+        return mapping;
+    }
+
+    private static MethodHandle getOpConstructorMethodHandle(Class<?> opClass) {
+        Method method = null;
+        try {
+            method = opClass.getMethod("create", ExternalizableOp.ExternalizedOp.class);
+        } catch (NoSuchMethodException e) {
+        }
+
+        if (method != null) {
+            if (!Modifier.isStatic(method.getModifiers())) {
+                throw new InternalError("Operation constructor is not a static method: " + method);
+            }
+
+            try {
+/*__return MethodHandles.lookup().unreflect(method);__*/                return MethodHandles.publicLookup().unreflect(method);
+            } catch (IllegalAccessException e) {
+                throw new InternalError("Inaccessible operation constructor for operation: " +
+                        method);
+            }
+        }
+
+        Constructor<?> constructor;
+        try {
+            constructor = opClass.getConstructor(ExternalizableOp.ExternalizedOp.class);
+        } catch (NoSuchMethodException e) {
+            return null;
+        }
+
+        try {
+/*__return MethodHandles.lookup().unreflectConstructor(constructor);__*/            return MethodHandles.publicLookup().unreflectConstructor(constructor);
+        } catch (IllegalAccessException e) {
+            throw new InternalError("Inaccessible operation constructor for operation: " +
+                    constructor);
+        }
+    }
+
+    private static Op constructOp(Class<? extends Op> opClass, ExternalizableOp.ExternalizedOp opDef) {
+        class Enclosed {
+            private static final ClassValue<Function<ExternalizableOp.ExternalizedOp, Op>> OP_CONSTRUCTOR = new ClassValue<>() {
+                @Override
+                protected Function<ExternalizableOp.ExternalizedOp, Op> computeValue(Class<?> opClass) {
+                    final MethodHandle opConstructorMH = getOpConstructorMethodHandle(opClass);
+                    assert opConstructorMH != null;
+
+                    return operationDefinition -> {
+                        try {
+                            return (Op) opConstructorMH.invoke(operationDefinition);
+                        } catch (RuntimeException | Error e) {
+                            throw e;
+                        } catch (Throwable t) {
+                            throw new RuntimeException(t);
+                        }
+                    };
+                }
+            };
+        }
+        return Enclosed.OP_CONSTRUCTOR.get(opClass).apply(opDef);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/op/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/op/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/op/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for operations declared in code models that can represent Java programs.
+ */
+package java.lang.reflect.code.op;
diff a/src/java.base/share/classes/java/lang/reflect/code/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for a code metamodel that is used to quote Java code as code models, build code models, and transform code models.
+ */
+package java.lang.reflect.code;
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/OpParser.java b/src/java.base/share/classes/java/lang/reflect/code/parser/OpParser.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/OpParser.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.op.*;
+import java.lang.reflect.code.parser.impl.DescParser;
+import java.lang.reflect.code.parser.impl.Lexer;
+import java.lang.reflect.code.parser.impl.Scanner;
+import java.lang.reflect.code.parser.impl.Tokens;
+import java.lang.reflect.code.type.CoreTypeFactory;
+import java.lang.reflect.code.type.TypeElementFactory;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A parser of serialized code models from their textual form.
+ * <p>
+ * The syntactic grammar of a code mode is specified in the grammar notation, and is a subset of the grammar,
+ * specified by the JLS, see section 2.4. (Except that we cannot express non-terminal symbols in italic type.)
+ * <p>
+ * {@snippet lang=text :
+ * Operation:
+ *   [Value =] Name {Operands} {Successors} {Attributes} {Bodies} ;
+ *
+ * Operands:
+ *   ValueIdentifier {ValueIdentifier}
+ *
+ * Successors:
+ *   Successor {Successor}
+ *
+ * Successor:
+ *   BlockIdentifier
+ *   BlockIdentifier ()
+ *   BlockIdentifier ( ValueIdentifier {, ValueIdentifier} )
+ *
+ * Attributes:
+ *   Attribute {Attribute}
+ *
+ * Attribute:
+ *   @ AttributeValue
+ *   @ Name = AttributeValue
+ *
+ * AttributeValue:
+ *   Name
+ *   StringLiteral
+ *   NullLiteral
+ *
+ * Bodies:
+ *   Body {Body}
+ *
+ * Body:
+ *   BlockIdentifier ( ) Type -> { Operations {Block} }
+ *   BlockIdentifier ( Value {, Value} ) Type -> { Operations {Block} }
+ *
+ * Operations:
+ *   Operation {Operation}
+ *
+ * Block:
+ *   BlockIdentifier : Operations
+ *   BlockIdentifier ( ) : Operations
+ *   BlockIdentifier ( Value {, Value} ) : Operations
+ *
+ * BlockIdentifier:
+ *   ^ Identifier
+ *
+ * Value:
+ *   ValueIdentifier : Type
+ *
+ * ValueIdentifier:
+ *   % JavaLetterOrDigit {JavaLetterOrDigit}
+ *
+ * Name:
+ *   Identifier
+ *   Name . Identifier
+ *
+ * Type:
+ *   same as in section 4.1 of JLS but without any annotations
+ *
+ * Identifier:
+ *   same as in section 3 of JLS
+ *
+ * JavaLetterOrDigit:
+ *   same as in section 3 of JLS
+ *
+ * StringLiteral:
+ *   same as in section 3 of JLS
+ *
+ * NullLiteral:
+ *   same as in section 3 of JLS
+ * }
+ */
+public final class OpParser {
+
+    static final TypeElement.ExternalizedTypeElement VOID =
+            new TypeElement.ExternalizedTypeElement("void", List.of());
+
+    /**
+     * Parse a code model from its serialized textual form obtained from an input stream.
+     *
+     * @param opFactory the operation factory used to construct operations from their general definition
+     * @param in the input stream
+     * @return the list of operations
+     * @throws IOException if parsing fails
+     */
+    public static List<Op> fromStream(OpFactory opFactory, InputStream in) throws IOException {
+        return fromStream(opFactory, CoreTypeFactory.CORE_TYPE_FACTORY, in);
+    }
+
+    public static List<Op> fromStream(OpFactory opFactory, TypeElementFactory typeFactory, InputStream in) throws IOException {
+        String s = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        return fromString(opFactory, typeFactory, s);
+    }
+
+    /**
+     * Parse a code model from its serialized textual form obtained from an input string.
+     *
+     * @param opFactory the operation factory used to construct operations from their general definition
+     * @param in the input string
+     * @return the list of operations
+     */
+    public static List<Op> fromString(OpFactory opFactory, String in) {
+        return parse(opFactory, CoreTypeFactory.CORE_TYPE_FACTORY, in);
+    }
+
+    public static List<Op> fromString(OpFactory opFactory, TypeElementFactory typeFactory, String in) {
+        return parse(opFactory, typeFactory, in);
+    }
+
+    /**
+     * Parse a code model, modeling a method, from its serialized textual form obtained from an input string.
+     *
+     * @param in the input string
+     * @return the func operation
+     */
+    public static Op fromStringOfFuncOp(String in) {
+        Op op = fromString(ExtendedOp.FACTORY, in).get(0);
+        if (!(op instanceof CoreOp.FuncOp)) {
+            throw new IllegalArgumentException("Op is not a FuncOp: " + op);
+        }
+        return op;
+    }
+
+    static List<Op> parse(OpFactory opFactory, TypeElementFactory typeFactory, String in) {
+        Lexer lexer = Scanner.factory().newScanner(in);
+        lexer.nextToken();
+
+        List<OpNode> opNodes = new OpParser(lexer).parseNodes();
+
+        Context c = new Context(opFactory, typeFactory);
+        return opNodes.stream().map(n -> nodeToOp(n, VOID, c, null)).toList();
+    }
+
+
+    static final class Context {
+        final Context parent;
+        final OpFactory opFactory;
+        final TypeElementFactory typeFactory;
+        final Map<String, Value> valueMap;
+        final Map<String, Block.Builder> blockMap;
+
+        Context(Context that, boolean isolated) {
+            this.parent = that;
+            this.opFactory = that.opFactory;
+            this.typeFactory = that.typeFactory;
+            this.valueMap = isolated ? new HashMap<>() : new HashMap<>(that.valueMap);
+            this.blockMap = new HashMap<>();
+        }
+
+        Context(OpFactory opFactory, TypeElementFactory typeFactory) {
+            this.parent = null;
+            this.opFactory = opFactory;
+            this.typeFactory = typeFactory;
+            this.valueMap = new HashMap<>();
+            this.blockMap = new HashMap<>();
+        }
+
+        Context fork(boolean isolated) {
+            return new Context(this, isolated);
+        }
+
+        void putValue(String name, Value opr) {
+            valueMap.put(name, opr);
+        }
+
+        Value getValue(String name) {
+            Value value = valueMap.get(name);
+            if (value == null) {
+                // @@@ location
+                throw new IllegalArgumentException("Undeclared value referenced: " + name);
+            }
+
+            return value;
+        }
+
+        void putBlock(String name, Block.Builder bm) {
+            blockMap.put(name, bm);
+        }
+
+        Block.Builder getBlock(String name) {
+            Block.Builder block = blockMap.get(name);
+            if (block == null) {
+                // @@@ location
+                throw new IllegalArgumentException("Undeclared block referenced: " + name);
+            }
+
+            return block;
+        }
+    }
+
+    static Op nodeToOp(OpNode opNode, TypeElement.ExternalizedTypeElement rtype, Context c, Body.Builder ancestorBody) {
+        ExternalizableOp.ExternalizedOp opdef = nodeToOpDef(opNode, rtype, c, ancestorBody);
+        return c.opFactory.constructOpOrFail(opdef);
+    }
+
+    static ExternalizableOp.ExternalizedOp nodeToOpDef(OpNode opNode, TypeElement.ExternalizedTypeElement rtype, Context c, Body.Builder ancestorBody) {
+        String operationName = opNode.name;
+        List<Value> operands = opNode.operands.stream().map(c::getValue).toList();
+        List<Block.Reference> successors = opNode.successors.stream()
+                .map(n -> nodeToSuccessor(n, c)).toList();
+        List<Body.Builder> bodies = opNode.bodies.stream()
+                .map(n -> nodeToBody(n, c.fork(false), ancestorBody)).toList();
+        return new ExternalizableOp.ExternalizedOp(operationName,
+                operands,
+                successors,
+                c.typeFactory.constructType(rtype),
+                opNode.attributes,
+                bodies);
+    }
+
+    static Body.Builder nodeToBody(BodyNode n, Context c, Body.Builder ancestorBody) {
+        Body.Builder body = Body.Builder.of(ancestorBody,
+                // Create function type with just the return type and add parameters afterward
+                FunctionType.functionType(c.typeFactory.constructType(n.rtype)));
+        Block.Builder eb = body.entryBlock();
+
+        // Create blocks upfront for forward referencing successors
+        for (int i = 0; i < n.blocks.size(); i++) {
+            BlockNode bn = n.blocks.get(i);
+            Block.Builder b;
+            if (i == 0) {
+                b = body.entryBlock();
+            } else {
+                b = eb.block();
+                c.putBlock(bn.name, b);
+            }
+
+            for (ValueNode a : bn.parameters) {
+                Block.Parameter v = b.parameter(c.typeFactory.constructType(a.type));
+                c.putValue(a.name, v);
+            }
+        }
+
+        // Create operations
+        for (int i = 0; i < n.blocks.size(); i++) {
+            BlockNode bn = n.blocks.get(i);
+            Block.Builder b;
+            if (i == 0) {
+                b = body.entryBlock();
+            } else {
+                b = c.getBlock(n.blocks.get(i).name);
+            }
+
+            for (OpNode on : bn.ops) {
+                ValueNode r = on.result;
+                if (r != null) {
+                    Op.Result v = b.op(nodeToOp(on, r.type, c, body));
+                    c.putValue(r.name, v);
+                } else {
+                    b.op(nodeToOp(on, VOID, c, body));
+                }
+            }
+        }
+
+        return body;
+    }
+
+    static Block.Reference nodeToSuccessor(SuccessorNode n, Context c) {
+        return c.getBlock(n.blockName).successor(n.arguments().stream().map(c::getValue).toList());
+    }
+
+    // @@@ Add tokens to access position of nodes on error
+
+    record OpNode(ValueNode result,
+                  String name,
+                  List<String> operands,
+                  List<SuccessorNode> successors,
+                  Map<String, Object> attributes,
+                  List<BodyNode> bodies) {
+    }
+
+    record SuccessorNode(String blockName,
+                         List<String> arguments) {
+    }
+
+    record BodyNode(TypeElement.ExternalizedTypeElement rtype,
+                    List<BlockNode> blocks) {
+    }
+
+    record BlockNode(String name,
+                     List<ValueNode> parameters,
+                     List<OpNode> ops) {
+    }
+
+    record ValueNode(String name,
+                     TypeElement.ExternalizedTypeElement type) {
+    }
+
+    final Lexer lexer;
+
+    OpParser(Lexer lexer) {
+        this.lexer = lexer;
+    }
+
+    List<OpNode> parseNodes() {
+        List<OpNode> ops = new ArrayList<>();
+        while (lexer.token().kind != Tokens.TokenKind.EOF) {
+            ops.add(parseOperation());
+        }
+        return ops;
+    }
+
+    OpNode parseOperation() {
+        ValueNode operationResult;
+        if (lexer.is(Tokens.TokenKind.VALUE_IDENTIFIER)) {
+            operationResult = parseValueNode();
+            lexer.accept(Tokens.TokenKind.EQ);
+        } else {
+            operationResult = null;
+        }
+
+        String operationName = parseName();
+
+        // Operands
+        final List<String> operands;
+        if (lexer.is(Tokens.TokenKind.VALUE_IDENTIFIER)) {
+            operands = parseOperands();
+        } else {
+            operands = List.of();
+        }
+
+        // Successors
+        // ^name(%x, %d)
+        final List<SuccessorNode> successors;
+        if (lexer.is(Tokens.TokenKind.CARET)) {
+            successors = parseSuccessors();
+        } else {
+            successors = List.of();
+        }
+
+        // Attributes
+        final Map<String, Object> attributes;
+        if (lexer.is(Tokens.TokenKind.MONKEYS_AT)) {
+            attributes = parseAttributes();
+        } else {
+            attributes = Map.of();
+        }
+
+        // Bodies
+        List<BodyNode> bodies;
+        if (lexer.is(Tokens.TokenKind.CARET) || lexer.is(Tokens.TokenKind.LPAREN)) {
+            bodies = parseBodies();
+        } else {
+            bodies = List.of();
+        }
+
+        lexer.accept(Tokens.TokenKind.SEMI);
+
+        return new OpNode(operationResult, operationName, operands, successors, attributes, bodies);
+    }
+
+    Map<String, Object> parseAttributes() {
+        Map<String, Object> attributes = new HashMap<>();
+        while (lexer.acceptIf(Tokens.TokenKind.MONKEYS_AT)) {
+            String attributeName;
+            if (lexer.is(Tokens.TokenKind.IDENTIFIER)) {
+                attributeName = parseName();
+                lexer.accept(Tokens.TokenKind.EQ);
+            } else {
+                attributeName = "";
+            }
+            Object attributeValue = parseAttributeValue();
+            attributes.put(attributeName, attributeValue);
+        }
+        return attributes;
+    }
+
+    Object parseAttributeValue() {
+        if (lexer.is(Tokens.TokenKind.IDENTIFIER)) {
+            return parseName();
+        }
+
+        Object value = parseLiteral(lexer.token());
+        lexer.nextToken();
+
+        return value;
+    }
+
+    Object parseLiteral(Tokens.Token t) {
+        return switch (t.kind) {
+            case STRINGLITERAL -> t.stringVal();
+            case NULL -> ExternalizableOp.NULL_ATTRIBUTE_VALUE;
+            default -> throw lexer.unexpected();
+        };
+    }
+
+    List<String> parseOperands() {
+        List<String> operands = new ArrayList<>();
+        while (lexer.is(Tokens.TokenKind.VALUE_IDENTIFIER)) {
+            operands.add(lexer.token().name().substring(1));
+            lexer.nextToken();
+        }
+        return operands;
+    }
+
+    List<SuccessorNode> parseSuccessors() {
+        List<SuccessorNode> successors = new ArrayList<>();
+
+        while (lexer.is(Tokens.TokenKind.CARET) && !isBody()) {
+            lexer.nextToken();
+            successors.add(parseSuccessor());
+        }
+
+        return successors;
+    }
+
+    // Lookahead from "^" to determine if Body
+    boolean isBody() {
+        assert lexer.is(Tokens.TokenKind.CARET);
+
+        int pos = 1;
+        lexer.token(pos++);
+        assert lexer.token(1).kind == Tokens.TokenKind.IDENTIFIER;
+
+        if (lexer.token(pos++).kind != Tokens.TokenKind.LPAREN) {
+            return false;
+        }
+
+        Tokens.Token t;
+        while ((t = lexer.token(pos++)).kind != Tokens.TokenKind.RPAREN) {
+            if (t.kind == Tokens.TokenKind.EOF) {
+                return false;
+            } else if (t.kind == Tokens.TokenKind.COLON) {
+                // Encountered Value
+                return true;
+            }
+        }
+
+        // Encountered return type
+        return lexer.token(pos++).kind == Tokens.TokenKind.IDENTIFIER;
+    }
+
+    SuccessorNode parseSuccessor() {
+        String blockName = lexer.accept(Tokens.TokenKind.IDENTIFIER).name();
+
+        List<String> arguments = new ArrayList<>();
+        if (lexer.acceptIf(Tokens.TokenKind.LPAREN) && !lexer.acceptIf(Tokens.TokenKind.RPAREN)) {
+            do {
+                arguments.add(lexer.accept(Tokens.TokenKind.VALUE_IDENTIFIER).name().substring(1));
+            } while (lexer.acceptIf(Tokens.TokenKind.COMMA));
+            lexer.accept(Tokens.TokenKind.RPAREN);
+        }
+
+        return new SuccessorNode(blockName, arguments);
+    }
+
+    List<BodyNode> parseBodies() {
+        List<BodyNode> bodies = new ArrayList<>();
+        while (lexer.is(Tokens.TokenKind.CARET) || lexer.is(Tokens.TokenKind.LPAREN)) {
+            BodyNode body = parseBody();
+            bodies.add(body);
+        }
+        return bodies;
+    }
+
+    BodyNode parseBody() {
+        // Body name
+        final String bodyName;
+        if (lexer.acceptIf(Tokens.TokenKind.CARET)) {
+            bodyName = lexer.accept(Tokens.TokenKind.IDENTIFIER).name();
+        } else {
+            bodyName = null;
+        }
+
+        // Entry block header
+        List<ValueNode> arguments = parseBlockHeaderArguments(true);
+        // Body return type
+        TypeElement.ExternalizedTypeElement rtype = parseExTypeElem();
+
+        lexer.accept(Tokens.TokenKind.ARROW);
+        lexer.accept(Tokens.TokenKind.LBRACE);
+
+        List<BlockNode> blocks = parseBlocks(bodyName, arguments);
+
+        lexer.accept(Tokens.TokenKind.RBRACE);
+
+        return new BodyNode(rtype, blocks);
+    }
+
+    List<ValueNode> parseBlockHeaderArguments(boolean isEntryBlock) {
+        boolean parseArguments;
+        if (isEntryBlock) {
+            lexer.accept(Tokens.TokenKind.LPAREN);
+            parseArguments = true;
+        } else {
+            parseArguments = lexer.acceptIf(Tokens.TokenKind.LPAREN);
+        }
+        if (!parseArguments || lexer.acceptIf(Tokens.TokenKind.RPAREN)) {
+            return new ArrayList<>();
+        }
+
+        List<ValueNode> arguments = new ArrayList<>();
+        do {
+            arguments.add(parseValueNode());
+        } while (lexer.acceptIf(Tokens.TokenKind.COMMA));
+        lexer.accept(Tokens.TokenKind.RPAREN);
+
+        return arguments;
+    }
+
+    ValueNode parseValueNode() {
+        String valueName = lexer.accept(Tokens.TokenKind.VALUE_IDENTIFIER).name().substring(1);
+
+        lexer.accept(Tokens.TokenKind.COLON);
+
+        TypeElement.ExternalizedTypeElement type = parseExTypeElem();
+
+        return new ValueNode(valueName, type);
+    }
+
+    List<BlockNode> parseBlocks(String entryBlockName, List<ValueNode> entryBlockArguments) {
+        List<BlockNode> blocks = new ArrayList<>();
+
+        // Entry block ops
+        BlockNode entryBlock = new BlockNode(entryBlockName, entryBlockArguments, parseOperations());
+        blocks.add(entryBlock);
+
+        // Subsequent blocks
+        while (lexer.acceptIf(Tokens.TokenKind.CARET)) {
+            String blockName = lexer.accept(Tokens.TokenKind.IDENTIFIER).name();
+            List<ValueNode> blockArguments = parseBlockHeaderArguments(false);
+
+            lexer.accept(Tokens.TokenKind.COLON);
+
+            BlockNode block = new BlockNode(blockName, blockArguments, parseOperations());
+            blocks.add(block);
+        }
+
+        return blocks;
+    }
+
+    List<OpNode> parseOperations() {
+        List<OpNode> ops = new ArrayList<>();
+        while (lexer.is(Tokens.TokenKind.MONKEYS_AT) || lexer.is(Tokens.TokenKind.VALUE_IDENTIFIER) || lexer.is(Tokens.TokenKind.IDENTIFIER)) {
+            OpNode op = parseOperation();
+            ops.add(op);
+        }
+        return ops;
+    }
+
+    String parseName() {
+        Tokens.Token t = lexer.accept(Tokens.TokenKind.IDENTIFIER);
+        StringBuilder name = new StringBuilder();
+        name.append(t.name());
+        while (lexer.acceptIf(Tokens.TokenKind.DOT)) {
+            name.append(Tokens.TokenKind.DOT.name);
+            t = lexer.accept(Tokens.TokenKind.IDENTIFIER);
+            name.append(t.name());
+        }
+        return name.toString();
+    }
+
+    TypeElement.ExternalizedTypeElement parseExTypeElem() {
+        return DescParser.parseExTypeElem(lexer);
+    }
+}
+
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/DescParser.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/DescParser.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/DescParser.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.lang.reflect.code.parser.impl.Tokens.Token;
+import java.lang.reflect.code.parser.impl.Tokens.TokenKind;
+import java.lang.reflect.code.type.*;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.RecordTypeRef;
+import java.lang.reflect.code.type.impl.FieldRefImpl;
+import java.lang.reflect.code.type.impl.MethodRefImpl;
+import java.lang.reflect.code.type.impl.RecordTypeRefImpl;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class DescParser {
+    private DescParser() {}
+
+    /**
+     * Parse an externalized type element from its serialized textual form.
+     * @param desc the serialized externalized type element
+     * @return the externalized type element
+     */
+    public static TypeElement.ExternalizedTypeElement parseExTypeElem(String desc) {
+        Scanner s = Scanner.factory().newScanner(desc);
+        s.nextToken();
+        return parseExTypeElem(s);
+    }
+
+    /**
+     * Parse a method reference from its serialized textual form.
+     *
+     * @param desc the serialized method reference
+     * @return the method reference
+     */
+    public static MethodRef parseMethodRef(String desc) {
+        Scanner s = Scanner.factory().newScanner(desc);
+        s.nextToken();
+        return parseMethodRef(s);
+    }
+
+    /**
+     * Parse a field reference from its serialized textual form.
+     *
+     * @param desc the serialized field reference
+     * @return the field reference
+     */
+    public static FieldRef parseFieldRef(String desc) {
+        Scanner s = Scanner.factory().newScanner(desc);
+        s.nextToken();
+        return parseFieldRef(s);
+    }
+
+    /**
+     * Parse a record type reference from its serialized textual form.
+     *
+     * @param desc the serialized record type reference
+     * @return the record type reference
+     */
+    public static RecordTypeRef parseRecordTypeRef(String desc) {
+        Scanner s = Scanner.factory().newScanner(desc);
+        s.nextToken();
+        return parseRecordTypeRef(s);
+    }
+
+    public static TypeElement.ExternalizedTypeElement parseExTypeElem(Lexer l) {
+        StringBuilder identifier = new StringBuilder();
+        if (l.token().kind == TokenKind.HASH) {
+            // Quoted identifier
+            Token t = l.token();
+            while (t.kind != TokenKind.LT) {
+                identifier.append(t.kind == TokenKind.IDENTIFIER ? t.name() : t.kind.name);
+                l.nextToken();
+                t = l.token();
+            }
+        } else {
+            // Qualified identifier
+            Tokens.Token t = l.accept(TokenKind.IDENTIFIER,
+                    TokenKind.PLUS, TokenKind.SUB);
+            identifier.append(t.kind == TokenKind.IDENTIFIER ? t.name() : t.kind.name);
+            while (l.acceptIf(Tokens.TokenKind.DOT)) {
+                identifier.append(Tokens.TokenKind.DOT.name);
+                t = l.accept(Tokens.TokenKind.IDENTIFIER);
+                identifier.append(t.name());
+            }
+        }
+
+        // Type parameters
+        List<TypeElement.ExternalizedTypeElement> args;
+        if (l.token().kind == Tokens.TokenKind.LT) {
+            args = new ArrayList<>();
+            do {
+                l.nextToken();
+                TypeElement.ExternalizedTypeElement arg = parseExTypeElem(l);
+                args.add(arg);
+            } while (l.token().kind == Tokens.TokenKind.COMMA);
+            l.accept(Tokens.TokenKind.GT);
+        } else {
+            args = List.of();
+        }
+
+        // @@@ Enclosed/inner classes, separated by $ which may also be parameterized
+
+        // Parse array-like syntax []+
+        int dims = 0;
+        while (l.acceptIf(Tokens.TokenKind.LBRACKET)) {
+            l.accept(Tokens.TokenKind.RBRACKET);
+            dims++;
+        }
+
+        TypeElement.ExternalizedTypeElement td = new TypeElement.ExternalizedTypeElement(identifier.toString(), args);
+        if (dims > 0) {
+            // If array-like then type definition becomes a child with identifier [+
+            return new TypeElement.ExternalizedTypeElement("[".repeat(dims), List.of(td));
+        } else {
+            return td;
+        }
+    }
+
+    static TypeElement parseTypeElement(Lexer l) {
+        TypeElement.ExternalizedTypeElement typeDesc = parseExTypeElem(l);
+        return CoreTypeFactory.CORE_TYPE_FACTORY.constructType(typeDesc);
+    }
+
+    // (T, T, T, T)R
+    static FunctionType parseMethodType(Lexer l) {
+        List<TypeElement> ptypes = new ArrayList<>();
+        l.accept(Tokens.TokenKind.LPAREN);
+        if (l.token().kind != Tokens.TokenKind.RPAREN) {
+            ptypes.add(parseTypeElement(l));
+            while (l.acceptIf(Tokens.TokenKind.COMMA)) {
+                ptypes.add(parseTypeElement(l));
+            }
+        }
+        l.accept(Tokens.TokenKind.RPAREN);
+        TypeElement rtype = parseTypeElement(l);
+        return FunctionType.functionType(rtype, ptypes);
+    }
+
+    static MethodRef parseMethodRef(Lexer l) {
+        TypeElement refType = parseTypeElement(l);
+
+        l.accept(Tokens.TokenKind.COLCOL);
+
+        String methodName;
+        if (l.acceptIf(Tokens.TokenKind.LT)) {
+            // Special name such as "<new>"
+            Tokens.Token t = l.accept(Tokens.TokenKind.IDENTIFIER);
+            l.accept(Tokens.TokenKind.GT);
+            methodName = "<" + t.name() + ">";
+        } else {
+            methodName = l.accept(Tokens.TokenKind.IDENTIFIER).name();
+        }
+
+        FunctionType mtype = parseMethodType(l);
+
+        return new MethodRefImpl(refType, methodName, mtype);
+    }
+
+    static FieldRef parseFieldRef(Lexer l) {
+        TypeElement refType = parseTypeElement(l);
+
+        l.accept(Tokens.TokenKind.COLCOL);
+
+        String fieldName = l.accept(Tokens.TokenKind.IDENTIFIER).name();
+
+        FunctionType mtype = parseMethodType(l);
+        if (!mtype.parameterTypes().isEmpty()) {
+            throw new IllegalArgumentException();
+        }
+        return new FieldRefImpl(refType, fieldName, mtype.returnType());
+    }
+
+    static RecordTypeRef parseRecordTypeRef(Lexer l) {
+        List<RecordTypeRef.ComponentRef> components = new ArrayList<>();
+        l.accept(Tokens.TokenKind.LPAREN);
+        if (l.token().kind != Tokens.TokenKind.RPAREN) {
+            do {
+                TypeElement componentType = parseTypeElement(l);
+                String componentName = l.accept(Tokens.TokenKind.IDENTIFIER).name();
+
+                components.add(new RecordTypeRef.ComponentRef(componentType, componentName));
+            } while(l.acceptIf(Tokens.TokenKind.COMMA));
+        }
+        l.accept(Tokens.TokenKind.RPAREN);
+        TypeElement recordType = parseTypeElement(l);
+        return new RecordTypeRefImpl(recordType, components);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Errors.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Errors.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Errors.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+final class Errors {
+
+    private Errors() {
+    }
+
+    static final class Error {
+        String s;
+
+        Error(String s) {
+            this.s = s;
+        }
+
+        @Override
+        public String toString() {
+            return s;
+        }
+    }
+
+    static final Error IllegalUnicodeEsc = new Error("illegal unicode escape");
+    static final Error IllegalNonasciiDigit = new Error("illegal non-ASCII digit");
+    static final Error IllegalEscChar = new Error("illegal escape character");
+    static final Error IllegalUnderscore = new Error("illegal underscore");
+    static final Error IllegalLineEndInCharLit = new Error("illegal line end in character literal");
+    static final Error IllegalDot = new Error("illegal '.'");
+    static final Error MalformedFpLit = new Error("malformed floating-point literal");
+    static final Error InvalidHexNumber = new Error("hexadecimal numbers must contain at least one hexadecimal digit");
+    static final Error InvalidBinaryNumber = new Error("binary numbers must contain at least one binary digit");
+    static final Error EmptyCharLit = new Error("empty character literal");
+    static final Error UnclosedStrLit = new Error("unclosed string literal");
+    static final Error UnclosedComment = new Error("unclosed comment");
+    static final Error UnclosedCharLit = new Error("unclosed character literal");
+
+    static Error IllegalChar(String c) {
+        return new Error("unmappable character for encoding " + c);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/JavaBasedTokenizer.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/JavaBasedTokenizer.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/JavaBasedTokenizer.java
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.lang.reflect.code.parser.impl.Tokens.TokenKind;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The lexical analyzer maps an input stream consisting of UTF-8 characters and unicode
+ * escape sequences into a token sequence.
+ */
+final class JavaBasedTokenizer extends UnicodeReader {
+    /**
+     * If true then prints token information after each nextToken().
+     */
+    private static final boolean scannerDebug = false;
+
+    /**
+     * Sentinel for non-value.
+     */
+    private static final int NOT_FOUND = -1;
+
+    /**
+     * The log to be used for error reporting. Copied from scanner factory.
+     */
+    private final Log log;
+
+    /**
+     * The token factory. Copied from scanner factory.
+     */
+    private final Tokens tokens;
+
+    /**
+     * The token kind, set by nextToken().
+     */
+    Tokens.TokenKind tk;
+
+    /**
+     * The token's radix, set by nextToken().
+     */
+    int radix;
+
+    /**
+     * The token's name, set by nextToken().
+     */
+    String name;
+
+    /**
+     * The position where a lexical error occurred;
+     */
+    int errPos = Position.NOPOS;
+
+    /**
+     * true if contains escape sequences, set by nextToken().
+     */
+    boolean hasEscapeSequences;
+
+    /**
+     * Buffer for building literals, used by nextToken().
+     */
+    StringBuilder sb;
+
+    /**
+     * Construct a Java token scanner from the input character array.
+     *
+     * @param fac    the factory which created this Scanner
+     * @param array  the input character array.
+     * @param length The length of the meaningful content in the array.
+     */
+    JavaBasedTokenizer(Scanner.Factory fac, char[] array, int length) {
+        super(fac, array, length);
+        this.log = fac.log;
+        this.tokens = fac.tokens;
+        this.sb = new StringBuilder(256);
+    }
+
+    /**
+     * Report an error at the given position using the provided arguments.
+     *
+     * @param pos position in input buffer.
+     * @param key error key to report.
+     */
+    void lexError(int pos, Errors.Error key) {
+        tk = Tokens.TokenKind.ERROR;
+        errPos = pos;
+        log.error(pos, key);
+    }
+
+    /**
+     * Add a character to the literal buffer.
+     *
+     * @param ch character to add.
+     */
+    void put(char ch) {
+        sb.append(ch);
+    }
+
+    /**
+     * Add a codepoint to the literal buffer.
+     *
+     * @param codePoint codepoint to add.
+     */
+    void putCodePoint(int codePoint) {
+        sb.appendCodePoint(codePoint);
+    }
+
+    /**
+     * Add current character or codepoint to the literal buffer.
+     */
+    void put() {
+        if (isSurrogate()) {
+            putCodePoint(getCodepoint());
+        } else {
+            put(get());
+        }
+    }
+
+    /**
+     * Add a string to the literal buffer.
+     */
+    void put(String string) {
+        sb.append(string);
+    }
+
+    /**
+     * Add current character or codepoint to the literal buffer then return next character.
+     */
+    char putThenNext() {
+        put();
+
+        return next();
+    }
+
+    /**
+     * If the specified character ch matches the current character then add current character
+     * to the literal buffer and then advance.
+     *
+     * @param ch character to match.
+     * @return true if ch matches current character.
+     */
+    boolean acceptThenPut(char ch) {
+        if (is(ch)) {
+            put(get());
+            next();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * If either ch1 or ch2 matches the current character then add current character
+     * to the literal buffer and then advance.
+     *
+     * @param ch1 first character to match.
+     * @param ch2 second character to match.
+     * @return true if either ch1 or ch2 matches current character.
+     */
+    boolean acceptOneOfThenPut(char ch1, char ch2) {
+        if (isOneOf(ch1, ch2)) {
+            put(get());
+            next();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Test if the current character is a line terminator.
+     *
+     * @return true if current character is a line terminator.
+     */
+    private boolean isEOLN() {
+        return isOneOf('\n', '\r');
+    }
+
+    /**
+     * Processes the current character and places in the literal buffer. If the current
+     * character is a backslash then the next character is validated as a proper
+     * escape character. Conversion of escape sequences takes place at end of nextToken().
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanLitChar(int pos) {
+        if (acceptThenPut('\\')) {
+            hasEscapeSequences = true;
+
+            switch (get()) {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                    char leadch = get();
+                    putThenNext();
+
+                    if (inRange('0', '7')) {
+                        putThenNext();
+
+                        if (leadch <= '3' && inRange('0', '7')) {
+                            putThenNext();
+                        }
+                    }
+                    break;
+
+                case 'b':
+                case 't':
+                case 'n':
+                case 'f':
+                case 'r':
+                case '\'':
+                case '\"':
+                case '\\':
+                    putThenNext();
+                    break;
+
+                case '\n':
+                case '\r':
+                    lexError(position(), Errors.IllegalEscChar);
+                    break;
+
+                default:
+                    lexError(position(), Errors.IllegalEscChar);
+                    break;
+            }
+        } else {
+            putThenNext();
+        }
+    }
+
+    /**
+     * Scan a string literal.
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanString(int pos) {
+        // Assume the best.
+        tk = Tokens.TokenKind.STRINGLITERAL;
+        // Skip first quote.
+        next();
+
+        // While characters are available.
+        while (isAvailable()) {
+            if (accept('\"')) {
+                return;
+            }
+
+            if (isEOLN()) {
+                // Line terminator in string literal is an error.
+                // Fall out to unclosed string literal error.
+                break;
+            } else {
+                // Add character to string buffer.
+                scanLitChar(pos);
+            }
+        }
+
+        lexError(pos, Errors.UnclosedStrLit);
+    }
+
+    /**
+     * Scan sequence of digits.
+     *
+     * @param pos        position of the first character in literal.
+     * @param digitRadix radix of numeric literal.
+     */
+    private void scanDigits(int pos, int digitRadix) {
+        int leadingUnderscorePos = is('_') ? position() : NOT_FOUND;
+        int trailingUnderscorePos;
+
+        do {
+            if (!is('_')) {
+                put();
+                trailingUnderscorePos = NOT_FOUND;
+            } else {
+                trailingUnderscorePos = position();
+            }
+
+            next();
+        } while (digit(pos, digitRadix) >= 0 || is('_'));
+
+        if (leadingUnderscorePos != NOT_FOUND) {
+            lexError(leadingUnderscorePos, Errors.IllegalUnderscore);
+        } else if (trailingUnderscorePos != NOT_FOUND) {
+            lexError(trailingUnderscorePos, Errors.IllegalUnderscore);
+        }
+    }
+
+    /**
+     * Read fractional part of hexadecimal floating point number.
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanHexExponentAndSuffix(int pos) {
+        if (acceptOneOfThenPut('p', 'P')) {
+            skipIllegalUnderscores();
+            acceptOneOfThenPut('+', '-');
+            skipIllegalUnderscores();
+
+            if (digit(pos, 10) >= 0) {
+                scanDigits(pos, 10);
+            } else {
+                lexError(pos, Errors.MalformedFpLit);
+            }
+        } else {
+            lexError(pos, Errors.MalformedFpLit);
+        }
+
+        if (acceptOneOfThenPut('f', 'F')) {
+            tk = Tokens.TokenKind.FLOATLITERAL;
+            radix = 16;
+        } else {
+            acceptOneOfThenPut('d', 'D');
+            tk = Tokens.TokenKind.DOUBLELITERAL;
+            radix = 16;
+        }
+    }
+
+    /**
+     * Read fractional part of floating point number.
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanFraction(int pos) {
+        skipIllegalUnderscores();
+
+        if (digit(pos, 10) >= 0) {
+            scanDigits(pos, 10);
+        }
+
+        int index = sb.length();
+
+        if (acceptOneOfThenPut('e', 'E')) {
+            skipIllegalUnderscores();
+            acceptOneOfThenPut('+', '-');
+            skipIllegalUnderscores();
+
+            if (digit(pos, 10) >= 0) {
+                scanDigits(pos, 10);
+                return;
+            }
+
+            lexError(pos, Errors.MalformedFpLit);
+            sb.setLength(index);
+        }
+    }
+
+    /**
+     * Read fractional part and 'd' or 'f' suffix of floating point number.
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanFractionAndSuffix(int pos) {
+        radix = 10;
+        scanFraction(pos);
+
+        if (acceptOneOfThenPut('f', 'F')) {
+            tk = Tokens.TokenKind.FLOATLITERAL;
+        } else {
+            acceptOneOfThenPut('d', 'D');
+            tk = Tokens.TokenKind.DOUBLELITERAL;
+        }
+    }
+
+    /**
+     * Read fractional part and 'd' or 'f' suffix of hexadecimal floating point number.
+     *
+     * @param pos position of the first character in literal.
+     */
+    private void scanHexFractionAndSuffix(int pos, boolean seendigit) {
+        radix = 16;
+        assert is('.');
+        putThenNext();
+        skipIllegalUnderscores();
+
+        if (digit(pos, 16) >= 0) {
+            seendigit = true;
+            scanDigits(pos, 16);
+        }
+
+        if (!seendigit)
+            lexError(pos, Errors.InvalidHexNumber);
+        else
+            scanHexExponentAndSuffix(pos);
+    }
+
+    /**
+     * Skip over underscores and report as a error if found.
+     */
+    private void skipIllegalUnderscores() {
+        if (is('_')) {
+            lexError(position(), Errors.IllegalUnderscore);
+            skip('_');
+        }
+    }
+
+    /**
+     * Read a number. (Spec. 3.10)
+     *
+     * @param pos   position of the first character in literal.
+     * @param radix the radix of the number; one of 2, 8, 10, 16.
+     */
+    private void scanNumber(int pos, int radix) {
+        // for octal, allow base-10 digit in case it's a float literal
+        this.radix = radix;
+        int digitRadix = (radix == 8 ? 10 : radix);
+        int firstDigit = digit(pos, Math.max(10, digitRadix));
+        boolean seendigit = firstDigit >= 0;
+        boolean seenValidDigit = firstDigit >= 0 && firstDigit < digitRadix;
+
+        if (seendigit) {
+            scanDigits(pos, digitRadix);
+        }
+
+        if (radix == 16 && is('.')) {
+            scanHexFractionAndSuffix(pos, seendigit);
+        } else if (seendigit && radix == 16 && isOneOf('p', 'P')) {
+            scanHexExponentAndSuffix(pos);
+        } else if (digitRadix == 10 && is('.')) {
+            putThenNext();
+            scanFractionAndSuffix(pos);
+        } else if (digitRadix == 10 && isOneOf('e', 'E', 'f', 'F', 'd', 'D')) {
+            scanFractionAndSuffix(pos);
+        } else {
+            if (!seenValidDigit) {
+                switch (radix) {
+                    case 2:
+                        lexError(pos, Errors.InvalidBinaryNumber);
+                        break;
+                    case 16:
+                        lexError(pos, Errors.InvalidHexNumber);
+                        break;
+                }
+            }
+            // If it is not a floating point literal,
+            // the octal number should be rescanned correctly.
+            if (radix == 8) {
+                sb.setLength(0);
+                reset(pos);
+                scanDigits(pos, 8);
+            }
+
+            if (acceptOneOf('l', 'L')) {
+                tk = Tokens.TokenKind.LONGLITERAL;
+            } else {
+                tk = Tokens.TokenKind.INTLITERAL;
+            }
+        }
+    }
+
+    /**
+     * Determines if the sequence in the literal buffer is a token (keyword, operator.)
+     */
+    private void checkIdent(Tokens.TokenKind identifier) {
+        name = sb.toString();
+        tk = tokens.lookupKind(name, identifier);
+    }
+
+    /**
+     * Read an identifier. (Spec. 3.8)
+     */
+    private void scanIdent() {
+        scanIdent(Tokens.TokenKind.IDENTIFIER);
+    }
+
+    /**
+     * Read an identifier. (Spec. 3.8)
+     */
+    private void scanIdent(Tokens.TokenKind identifier) {
+        putThenNext();
+
+        do {
+            switch (get()) {
+                case 'A':
+                case 'B':
+                case 'C':
+                case 'D':
+                case 'E':
+                case 'F':
+                case 'G':
+                case 'H':
+                case 'I':
+                case 'J':
+                case 'K':
+                case 'L':
+                case 'M':
+                case 'N':
+                case 'O':
+                case 'P':
+                case 'Q':
+                case 'R':
+                case 'S':
+                case 'T':
+                case 'U':
+                case 'V':
+                case 'W':
+                case 'X':
+                case 'Y':
+                case 'Z':
+                case 'a':
+                case 'b':
+                case 'c':
+                case 'd':
+                case 'e':
+                case 'f':
+                case 'g':
+                case 'h':
+                case 'i':
+                case 'j':
+                case 'k':
+                case 'l':
+                case 'm':
+                case 'n':
+                case 'o':
+                case 'p':
+                case 'q':
+                case 'r':
+                case 's':
+                case 't':
+                case 'u':
+                case 'v':
+                case 'w':
+                case 'x':
+                case 'y':
+                case 'z':
+                case '$':
+                case '_':
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                    break;
+
+                case '\u0000':
+                case '\u0001':
+                case '\u0002':
+                case '\u0003':
+                case '\u0004':
+                case '\u0005':
+                case '\u0006':
+                case '\u0007':
+                case '\u0008':
+                case '\u000E':
+                case '\u000F':
+                case '\u0010':
+                case '\u0011':
+                case '\u0012':
+                case '\u0013':
+                case '\u0014':
+                case '\u0015':
+                case '\u0016':
+                case '\u0017':
+                case '\u0018':
+                case '\u0019':
+                case '\u001B':
+                case '\u007F':
+                    next();
+                    continue;
+
+                case '\u001A': // EOI is also a legal identifier part
+                    if (isAvailable()) {
+                        next();
+                        continue;
+                    }
+
+                    checkIdent(identifier);
+                    return;
+
+                default:
+                    boolean isJavaIdentifierPart;
+
+                    if (isASCII()) {
+                        // all ASCII range chars already handled, above
+                        isJavaIdentifierPart = false;
+                    } else {
+                        if (Character.isIdentifierIgnorable(get())) {
+                            next();
+                            continue;
+                        }
+
+                        isJavaIdentifierPart = isSurrogate()
+                                ? Character.isJavaIdentifierPart(getCodepoint())
+                                : Character.isJavaIdentifierPart(get());
+                    }
+
+                    if (!isJavaIdentifierPart) {
+                        checkIdent(identifier);
+                        return;
+                    }
+            }
+
+            putThenNext();
+        } while (true);
+    }
+
+    /**
+     * Read token (main entrypoint.)
+     */
+    public Tokens.Token readToken() {
+        sb.setLength(0);
+        name = null;
+        radix = 0;
+        hasEscapeSequences = false;
+
+        int pos = 0;
+        List<Tokens.Comment> comments = null;
+
+        try {
+            loop:
+            while (true) {
+                pos = position();
+
+                switch (get()) {
+                    case ' ':  // (Spec 3.6)
+                    case '\t': // (Spec 3.6)
+                    case '\f': // (Spec 3.6)
+                        skipWhitespace();
+                        processWhiteSpace(pos, position());
+                        break;
+
+                    case '\n': // (Spec 3.4)
+                        next();
+                        processLineTerminator(pos, position());
+                        break;
+
+                    case '\r': // (Spec 3.4)
+                        next();
+                        accept('\n');
+                        processLineTerminator(pos, position());
+                        break;
+
+                    case 'A':
+                    case 'B':
+                    case 'C':
+                    case 'D':
+                    case 'E':
+                    case 'F':
+                    case 'G':
+                    case 'H':
+                    case 'I':
+                    case 'J':
+                    case 'K':
+                    case 'L':
+                    case 'M':
+                    case 'N':
+                    case 'O':
+                    case 'P':
+                    case 'Q':
+                    case 'R':
+                    case 'S':
+                    case 'T':
+                    case 'U':
+                    case 'V':
+                    case 'W':
+                    case 'X':
+                    case 'Y':
+                    case 'Z':
+                    case 'a':
+                    case 'b':
+                    case 'c':
+                    case 'd':
+                    case 'e':
+                    case 'f':
+                    case 'g':
+                    case 'h':
+                    case 'i':
+                    case 'j':
+                    case 'k':
+                    case 'l':
+                    case 'm':
+                    case 'n':
+                    case 'o':
+                    case 'p':
+                    case 'q':
+                    case 'r':
+                    case 's':
+                    case 't':
+                    case 'u':
+                    case 'v':
+                    case 'w':
+                    case 'x':
+                    case 'y':
+                    case 'z':
+                    case '$':
+                    case '_': // (Spec. 3.8)
+                        scanIdent();
+                        break loop;
+
+                    case '%':
+                        scanIdent(Tokens.TokenKind.VALUE_IDENTIFIER);
+                        break loop;
+
+                    case '0': // (Spec. 3.10)
+                        next();
+
+                        if (acceptOneOf('x', 'X')) {
+                            skipIllegalUnderscores();
+                            scanNumber(pos, 16);
+                        } else if (acceptOneOf('b', 'B')) {
+                            skipIllegalUnderscores();
+                            scanNumber(pos, 2);
+                        } else {
+                            put('0');
+
+                            if (is('_')) {
+                                int savePos = position();
+                                skip('_');
+
+                                if (digit(pos, 10) < 0) {
+                                    lexError(savePos, Errors.IllegalUnderscore);
+                                }
+                            }
+
+                            scanNumber(pos, 8);
+                        }
+                        break loop;
+
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':  // (Spec. 3.10)
+                        scanNumber(pos, 10);
+                        break loop;
+
+                    case '.': // (Spec. 3.12)
+                        next();
+                        int savePos = position();
+
+                        if (accept('.')) {
+                            lexError(savePos, Errors.IllegalDot);
+                        } else if (digit(pos, 10) >= 0) {
+                            put('.');
+                            scanFractionAndSuffix(pos); // (Spec. 3.10)
+                        } else {
+                            tk = Tokens.TokenKind.DOT;
+                        }
+                        break loop;
+
+                    case ',': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.COMMA;
+                        break loop;
+
+                    case '(': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.LPAREN;
+                        break loop;
+
+                    case ')': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.RPAREN;
+                        break loop;
+
+                    case '[': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.LBRACKET;
+                        break loop;
+
+                    case ']': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.RBRACKET;
+                        break loop;
+
+                    case '{': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.LBRACE;
+                        break loop;
+
+                    case '}': // (Spec. 3.12)
+                        next();
+                        tk = Tokens.TokenKind.RBRACE;
+                        break loop;
+
+                    case '?':
+                        next();
+                        tk = Tokens.TokenKind.QUES;
+                        break loop;
+
+                    case ';':
+                        next();
+                        tk = Tokens.TokenKind.SEMI;
+                        break loop;
+
+                    case ':':
+                        next();
+                        if (accept(':')) {
+                            tk = Tokens.TokenKind.COLCOL;
+                        } else {
+                            tk = Tokens.TokenKind.COLON;
+                        }
+                        break loop;
+
+                    case '&':
+                        next();
+                        tk = Tokens.TokenKind.AMP;
+                        break loop;
+
+                    case '@':
+                        next();
+                        tk = Tokens.TokenKind.MONKEYS_AT;
+                        break loop;
+
+                    case '^':
+                        next();
+                        tk = Tokens.TokenKind.CARET;
+                        break loop;
+
+                    case '=':
+                        next();
+                        tk = Tokens.TokenKind.EQ;
+                        break loop;
+
+                    case '<':
+                        next();
+                        tk = Tokens.TokenKind.LT;
+                        break loop;
+
+                    case '>':
+                        next();
+                        tk = Tokens.TokenKind.GT;
+                        break loop;
+
+                    case '#':
+                        next();
+                        tk = TokenKind.HASH;
+                        break loop;
+
+                    case '+':
+                        next();
+                        tk = Tokens.TokenKind.PLUS;
+                        break loop;
+
+                    case '-':
+                        next();
+                        if (accept('>')) {
+                            tk = Tokens.TokenKind.ARROW;
+                        } else {
+                            tk = Tokens.TokenKind.SUB;
+                        }
+                        break loop;
+
+                    case '/':
+                        next();
+
+                        if (accept('/')) { // (Spec. 3.7)
+                            skipToEOLN();
+
+                            if (isAvailable()) {
+                                comments = appendComment(comments, processComment(pos, position(), Tokens.Comment.CommentStyle.LINE));
+                            }
+                            break;
+                        } else if (accept('*')) { // (Spec. 3.7)
+                            while (isAvailable()) {
+                                if (accept('*')) {
+                                    if (is('/')) {
+                                        break;
+                                    }
+                                } else {
+                                    next();
+                                }
+                            }
+
+                            if (accept('/')) {
+                                comments = appendComment(comments, processComment(pos, position(), Tokens.Comment.CommentStyle.BLOCK));
+
+                                break;
+                            } else {
+                                lexError(pos, Errors.UnclosedComment);
+
+                                break loop;
+                            }
+                        } else {
+                            lexError(pos, Errors.UnclosedComment);
+                        }
+                        break loop;
+
+                    case '\'': // (Spec. 3.10)
+                        next();
+
+                        if (accept('\'')) {
+                            lexError(pos, Errors.EmptyCharLit);
+                        } else {
+                            if (isEOLN()) {
+                                lexError(pos, Errors.IllegalLineEndInCharLit);
+                            }
+
+                            scanLitChar(pos);
+
+                            if (accept('\'')) {
+                                tk = Tokens.TokenKind.CHARLITERAL;
+                            } else {
+                                lexError(pos, Errors.UnclosedCharLit);
+                            }
+                        }
+                        break loop;
+
+                    case '\"': // (Spec. 3.10)
+                        scanString(pos);
+                        break loop;
+
+                    default:
+                        boolean isJavaIdentifierStart;
+
+                        if (isASCII()) {
+                            // all ASCII range chars already handled, above
+                            isJavaIdentifierStart = false;
+                        } else {
+                            isJavaIdentifierStart = isSurrogate()
+                                    ? Character.isJavaIdentifierStart(getCodepoint())
+                                    : Character.isJavaIdentifierStart(get());
+                        }
+
+                        if (isJavaIdentifierStart) {
+                            scanIdent();
+                        } else if (digit(pos, 10) >= 0) {
+                            scanNumber(pos, 10);
+                        } else if (is((char) EOI) || !isAvailable()) {
+                            tk = Tokens.TokenKind.EOF;
+                            pos = position();
+                        } else {
+                            String arg;
+
+                            if (isSurrogate()) {
+                                int codePoint = getCodepoint();
+                                char hi = Character.highSurrogate(codePoint);
+                                char lo = Character.lowSurrogate(codePoint);
+                                arg = String.format("\\u%04x\\u%04x", (int) hi, (int) lo);
+                            } else {
+                                char ch = get();
+                                arg = (32 < ch && ch < 127) ? String.format("%s", ch) :
+                                        String.format("\\u%04x", (int) ch);
+                            }
+
+                            lexError(pos, Errors.IllegalChar(arg));
+                            next();
+                        }
+                        break loop;
+                }
+            }
+
+            int endPos = position();
+
+            if (tk.tag == Tokens.Token.Tag.DEFAULT) {
+                return new Tokens.Token(tk, pos, endPos, comments);
+            } else if (tk.tag == Tokens.Token.Tag.NAMED) {
+                return new Tokens.NamedToken(tk, pos, endPos, name, comments);
+            } else {
+                // Get characters from string buffer.
+                String string = sb.toString();
+
+                // Translate escape sequences if present.
+                if (hasEscapeSequences) {
+                    try {
+                        string = string.translateEscapes();
+                    } catch (Exception ex) {
+                        // Error already reported, just use untranslated string.
+                    }
+                }
+
+                if (tk.tag == Tokens.Token.Tag.STRING) {
+                    // Build string token.
+                    return new Tokens.StringToken(tk, pos, endPos, string, comments);
+                } else {
+                    // Build numeric token.
+                    return new Tokens.NumericToken(tk, pos, endPos, string, radix, comments);
+                }
+            }
+        } finally {
+            int endPos = position();
+
+            if (scannerDebug) {
+                System.out.println("nextToken(" + pos
+                        + "," + endPos + ")=|" +
+                        new String(getRawCharacters(pos, endPos))
+                        + "| " + tk.name());
+            }
+        }
+    }
+
+    /**
+     * Appends a comment to the list of comments preceding the current token.
+     *
+     * @param comments existing list of comments.
+     * @param comment  comment to append.
+     * @return new list with comment prepended to the existing list.
+     */
+    List<Tokens.Comment> appendComment(List<Tokens.Comment> comments, Tokens.Comment comment) {
+        if (comments == null) {
+            comments = new ArrayList<>();
+        }
+        // prepend
+        comments.add(0, comment);
+        return comments;
+    }
+
+    /**
+     * Return the position where a lexical error occurred.
+     *
+     * @return position in the input buffer of where the error occurred.
+     */
+    public int errPos() {
+        return errPos;
+    }
+
+    /**
+     * Set the position where a lexical error occurred.
+     *
+     * @param pos position in the input buffer of where the error occurred.
+     */
+    public void errPos(int pos) {
+        errPos = pos;
+    }
+
+    /**
+     * Called when a complete comment has been scanned. pos and endPos
+     * will mark the comment boundary.
+     *
+     * @param pos    position of the opening / in the input buffer.
+     * @param endPos position + 1 of the closing / in the input buffer.
+     * @param style  style of comment.
+     * @return the constructed BasicComment.
+     */
+    Tokens.Comment processComment(int pos, int endPos, Tokens.Comment.CommentStyle style) {
+        if (scannerDebug) {
+            System.out.println("processComment(" + pos
+                    + "," + endPos + "," + style + ")=|"
+                    + new String(getRawCharacters(pos, endPos))
+                    + "|");
+        }
+
+        char[] buf = getRawCharacters(pos, endPos);
+        return new BasicComment(style, new String(buf));
+    }
+
+    /**
+     * Called when a complete whitespace run has been scanned. pos and endPos
+     * will mark the whitespace boundary.
+     * <p>
+     * (Spec 3.6)
+     *
+     * @param pos    position in input buffer of first whitespace character.
+     * @param endPos position + 1 in input buffer of last whitespace character.
+     */
+    void processWhiteSpace(int pos, int endPos) {
+        if (scannerDebug) {
+            System.out.println("processWhitespace(" + pos
+                    + "," + endPos + ")=|" +
+                    new String(getRawCharacters(pos, endPos))
+                    + "|");
+        }
+    }
+
+    /**
+     * Called when a line terminator has been processed.
+     *
+     * @param pos    position in input buffer of first character in sequence.
+     * @param endPos position + 1 in input buffer of last character in sequence.
+     */
+    void processLineTerminator(int pos, int endPos) {
+        if (scannerDebug) {
+            System.out.println("processTerminator(" + pos
+                    + "," + endPos + ")=|" +
+                    new String(getRawCharacters(pos, endPos))
+                    + "|");
+        }
+    }
+
+    /**
+     * Build a map for translating between line numbers and positions in the input.
+     *
+     * @return a LineMap
+     */
+    public Position.LineMap getLineMap() {
+        return Position.makeLineMap(getRawCharacters(), length(), false);
+    }
+
+    /**
+     * Scan a documentation comment; determine if a deprecated tag is present.
+     * Called once the initial /, * have been skipped, positioned at the second *
+     * (which is treated as the beginning of the first line).
+     * Stops positioned at the closing '/'.
+     *
+     * @param style Style of comment
+     *              LINE starting with //
+     *              BLOCK starting with /*
+     */
+    record BasicComment(Tokens.Comment.CommentStyle style, String text) implements Tokens.Comment {
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Lexer.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Lexer.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Lexer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.lang.reflect.code.parser.impl.Position.LineMap;
+import java.lang.reflect.code.parser.impl.Tokens.Token;
+import java.util.Arrays;
+
+/**
+ * The lexical analyzer maps an input stream consisting of ASCII
+ * characters and Unicode escapes into a token sequence.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public sealed interface Lexer permits Scanner {
+
+    /**
+     * Consume the next token.
+     */
+    void nextToken();
+
+    /**
+     * Return current token.
+     */
+    Tokens.Token token();
+
+    /**
+     * Return token with given lookahead.
+     */
+    Tokens.Token token(int lookahead);
+
+    /**
+     * Return the last character position of the previous token.
+     */
+    Tokens.Token prevToken();
+
+    /**
+     * Return the position where a lexical error occurred;
+     */
+    int errPos();
+
+    /**
+     * Set the position where a lexical error occurred;
+     */
+    void errPos(int pos);
+
+    /**
+     * Build a map for translating between line numbers and
+     * positions in the input.
+     *
+     * @return a LineMap
+     */
+    LineMap getLineMap();
+
+    default boolean is(Tokens.TokenKind tk) {
+        Tokens.Token t = token();
+        if (t.kind == tk) {
+            return true;
+        }
+        return false;
+    }
+
+    default Tokens.Token accept(Tokens.TokenKind tk) {
+        Tokens.Token t = token();
+        if (t.kind == tk) {
+            nextToken();
+            return t;
+        } else {
+            // @@@ Exception
+            LineMap lineMap = getLineMap();
+            int lineNumber = lineMap.getLineNumber(t.pos);
+            int columnNumber = lineMap.getColumnNumber(t.pos);
+            throw new IllegalArgumentException("Expected " + tk + " but observed " + t.kind +
+                    " " + lineNumber + ":" + columnNumber);
+        }
+    }
+
+    default Tokens.Token accept(Tokens.TokenKind... tks) {
+        Token t = token();
+        for (Tokens.TokenKind tk : tks) {
+            if (acceptIf(tk)) {
+                return t;
+            }
+        }
+        // @@@ Exception
+        LineMap lineMap = getLineMap();
+        int lineNumber = lineMap.getLineNumber(t.pos);
+        int columnNumber = lineMap.getColumnNumber(t.pos);
+        throw new IllegalArgumentException("Expected one of " + Arrays.toString(tks) + " but observed " + t.kind +
+                " " + lineNumber + ":" + columnNumber);
+    }
+
+    default boolean acceptIf(Tokens.TokenKind tk) {
+        Tokens.Token t = token();
+        if (t.kind == tk) {
+            nextToken();
+            return true;
+        }
+        return false;
+    }
+
+    default RuntimeException unexpected() {
+        Tokens.Token t = token();
+        LineMap lineMap = getLineMap();
+        int lineNumber = lineMap.getLineNumber(t.pos);
+        int columnNumber = lineMap.getColumnNumber(t.pos);
+        return new IllegalArgumentException("Unexpected token " + t.kind +
+                " " + lineNumber + ":" + columnNumber);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Log.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Log.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Log.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+final class Log {
+    Log() {
+    }
+
+    public void error(int pos, Errors.Error errorKey) {
+        // @@@ Exception
+        throw new IllegalStateException("Lexer error: " + errorKey.toString() + " :" + pos);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Position.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Position.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Position.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.util.BitSet;
+
+/**
+ * A class that defines source code positions as simple character
+ * offsets from the beginning of the file. The first character
+ * is at position 0.
+ * <p>
+ * Support is also provided for (line,column) coordinates, but tab
+ * expansion is optional and no Unicode escape translation is considered.
+ * The first character is at location (1,1).
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public final class Position {
+    static final int NOPOS = -1;
+
+    static final int FIRSTPOS = 0;
+    static final int FIRSTLINE = 1;
+    static final int FIRSTCOLUMN = 1;
+
+    static final int LINESHIFT = 10;
+    static final int MAXCOLUMN = (1 << LINESHIFT) - 1;
+    static final int MAXLINE = (1 << (Integer.SIZE - LINESHIFT)) - 1;
+
+    static final int MAXPOS = Integer.MAX_VALUE;
+
+    /**
+     * This is class is not supposed to be instantiated.
+     */
+    private Position() {
+    }
+
+    /**
+     * A two-way map between line/column numbers and positions,
+     * derived from a scan done at creation time.  Tab expansion is
+     * optionally supported via a character map.  Text content
+     * is not retained.
+     * <p>
+     * Notes:  The first character position FIRSTPOS is at
+     * (FIRSTLINE,FIRSTCOLUMN).  No account is taken of Unicode escapes.
+     *
+     * @param src        Source characters
+     * @param max        Number of characters to read
+     * @param expandTabs If true, expand tabs when calculating columns
+     */
+    static LineMap makeLineMap(char[] src, int max, boolean expandTabs) {
+        LineMapImpl lineMap = expandTabs ?
+                new LineTabMapImpl(max) : new LineMapImpl();
+        lineMap.build(src, max);
+        return lineMap;
+    }
+
+    /**
+     * Encode line and column numbers in an integer as:
+     * {@code line-number << LINESHIFT + column-number }.
+     * {@link Position#NOPOS} represents an undefined position.
+     *
+     * @param line number of line (first is 1)
+     * @param col  number of character on line (first is 1)
+     * @return an encoded position or {@link Position#NOPOS}
+     * if the line or column number is too big to
+     * represent in the encoded format
+     * @throws IllegalArgumentException if line or col is less than 1
+     */
+    static int encodePosition(int line, int col) {
+        if (line < 1)
+            throw new IllegalArgumentException("line must be greater than 0");
+        if (col < 1)
+            throw new IllegalArgumentException("column must be greater than 0");
+
+        if (line > MAXLINE || col > MAXCOLUMN) {
+            return NOPOS;
+        }
+        return (line << LINESHIFT) + col;
+    }
+
+    public interface LineMap {
+        /**
+         * Finds the start position of a line.
+         *
+         * @param line line number (beginning at 1)
+         * @return position of first character in line
+         * @throws IndexOutOfBoundsException if {@code lineNumber < 1}
+         *                                   if {@code lineNumber > no. of lines}
+         */
+        long getStartPosition(long line);
+
+        /**
+         * Finds the position corresponding to a (line,column).
+         *
+         * @param line   line number (beginning at 1)
+         * @param column tab-expanded column number (beginning 1)
+         * @return position of character
+         * @throws IndexOutOfBoundsException if {@code line < 1}
+         *                                   if {@code line > no. of lines}
+         */
+        long getPosition(long line, long column);
+
+        /**
+         * Finds the line containing a position; a line termination
+         * character is on the line it terminates.
+         *
+         * @param pos character offset of the position
+         * @return the line number of pos (first line is 1)
+         */
+        long getLineNumber(long pos);
+
+        /**
+         * Finds the column for a character position.
+         * Tab characters preceding the position on the same line
+         * will be expanded when calculating the column number.
+         *
+         * @param pos character offset of the position
+         * @return the tab-expanded column number of pos (first column is 1)
+         */
+        long getColumnNumber(long pos);
+
+        /**
+         * Find the start position of a line.
+         *
+         * @param line number of line (first is 1)
+         * @return position of first character in line
+         * @throws ArrayIndexOutOfBoundsException if {@code lineNumber < 1}
+         *                                        if {@code lineNumber > no. of lines}
+         */
+        int getStartPosition(int line);
+
+        /**
+         * Find the position corresponding to a (line,column).
+         *
+         * @param line   number of line (first is 1)
+         * @param column number of character on line (first is 1)
+         * @return position of character
+         * @throws ArrayIndexOutOfBoundsException if {@code line < 1}
+         *                                        if {@code line > no. of lines}
+         */
+        int getPosition(int line, int column);
+
+        /**
+         * Find the line containing a position; a line termination
+         * character is on the line it terminates.
+         *
+         * @param pos character offset of the position
+         * @return the line number on which pos occurs (first line is 1)
+         */
+        int getLineNumber(int pos);
+
+        /**
+         * Find the column for a character position.
+         * Note:  this method does not handle tab expansion.
+         * If tab expansion is needed, use a LineTabMap instead.
+         *
+         * @param pos character offset of the position
+         * @return the column number at which pos occurs
+         */
+        int getColumnNumber(int pos);
+    }
+
+    static class LineMapImpl implements LineMap {
+        int[] startPosition; // start position of each line
+
+        LineMapImpl() {
+        }
+
+        void build(char[] src, int max) {
+            int c = 0;
+            int i = 0;
+            int[] linebuf = new int[max];
+            while (i < max) {
+                linebuf[c++] = i;
+                do {
+                    char ch = src[i];
+                    if (ch == '\r' || ch == '\n') {
+                        if (ch == '\r' && (i + 1) < max && src[i + 1] == '\n')
+                            i += 2;
+                        else
+                            ++i;
+                        break;
+                    } else if (ch == '\t')
+                        setTabPosition(i);
+                } while (++i < max);
+            }
+            this.startPosition = new int[c];
+            System.arraycopy(linebuf, 0, startPosition, 0, c);
+        }
+
+        @Override
+        public int getStartPosition(int line) {
+            return startPosition[line - FIRSTLINE];
+        }
+
+        @Override
+        public long getStartPosition(long line) {
+            return getStartPosition(longToInt(line));
+        }
+
+        @Override
+        public int getPosition(int line, int column) {
+            return startPosition[line - FIRSTLINE] + column - FIRSTCOLUMN;
+        }
+
+        @Override
+        public long getPosition(long line, long column) {
+            return getPosition(longToInt(line), longToInt(column));
+        }
+
+        // Cache of last line number lookup
+        private int lastPosition = Position.FIRSTPOS;
+        private int lastLine = Position.FIRSTLINE;
+
+        @Override
+        public int getLineNumber(int pos) {
+            if (pos == lastPosition) {
+                return lastLine;
+            }
+            lastPosition = pos;
+
+            int low = 0;
+            int high = startPosition.length - 1;
+            while (low <= high) {
+                int mid = (low + high) >> 1;
+                int midVal = startPosition[mid];
+
+                if (midVal < pos)
+                    low = mid + 1;
+                else if (midVal > pos)
+                    high = mid - 1;
+                else {
+                    lastLine = mid + 1; // pos is at beginning of this line
+                    return lastLine;
+                }
+            }
+            lastLine = low;
+            return lastLine;  // pos is on this line
+        }
+
+        @Override
+        public long getLineNumber(long pos) {
+            return getLineNumber(longToInt(pos));
+        }
+
+        @Override
+        public int getColumnNumber(int pos) {
+            return pos - startPosition[getLineNumber(pos) - FIRSTLINE] + FIRSTCOLUMN;
+        }
+
+        @Override
+        public long getColumnNumber(long pos) {
+            return getColumnNumber(longToInt(pos));
+        }
+
+        static int longToInt(long longValue) {
+            int intValue = (int) longValue;
+            if (intValue != longValue)
+                throw new IndexOutOfBoundsException();
+            return intValue;
+        }
+
+        void setTabPosition(int offset) {
+        }
+    }
+
+    /**
+     * A LineMap that handles tab expansion correctly.  The cost is
+     * an additional bit per character in the source array.
+     */
+    static class LineTabMapImpl extends LineMapImpl {
+        private final BitSet tabMap;       // bits set for tab positions.
+
+        LineTabMapImpl(int max) {
+            super();
+            tabMap = new BitSet(max);
+        }
+
+        @Override
+        void setTabPosition(int offset) {
+            tabMap.set(offset);
+        }
+
+        @Override
+        public int getColumnNumber(int pos) {
+            int lineStart = startPosition[getLineNumber(pos) - FIRSTLINE];
+            int column = 0;
+            for (int bp = lineStart; bp < pos; bp++) {
+                if (tabMap.get(bp))
+                    column = tabulate(column);
+                else
+                    column++;
+            }
+            return column + FIRSTCOLUMN;
+        }
+
+        @Override
+        public int getPosition(int line, int column) {
+            int pos = startPosition[line - FIRSTLINE];
+            column -= FIRSTCOLUMN;
+            int col = 0;
+            while (col < column) {
+                pos++;
+                if (tabMap.get(pos))
+                    col = tabulate(col);
+                else
+                    col++;
+            }
+            return pos;
+        }
+    }
+
+    /**
+     * Tabulator column increment.
+     */
+    static final int TabInc = 8;
+
+    /**
+     * Bump column to the next tab.
+     */
+    static int tabulate(int column) {
+        return (column / TabInc * TabInc) + TabInc;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Scanner.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Scanner.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Scanner.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.lang.reflect.code.parser.impl.Position.LineMap;
+
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.reflect.code.parser.impl.Tokens.DUMMY;
+import static java.lang.reflect.code.parser.impl.Tokens.Token;
+
+/**
+ * The lexical analyzer maps an input stream consisting of
+ * ASCII characters and Unicode escapes into a token sequence.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public final class Scanner implements Lexer {
+
+    private final Tokens tokens;
+
+    /**
+     * The token, set by nextToken().
+     */
+    private Token token;
+
+    /**
+     * The previous token, set by nextToken().
+     */
+    private Token prevToken;
+
+    /**
+     * Buffer of saved tokens (used during lookahead)
+     */
+    private final List<Token> savedTokens = new ArrayList<>();
+
+    private final JavaBasedTokenizer tokenizer;
+
+    Scanner(Factory fac, char[] buf, int inputLength) {
+        this(fac, new JavaBasedTokenizer(fac, buf, inputLength));
+    }
+
+    Scanner(Factory fac, JavaBasedTokenizer tokenizer) {
+        this.tokenizer = tokenizer;
+        tokens = fac.tokens;
+        token = prevToken = DUMMY;
+    }
+
+    public Token token() {
+        return token(0);
+    }
+
+    public Token token(int lookahead) {
+        if (lookahead == 0) {
+            return token;
+        } else {
+            ensureLookahead(lookahead);
+            return savedTokens.get(lookahead - 1);
+        }
+    }
+
+    //where
+    private void ensureLookahead(int lookahead) {
+        for (int i = savedTokens.size(); i < lookahead; i++) {
+            savedTokens.add(tokenizer.readToken());
+        }
+    }
+
+    public Token prevToken() {
+        return prevToken;
+    }
+
+    public void nextToken() {
+        prevToken = token;
+        if (!savedTokens.isEmpty()) {
+            token = savedTokens.remove(0);
+        } else {
+            token = tokenizer.readToken();
+        }
+    }
+
+    public LineMap getLineMap() {
+        return tokenizer.getLineMap();
+    }
+
+    public int errPos() {
+        return tokenizer.errPos();
+    }
+
+    public void errPos(int pos) {
+        tokenizer.errPos(pos);
+    }
+
+    public static final class Factory {
+        final Log log;
+        final Tokens tokens;
+
+        /**
+         * Create a new scanner factory.
+         */
+        Factory() {
+            this.log = new Log();
+            this.tokens = new Tokens();
+        }
+
+        public Scanner newScanner(CharSequence input) {
+            if (input instanceof CharBuffer charBuffer) {
+                return newScanner(toArray(charBuffer), charBuffer.limit());
+            } else {
+                char[] array = input.toString().toCharArray();
+                return newScanner(array, array.length);
+            }
+        }
+
+        public Scanner newScanner(char[] input, int inputLength) {
+            return new Scanner(this, input, inputLength);
+        }
+
+        static char[] toArray(CharBuffer buffer) {
+            if (buffer.hasArray())
+                return buffer.compact().flip().array();
+            else
+                return buffer.toString().toCharArray();
+        }
+
+        static final Factory INSTANCE = new Factory();
+    }
+
+    public static Factory factory() {
+        return Factory.INSTANCE;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Tokens.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Tokens.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/Tokens.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * A class that defines codes/utilities for Java source tokens
+ * returned from lexical analysis.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public final class Tokens {
+
+    /**
+     * Keyword array. Maps name indices to Token.
+     */
+    private final Map<String, TokenKind> keywords = new HashMap<>();
+
+    Tokens() {
+        for (TokenKind t : TokenKind.values()) {
+            if (t.name != null) {
+                keywords.put(t.name, t);
+            }
+        }
+    }
+
+    TokenKind lookupKind(String name, TokenKind identifier) {
+        TokenKind t = keywords.get(name);
+        return (t != null) ? t : identifier;
+    }
+
+    /**
+     * This enum defines all tokens used by the javac scanner. A token is
+     * optionally associated with a name.
+     */
+    public enum TokenKind implements Predicate<TokenKind> {
+        EOF(),
+        ERROR(),
+        IDENTIFIER(Token.Tag.NAMED),
+        VALUE_IDENTIFIER(Token.Tag.NAMED),
+        EXTENDS("extends", Token.Tag.NAMED),
+        SUPER("super", Token.Tag.NAMED),
+        INTLITERAL(Token.Tag.NUMERIC),
+        LONGLITERAL(Token.Tag.NUMERIC),
+        FLOATLITERAL(Token.Tag.NUMERIC),
+        DOUBLELITERAL(Token.Tag.NUMERIC),
+        CHARLITERAL(Token.Tag.NUMERIC),
+        STRINGLITERAL(Token.Tag.STRING),
+        TRUE("true", Token.Tag.NAMED),
+        FALSE("false", Token.Tag.NAMED),
+        NULL("null", Token.Tag.NAMED),
+        UNDERSCORE("_", Token.Tag.NAMED),
+        ARROW("->"),
+        LPAREN("("),
+        RPAREN(")"),
+        LBRACE("{"),
+        RBRACE("}"),
+        LBRACKET("["),
+        RBRACKET("]"),
+        COMMA(","),
+        DOT("."),
+        EQ("="),
+        GT(">"),
+        LT("<"),
+        QUES("?"),
+        COLON(":"),
+        COLCOL("::"),
+        SEMI(";"),
+        PLUS("+"),
+        SUB("-"),
+        AMP("&"),
+        CARET("^"),
+        MONKEYS_AT("@"),
+        HASH("#"),
+        CUSTOM;
+
+        public final String name;
+        public final Token.Tag tag;
+
+        TokenKind() {
+            this(null, Token.Tag.DEFAULT);
+        }
+
+        TokenKind(String name) {
+            this(name, Token.Tag.DEFAULT);
+        }
+
+        TokenKind(Token.Tag tag) {
+            this(null, tag);
+        }
+
+        TokenKind(String name, Token.Tag tag) {
+            this.name = name;
+            this.tag = tag;
+        }
+
+        public String toString() {
+            return switch (this) {
+                case IDENTIFIER -> "token.identifier";
+                case VALUE_IDENTIFIER -> "token.value-identifier";
+                case CHARLITERAL -> "token.character";
+                case STRINGLITERAL -> "token.string";
+                case INTLITERAL -> "token.integer";
+                case LONGLITERAL -> "token.long-integer";
+                case FLOATLITERAL -> "token.float";
+                case DOUBLELITERAL -> "token.double";
+                case ERROR -> "token.bad-symbol";
+                case EOF -> "token.end-of-input";
+                case DOT, COMMA, LPAREN, RPAREN, LBRACKET, RBRACKET, LBRACE, RBRACE -> "'" + name + "'";
+                default -> name;
+            };
+        }
+
+        @Override
+        public boolean test(TokenKind that) {
+            return this == that;
+        }
+    }
+
+    public interface Comment {
+
+        enum CommentStyle {
+            LINE,
+            BLOCK,
+        }
+
+        String text();
+
+        CommentStyle style();
+    }
+
+    /**
+     * This is the class representing a javac token. Each token has several fields
+     * that are set by the javac lexer (i.e. start/end position, string value, etc).
+     */
+    public static class Token {
+
+        /**
+         * tags constants
+         **/
+        public enum Tag {
+            DEFAULT,
+            NAMED,
+            STRING,
+            NUMERIC
+        }
+
+        /**
+         * The token kind
+         */
+        public final TokenKind kind;
+
+        /**
+         * The start position of this token
+         */
+        public final int pos;
+
+        /**
+         * The end position of this token
+         */
+        public final int endPos;
+
+        /**
+         * Comment reader associated with this token
+         */
+        public final List<Comment> comments;
+
+        Token(TokenKind kind, int pos, int endPos, List<Comment> comments) {
+            this.kind = kind;
+            this.pos = pos;
+            this.endPos = endPos;
+            this.comments = comments == null ? null : List.copyOf(comments);
+            checkKind();
+        }
+
+        void checkKind() {
+            if (kind.tag != Tag.DEFAULT) {
+                throw new AssertionError("Bad token kind - expected " + Tag.DEFAULT);
+            }
+        }
+
+        public String name() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String stringVal() {
+            throw new UnsupportedOperationException();
+        }
+
+        public int radix() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    static final class NamedToken extends Token {
+        /**
+         * The name of this token
+         */
+        public final String name;
+
+        NamedToken(TokenKind kind, int pos, int endPos, String name, List<Comment> comments) {
+            super(kind, pos, endPos, comments);
+            this.name = name;
+        }
+
+        void checkKind() {
+            if (kind.tag != Tag.NAMED) {
+                throw new AssertionError("Bad token kind - expected " + Tag.NAMED);
+            }
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+    }
+
+    static class StringToken extends Token {
+        /**
+         * The string value of this token
+         */
+        public final String stringVal;
+
+        StringToken(TokenKind kind, int pos, int endPos, String stringVal, List<Comment> comments) {
+            super(kind, pos, endPos, comments);
+            this.stringVal = stringVal;
+        }
+
+        void checkKind() {
+            if (kind.tag != Tag.STRING) {
+                throw new AssertionError("Bad token kind - expected " + Tag.STRING);
+            }
+        }
+
+        @Override
+        public String stringVal() {
+            return stringVal;
+        }
+    }
+
+    static final class NumericToken extends StringToken {
+        /**
+         * The 'radix' value of this token
+         */
+        public final int radix;
+
+        NumericToken(TokenKind kind, int pos, int endPos, String stringVal, int radix, List<Comment> comments) {
+            super(kind, pos, endPos, stringVal, comments);
+            this.radix = radix;
+        }
+
+        void checkKind() {
+            if (kind.tag != Tag.NUMERIC) {
+                throw new AssertionError("Bad token kind - expected " + Tag.NUMERIC);
+            }
+        }
+
+        @Override
+        public int radix() {
+            return radix;
+        }
+    }
+
+    public static final Token DUMMY =
+            new Token(TokenKind.ERROR, 0, 0, null);
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/impl/UnicodeReader.java b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/UnicodeReader.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/impl/UnicodeReader.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.parser.impl;
+
+import java.util.Arrays;
+
+/**
+ * The unicode character reader used by the javac/javadoc lexer/tokenizer, returns characters
+ * one by one as contained in the input stream, handling unicode escape sequences accordingly.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b></p>
+ */
+sealed class UnicodeReader permits JavaBasedTokenizer {
+    /**
+     * End of input character.  Used as a sentinel to denote the
+     * character one beyond the last defined character in a
+     * source file.
+     */
+    static final byte EOI = 0x1A;
+
+    /**
+     * Buffer containing characters from source file. May contain extraneous characters
+     * beyond this.length.
+     */
+    private final char[] buffer;
+
+    /**
+     * Length of meaningful content in buffer.
+     */
+    private final int length;
+
+    /**
+     * Character buffer index of character currently being observed.
+     */
+    private int position;
+
+    /**
+     * Number of characters combined to provide character currently being observed. Typically
+     * one, but may be more when combinations of surrogate pairs and unicode escape sequences
+     * are read.
+     */
+    private int width;
+
+    /**
+     * Character currently being observed. If a surrogate pair is read then will be the high
+     * member of the pair.
+     */
+    private char character;
+
+    /**
+     * Codepoint of character currently being observed. Typically equivalent to the character
+     * but will have a value greater that 0xFFFF when a surrogate pair.
+     */
+    private int codepoint;
+
+    /**
+     * true if the last character was a backslash. This is used to handle the special case
+     * when a backslash precedes an unicode escape. In that case, the second backslash
+     * is treated as a backslash and not part of an unicode escape.
+     */
+    private boolean wasBackslash;
+
+    /**
+     * true if the last character was derived from an unicode escape sequence.
+     */
+    private boolean wasUnicodeEscape;
+
+    /**
+     * Log for error reporting.
+     */
+    private final Log log;
+
+    /**
+     * Constructor.
+     *
+     * @param sf     scan factory.
+     * @param array  array containing contents of source.
+     * @param length length of meaningful content in buffer.
+     */
+    UnicodeReader(Scanner.Factory sf, char[] array, int length) {
+        this.buffer = array;
+        this.length = length;
+        this.position = 0;
+        this.width = 0;
+        this.character = '\0';
+        this.codepoint = 0;
+        this.wasBackslash = false;
+        this.wasUnicodeEscape = false;
+        this.log = sf.log;
+
+        nextCodePoint();
+    }
+
+    /**
+     * Returns the length of the buffer. This is length of meaningful content in buffer and
+     * not the length of the buffer array.
+     *
+     * @return length of the buffer.
+     */
+    protected int length() {
+        return length;
+    }
+
+    /**
+     * Return true if current position is within the meaningful part of the buffer.
+     *
+     * @return true if current position is within the meaningful part of the buffer.
+     */
+    protected boolean isAvailable() {
+        return position < length;
+    }
+
+    /**
+     * Fetches the next 16-bit character from the buffer and places it in this.character.
+     */
+    private void nextCodeUnit() {
+        // Index of next character in buffer.
+        int index = position + width;
+
+        // If past end of buffer.
+        if (length <= index) {
+            // End of file is marked with EOI.
+            character = EOI;
+        } else {
+            // Next character in buffer.
+            character = buffer[index];
+            // Increment length of codepoint.
+            width++;
+        }
+    }
+
+    /**
+     * Fetches the next 16-bit character from the buffer. If an unicode escape
+     * is detected then converts the unicode escape to a character.
+     */
+    private void nextUnicodeInputCharacter() {
+        // Position to next codepoint.
+        position += width;
+        // Codepoint has no characters yet.
+        width = 0;
+
+        // Fetch next character.
+        nextCodeUnit();
+
+        if (character == '\\' && (!wasBackslash || wasUnicodeEscape)) {
+            // Is a backslash and may be an unicode escape.
+            switch (unicodeEscape()) {
+                case BACKSLASH -> {
+                    wasUnicodeEscape = false;
+                    wasBackslash = !wasBackslash;
+                }
+                case VALID_ESCAPE -> {
+                    wasUnicodeEscape = true;
+                    wasBackslash = character == '\\' && !wasBackslash;
+                }
+                case BROKEN_ESCAPE -> nextUnicodeInputCharacter(); //skip broken unicode escapes
+            }
+        } else {
+            wasBackslash = false;
+            wasUnicodeEscape = false;
+        }
+
+        // Codepoint and character match if not surrogate.
+        codepoint = (int) character;
+    }
+
+    /**
+     * Fetches the nextcode point from the buffer. If an unicode escape is recognized
+     * then converts unicode escape to a character. If two characters are a surrogate pair
+     * then converts to a codepoint.
+     */
+    private void nextCodePoint() {
+        // Next unicode character.
+        nextUnicodeInputCharacter();
+
+        // Return early if ASCII or not a surrogate pair.
+        if (isASCII() || !Character.isHighSurrogate(character)) {
+            return;
+        }
+
+        // Capture high surrogate and position.
+        char hi = character;
+        int savePosition = position;
+        int saveWidth = width;
+
+        // Get potential low surrogate.
+        nextUnicodeInputCharacter();
+        char lo = character;
+
+        if (Character.isLowSurrogate(lo)) {
+            // Start codepoint at start of high surrogate.
+            position = savePosition;
+            width += saveWidth;
+            // Compute codepoint.
+            codepoint = Character.toCodePoint(hi, lo);
+        } else {
+            // Restore to treat high surrogate as just a character.
+            position = savePosition;
+            width = saveWidth;
+            character = hi;
+            codepoint = (int) hi;
+            // Could potential report an error here (old code did not.)
+        }
+    }
+
+    /**
+     * Converts an unicode escape into a character.
+     *
+     * @return true if was an unicode escape.
+     */
+    private UnicodeEscapeResult unicodeEscape() {
+        // Start of unicode escape (past backslash.)
+        int start = position + width;
+
+        // Default to backslash result, unless proven otherwise.
+        character = '\\';
+        width = 1;
+
+        // Skip multiple 'u'.
+        int index;
+        for (index = start; index < length; index++) {
+            if (buffer[index] != 'u') {
+                break;
+            }
+        }
+
+        // Needs to have been at least one u.
+        if (index == start) {
+            return UnicodeEscapeResult.BACKSLASH;
+        }
+
+        int code = 0;
+
+        for (int i = 0; i < 4; i++) {
+            // Translate and merge digit.
+            int digit = index < length ? Character.digit(buffer[index], 16) : -1;
+            code = code << 4 | digit;
+
+            // If invalid digit.
+            if (code < 0) {
+                break;
+            }
+
+            // On to next character.
+            index++;
+        }
+
+        // Skip digits even if error.
+        width = index - position;
+
+        // If all digits are good.
+        if (code >= 0) {
+            character = (char) code;
+            return UnicodeEscapeResult.VALID_ESCAPE;
+        } else {
+            log.error(index, Errors.IllegalUnicodeEsc);
+            return UnicodeEscapeResult.BROKEN_ESCAPE;
+        }
+    }
+
+    private enum UnicodeEscapeResult {
+        BACKSLASH,
+        VALID_ESCAPE,
+        BROKEN_ESCAPE
+    }
+
+    /**
+     * Return the current position in the character buffer.
+     *
+     * @return current position in the character buffer.
+     */
+    protected int position() {
+        return position;
+    }
+
+
+    /**
+     * Reset the reader to the specified position.
+     * Warning: Do not use when previous character was an ASCII or unicode backslash.
+     *
+     * @param pos
+     */
+    protected void reset(int pos) {
+        position = pos;
+        width = 0;
+        wasBackslash = false;
+        wasUnicodeEscape = false;
+        nextCodePoint();
+    }
+
+    /**
+     * Return the current character in at the current position.
+     *
+     * @return current character in at the current position.
+     */
+    protected char get() {
+        return character;
+    }
+
+    /**
+     * Return the current codepoint in at the current position.
+     *
+     * @return current codepoint in at the current position.
+     */
+    protected int getCodepoint() {
+        return codepoint;
+    }
+
+    /**
+     * Returns true if the current codepoint is a surrogate.
+     *
+     * @return true if the current codepoint is a surrogate.
+     */
+    protected boolean isSurrogate() {
+        return 0xFFFF < codepoint;
+    }
+
+    /**
+     * Returns true if the current character is ASCII.
+     *
+     * @return true if the current character is ASCII.
+     */
+    protected boolean isASCII() {
+        return character <= 0x7F;
+    }
+
+    /**
+     * Advances the current character to the next character.
+     *
+     * @return next character.
+     */
+    protected char next() {
+        nextCodePoint();
+
+        return character;
+    }
+
+    /**
+     * Compare character. Returns true if a match.
+     *
+     * @param ch character to match.
+     * @return true if a match.
+     */
+    protected boolean is(char ch) {
+        return character == ch;
+    }
+
+    /**
+     * Match one of the arguments. Returns true if a match.
+     */
+    protected boolean isOneOf(char ch1, char ch2) {
+        return is(ch1) || is(ch2);
+    }
+
+    protected boolean isOneOf(char ch1, char ch2, char ch3) {
+        return is(ch1) || is(ch2) || is(ch3);
+    }
+
+    protected boolean isOneOf(char ch1, char ch2, char ch3, char ch4, char ch5, char ch6) {
+        return is(ch1) || is(ch2) || is(ch3) || is(ch4) || is(ch5) || is(ch6);
+    }
+
+    /**
+     * Tests to see if current character is in the range of lo to hi characters (inclusive).
+     *
+     * @param lo lowest character in range.
+     * @param hi highest character in range.
+     * @return true if the current character is in range.
+     */
+    protected boolean inRange(char lo, char hi) {
+        return lo <= character && character <= hi;
+    }
+
+    /**
+     * Compare character and advance if a match. Returns true if a match.
+     *
+     * @param ch character to match.
+     * @return true if a match.
+     */
+    protected boolean accept(char ch) {
+        if (is(ch)) {
+            next();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Match one of the arguments and advance if a match. Returns true if a match.
+     */
+    protected boolean acceptOneOf(char ch1, char ch2) {
+        if (isOneOf(ch1, ch2)) {
+            next();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    protected boolean acceptOneOf(char ch1, char ch2, char ch3) {
+        if (isOneOf(ch1, ch2, ch3)) {
+            next();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Skip over all occurrences of character.
+     *
+     * @param ch character to accept.
+     */
+    protected void skip(char ch) {
+        while (accept(ch)) {
+            // next
+        }
+    }
+
+    /**
+     * Skip over ASCII white space characters.
+     */
+    protected void skipWhitespace() {
+        while (acceptOneOf(' ', '\t', '\f')) {
+            // next
+        }
+    }
+
+    /**
+     * Skip to end of line.
+     */
+    protected void skipToEOLN() {
+        while (isAvailable()) {
+            if (isOneOf('\r', '\n')) {
+                break;
+            }
+
+            next();
+        }
+
+    }
+
+    /**
+     * Compare string and advance if a match. Returns true if a match.
+     * Warning: Do not use when previous character was a backslash
+     * (confuses state of wasBackslash.)
+     *
+     * @param string string to match character for character.
+     * @return true if a match.
+     */
+    protected boolean accept(String string) {
+        // Quick test.
+        if (string.length() == 0 || !is(string.charAt(0))) {
+            return false;
+        }
+
+        // Be prepared to retreat if not a match.
+        int savedPosition = position;
+
+        nextCodePoint();
+
+        // Check each character.
+        for (int i = 1; i < string.length(); i++) {
+            if (!is(string.charAt(i))) {
+                // Restart if not a match.
+                reset(savedPosition);
+
+                return false;
+            }
+
+            nextCodePoint();
+        }
+
+        return true;
+    }
+
+    /**
+     * Convert an ASCII digit from its base (8, 10, or 16) to its value. Does not
+     * advance character.
+     *
+     * @param pos        starting position.
+     * @param digitRadix base of number being converted.
+     * @return value of digit.
+     */
+    protected int digit(int pos, int digitRadix) {
+        int result;
+
+        // Just an ASCII digit.
+        if (inRange('0', '9')) {
+            // Fast common case.
+            result = character - '0';
+
+            return result < digitRadix ? result : -1;
+        }
+
+        // Handle other digits.
+        result = isSurrogate() ? Character.digit(codepoint, digitRadix) :
+                Character.digit(character, digitRadix);
+
+        if (result >= 0 && !isASCII()) {
+            log.error(position(), Errors.IllegalNonasciiDigit);
+            character = "0123456789abcdef".charAt(result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the input buffer. Unicode escape sequences are not translated.
+     *
+     * @return the input buffer.
+     */
+    public char[] getRawCharacters() {
+        return length == buffer.length ? buffer : Arrays.copyOf(buffer, length);
+    }
+
+    /**
+     * Returns a copy of a character array subset of the input buffer.
+     * The returned array begins at the {@code beginIndex} and
+     * extends to the character at index {@code endIndex - 1}.
+     * Thus the length of the substring is {@code endIndex-beginIndex}.
+     * This behavior is like
+     * {@code String.substring(beginIndex, endIndex)}.
+     * Unicode escape sequences are not translated.
+     *
+     * @param beginIndex the beginning index, inclusive.
+     * @param endIndex   the ending index, exclusive.
+     * @throws ArrayIndexOutOfBoundsException if either offset is outside of the
+     *                                        array bounds
+     */
+    public char[] getRawCharacters(int beginIndex, int endIndex) {
+        return Arrays.copyOfRange(buffer, beginIndex, endIndex);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/parser/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/parser/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/parser/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for parsing code models.
+ */
+package java.lang.reflect.code.parser;
diff a/src/java.base/share/classes/java/lang/reflect/code/type/ArrayType.java b/src/java.base/share/classes/java/lang/reflect/code/type/ArrayType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/ArrayType.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+
+/**
+ * An array type.
+ */
+public final class ArrayType implements JavaType {
+
+    final JavaType componentType;
+
+    ArrayType(JavaType componentType) {
+        this.componentType = componentType;
+    }
+
+    @Override
+    public Type resolve(Lookup lookup) throws ReflectiveOperationException {
+        Type resolvedComponent = componentType.resolve(lookup);
+        if (resolvedComponent instanceof Class<?> resolvedComponentClass) {
+            return resolvedComponentClass.arrayType();
+        } else { // generic array
+            return makeReflectiveGenericArray(resolvedComponent);
+        }
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    private static GenericArrayType makeReflectiveGenericArray(Type component) {
+/*__throw new UnsupportedOperationException();__*/        return sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl.make(component);
+    }
+
+    /**
+     * {@return the array type's component type}
+     */
+    public JavaType componentType() {
+        return componentType;
+    }
+
+    /**
+     * {@return the dimensions associated with this array type}
+     */
+    public int dimensions() {
+        int dims = 0;
+        JavaType current = this;
+        while (current instanceof ArrayType at) {
+            dims++;
+            current = at.componentType();
+        }
+        return dims;
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        int dims = 0;
+        TypeElement current = this;
+        while (current instanceof ArrayType at) {
+            dims++;
+            current = at.componentType();
+        }
+        return new ExternalizedTypeElement("[".repeat(dims), List.of(current.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof ArrayType that &&
+                componentType.equals(that.componentType);
+    }
+
+    @Override
+    public int hashCode() {
+        return 17 * componentType.hashCode();
+    }
+
+    @Override
+    public JavaType erasure() {
+        return JavaType.array(componentType.erasure());
+    }
+
+    @Override
+    public JavaType toBasicType() {
+        return JavaType.J_L_OBJECT;
+    }
+
+    @Override
+    public ClassDesc toNominalDescriptor() {
+        return componentType.toNominalDescriptor().arrayType();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/ClassType.java b/src/java.base/share/classes/java/lang/reflect/code/type/ClassType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/ClassType.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.code.TypeElement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A class type.
+ */
+public final class ClassType implements TypeVarRef.Owner, JavaType {
+    // Fully qualified name
+    private final ClassDesc type;
+
+    private final List<JavaType> typeArguments;
+
+    ClassType(ClassDesc type) {
+        this(type, List.of());
+    }
+
+    ClassType(ClassDesc type, List<JavaType> typeArguments) {
+        if (!type.isClassOrInterface()) {
+            throw new IllegalArgumentException("Invalid base type: " + type);
+        }
+        this.type = type;
+        this.typeArguments = List.copyOf(typeArguments);
+    }
+
+    @Override
+    public Type resolve(Lookup lookup) throws ReflectiveOperationException {
+        Class<?> baseType = type.resolveConstantDesc(lookup);
+        List<Type> resolvedTypeArgs = new ArrayList<>();
+        for (JavaType typearg : typeArguments) {
+            resolvedTypeArgs.add(typearg.resolve(lookup));
+        }
+        return resolvedTypeArgs.isEmpty() ?
+                baseType :
+                makeReflectiveParameterizedType(baseType,
+                        resolvedTypeArgs.toArray(new Type[0]), baseType.getDeclaringClass()); // @@@: generic owner is erased here
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    private static ParameterizedType makeReflectiveParameterizedType(Class<?> base, Type[] typeArgs, Class<?> owner) {
+/*__throw new UnsupportedOperationException();__*/        return sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(base, typeArgs, owner);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        List<ExternalizedTypeElement> args = typeArguments.stream()
+                .map(TypeElement::externalize)
+                .toList();
+
+        ExternalizedTypeElement td = new ExternalizedTypeElement(toClassName(), args);
+        return td;
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ClassType typeDesc = (ClassType) o;
+
+        if (!type.equals(typeDesc.type)) return false;
+        return typeArguments.equals(typeDesc.typeArguments);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type.hashCode();
+        result = 31 * result + typeArguments.hashCode();
+        return result;
+    }
+
+    /**
+     * {@return the unboxed primitive type associated with this class type (if any)}
+     */
+    public Optional<PrimitiveType> unbox() {
+        class LazyHolder {
+            static final Map<ClassType, PrimitiveType> wrapperToPrimitive = Map.of(
+                    J_L_BYTE, BYTE,
+                    J_L_SHORT, SHORT,
+                    J_L_INTEGER, INT,
+                    J_L_LONG, LONG,
+                    J_L_FLOAT, FLOAT,
+                    J_L_DOUBLE, DOUBLE,
+                    J_L_CHARACTER, CHAR,
+                    J_L_BOOLEAN, BOOLEAN
+            );
+        }
+        return Optional.ofNullable(LazyHolder.wrapperToPrimitive.get(this));
+    }
+
+    @Override
+    public JavaType erasure() {
+        return rawType();
+    }
+
+    // Conversions
+
+    /**
+     * {@return a class type whose base type is the same as this class type, but without any
+     * type arguments}
+     */
+    public ClassType rawType() {
+        return new ClassType(type);
+    }
+
+    /**
+     * {@return {@code true} if this class type has a non-empty type argument list}
+     * @see ClassType#typeArguments()
+     */
+    public boolean hasTypeArguments() {
+        return !typeArguments.isEmpty();
+    }
+
+    /**
+     * {@return the type argument list associated with this class type}
+     */
+    public List<JavaType> typeArguments() {
+        return typeArguments;
+    }
+
+    @Override
+    public JavaType toBasicType() {
+        return JavaType.J_L_OBJECT;
+    }
+
+    /**
+     * {@return a human-readable name for this class type}
+     */
+    public String toClassName() {
+        String pkg = type.packageName();
+        return pkg.isEmpty() ?
+                type.displayName() :
+                String.format("%s.%s", pkg, type.displayName());
+    }
+
+    @Override
+    public ClassDesc toNominalDescriptor() {
+        return type;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/CoreTypeFactory.java b/src/java.base/share/classes/java/lang/reflect/code/type/CoreTypeFactory.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/CoreTypeFactory.java
@@ -0,0 +1,178 @@
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.WildcardType.BoundKind;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class CoreTypeFactory {
+
+    private CoreTypeFactory() {
+    }
+
+    // Code model type factory composed
+
+    /**
+     * Create a code model factory combining and composing the construction
+     * of code model types with types constructed from the given type factory.
+     *
+     * @param f the type factory.
+     * @return the code model factory.
+     */
+    public static TypeElementFactory codeModelTypeFactory(TypeElementFactory f) {
+        class CodeModelFactory implements TypeElementFactory {
+            final TypeElementFactory thisThenF = this.andThen(f);
+
+            @Override
+            public TypeElement constructType(TypeElement.ExternalizedTypeElement tree) {
+                return switch (tree.identifier()) {
+                    case VarType.NAME -> {
+                        if (tree.arguments().size() != 1) {
+                            throw new IllegalArgumentException();
+                        }
+
+                        TypeElement v = thisThenF.constructType(tree.arguments().getFirst());
+                        if (v == null) {
+                            throw new IllegalArgumentException("Bad type: " + tree);
+                        }
+                        yield VarType.varType(v);
+                    }
+                    case TupleType.NAME -> {
+                        if (tree.arguments().isEmpty()) {
+                            throw new IllegalArgumentException("Bad type: " + tree);
+                        }
+
+                        List<TypeElement> cs = new ArrayList<>(tree.arguments().size());
+                        for (TypeElement.ExternalizedTypeElement child : tree.arguments()) {
+                            TypeElement c = thisThenF.constructType(child);
+                            if (c == null) {
+                                throw new IllegalArgumentException("Bad type: " + tree);
+                            }
+                            cs.add(c);
+                        }
+                        yield TupleType.tupleType(cs);
+                    }
+                    case FunctionType.NAME -> {
+                        if (tree.arguments().isEmpty()) {
+                            throw new IllegalArgumentException("Bad type: " + tree);
+                        }
+
+                        TypeElement rt = thisThenF.constructType(tree.arguments().getFirst());
+                        if (rt == null) {
+                            throw new IllegalArgumentException("Bad type: " + tree);
+                        }
+                        List<TypeElement> pts = new ArrayList<>(tree.arguments().size() - 1);
+                        for (TypeElement.ExternalizedTypeElement child : tree.arguments().subList(1, tree.arguments().size())) {
+                            TypeElement c = thisThenF.constructType(child);
+                            if (c == null) {
+                                throw new IllegalArgumentException("Bad type: " + tree);
+                            }
+                            pts.add(c);
+                        }
+                        yield FunctionType.functionType(rt, pts);
+                    }
+                    default -> null;
+                };
+            }
+        }
+        if (f instanceof CodeModelFactory) {
+            throw new IllegalArgumentException();
+        }
+
+        return new CodeModelFactory().thisThenF;
+    }
+
+    // Java type factory
+
+    /**
+     * The Java type factory.
+     */
+    public static final TypeElementFactory JAVA_TYPE_FACTORY = new TypeElementFactory() {
+        @Override
+        public TypeElement constructType(TypeElement.ExternalizedTypeElement tree) {
+            String identifier = tree.identifier();
+            int dimensions = 0;
+            if (identifier.startsWith("[")) {
+                if (tree.arguments().size() != 1) {
+                    throw new IllegalArgumentException("Bad type: " + tree);
+                }
+                for (int i = 1; i < identifier.length(); i++) {
+                    if (identifier.charAt(i) != '[') {
+                        throw new IllegalArgumentException("Bad type: " + tree);
+                    }
+                }
+                dimensions = identifier.length();
+                tree = tree.arguments().getFirst();
+                identifier = tree.identifier();
+            }
+
+            List<JavaType> typeArguments = new ArrayList<>(tree.arguments().size());
+            for (TypeElement.ExternalizedTypeElement child : tree.arguments()) {
+                TypeElement t = JAVA_TYPE_FACTORY.constructType(child);
+                if (!(t instanceof JavaType a)) {
+                    throw new IllegalArgumentException("Bad type: " + tree);
+                }
+                typeArguments.add(a);
+            }
+            if (identifier.equals("+") || identifier.equals("-")) {
+                // wildcard type
+                BoundKind kind = identifier.equals("+") ?
+                        BoundKind.EXTENDS : BoundKind.SUPER;
+                return JavaType.wildcard(kind, typeArguments.get(0));
+            } else if (identifier.startsWith("#")) {
+                // type-var
+                if (typeArguments.size() != 1) {
+                    throw new IllegalArgumentException("Bad type-variable bounds: " + tree);
+                }
+                String[] parts = identifier.substring(1).split("::");
+                if (parts.length == 2) {
+                    // class type-var
+                    return JavaType.typeVarRef(parts[1],
+                            (ClassType)constructType(parseExTypeElem(parts[0])),
+                            typeArguments.get(0));
+                } else {
+                    // method type-var
+                    return JavaType.typeVarRef(parts[2],
+                            parseMethodRef(String.format("%s::%s", parts[0], parts[1])),
+                            typeArguments.get(0));
+                }
+            }
+            JavaType t = switch (identifier) {
+                case "boolean" -> JavaType.BOOLEAN;
+                case "byte" -> JavaType.BYTE;
+                case "char" -> JavaType.CHAR;
+                case "short" -> JavaType.SHORT;
+                case "int" -> JavaType.INT;
+                case "long" -> JavaType.LONG;
+                case "float" -> JavaType.FLOAT;
+                case "double" -> JavaType.DOUBLE;
+                case "void" -> JavaType.VOID;
+                default -> JavaType.type(ClassDesc.of(identifier));
+            };
+            if (!typeArguments.isEmpty()) {
+                t = JavaType.parameterized(t, typeArguments);
+            }
+            return dimensions == 0 ?
+                    t : JavaType.array(t, dimensions);
+        }
+    };
+
+
+    /**
+     * The core type factory that can construct instance of {@link JavaType}
+     * or code model types such as {@link VarType} or {@link TupleType} that
+     * may contain instances of those types.
+     */
+    public static final TypeElementFactory CORE_TYPE_FACTORY = codeModelTypeFactory(JAVA_TYPE_FACTORY);
+
+    // Copied code in jdk.compiler module throws UOE
+    static MethodRef parseMethodRef(String desc) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseMethodRef(desc);
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    static TypeElement.ExternalizedTypeElement parseExTypeElem(String desc) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseExTypeElem(desc);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/FieldRef.java b/src/java.base/share/classes/java/lang/reflect/code/type/FieldRef.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/FieldRef.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.type.impl.FieldRefImpl;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.code.TypeElement;
+
+/**
+ * The symbolic reference to a Java field.
+ */
+public sealed interface FieldRef permits FieldRefImpl {
+    TypeElement refType();
+
+    String name();
+
+    TypeElement type();
+
+    // Conversions
+
+    Field resolveToMember(MethodHandles.Lookup l) throws ReflectiveOperationException;
+
+    VarHandle resolveToHandle(MethodHandles.Lookup l) throws ReflectiveOperationException;
+
+    // Factories
+
+    static FieldRef field(Field f) {
+        return field(f.getDeclaringClass(), f.getName(), f.getType());
+    }
+
+    static FieldRef field(Class<?> refType, String name, Class<?> type) {
+        return field(JavaType.type(refType), name, JavaType.type(type));
+    }
+
+    static FieldRef field(TypeElement refType, String name, TypeElement type) {
+        return new FieldRefImpl(refType, name, type);
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    static FieldRef ofString(String s) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseFieldRef(s);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/FunctionType.java b/src/java.base/share/classes/java/lang/reflect/code/type/FunctionType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/FunctionType.java
@@ -0,0 +1,93 @@
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A function type.
+ */
+public final class FunctionType implements TypeElement {
+    // @@@ Change to "->" when the textual form supports it
+    static final String NAME = "func";
+
+    /**
+     * The function type with no parameters, returning void.
+     */
+    // @@@ Uses JavaType
+    public static final FunctionType VOID = functionType(JavaType.VOID);
+
+    final TypeElement returnType;
+    final List<TypeElement> parameterTypes;
+
+    FunctionType(TypeElement returnType, List<? extends TypeElement> parameterTypes) {
+        this.returnType = returnType;
+        this.parameterTypes = List.copyOf(parameterTypes);
+    }
+
+    /**
+     * {@return the function type's return type}
+     */
+    public TypeElement returnType() {
+        return returnType;
+    }
+
+    /**
+     * {@return the function type's parameter types}
+     */
+    public List<TypeElement> parameterTypes() {
+        return parameterTypes;
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME,
+                Stream.concat(Stream.of(returnType), parameterTypes.stream())
+                        .map(TypeElement::externalize).toList());
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof FunctionType that &&
+                returnType.equals(that.returnType) &&
+                parameterTypes.equals(that.parameterTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = returnType.hashCode();
+        result = 31 * result + parameterTypes.hashCode();
+        return result;
+    }
+
+    /**
+     * Constructs a function type.
+     *
+     * @param returnType the function type's return type.
+     * @param parameterTypes the function type's parameter types.
+     * @return a function type.
+     */
+    public static FunctionType functionType(TypeElement returnType, List<? extends TypeElement> parameterTypes) {
+        Objects.requireNonNull(returnType);
+        Objects.requireNonNull(parameterTypes);
+        return new FunctionType(returnType, parameterTypes);
+    }
+    /**
+     * Constructs a function type.
+     *
+     * @param returnType the function type's return type.
+     * @param parameterTypes the function type's parameter types.
+     * @return a function type.
+     */
+    public static FunctionType functionType(TypeElement returnType, TypeElement... parameterTypes) {
+        return functionType(returnType, List.of(parameterTypes));
+    }
+
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/JavaType.java b/src/java.base/share/classes/java/lang/reflect/code/type/JavaType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/JavaType.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Executable;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.type.WildcardType.BoundKind;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * The symbolic description of a Java type. Java types can be classified as follows:
+ * <ul>
+ *     <li>{@linkplain PrimitiveType primitive types}, e.g. {@code int}, {@code void}</li>
+ *     <li>{@linkplain ClassType class types}, e.g. {@code String}, {@code List<? extends Number>}</li>
+ *     <li>{@linkplain ArrayType array types}, e.g. {@code Object[][]}, {@code List<Runnable>[]}</li>
+ *     <li>{@linkplain WildcardType wildcard types}, e.g. {@code ? extends Number}, {@code ? super ArrayList<String>}</li>
+ *     <li>{@linkplain TypeVarRef type-variables}, e.g. {@code T extends Runnable}</li>
+ * </ul>
+ * Java types can be constructed from either {@linkplain ClassDesc nominal descriptors} or
+ * {@linkplain Type reflective type mirrors}. Conversely, Java types can be
+ * {@linkplain #toNominalDescriptor() turned} into nominal descriptors,
+ * or be {@linkplain #resolve(Lookup) resolved} into reflective type mirrors.
+ * @sealedGraph
+ */
+public sealed interface JavaType extends TypeElement permits ClassType, ArrayType,
+                                                             PrimitiveType, WildcardType, TypeVarRef {
+
+    /** {@link JavaType} representing {@code void} */
+    PrimitiveType VOID = new PrimitiveType(ConstantDescs.CD_void);
+
+    /** {@link JavaType} representing {@code boolean} */
+    PrimitiveType BOOLEAN = new PrimitiveType(ConstantDescs.CD_boolean);
+
+    /** {@link JavaType} representing {@link Boolean} */
+    ClassType J_L_BOOLEAN = new ClassType(ConstantDescs.CD_Boolean);
+
+    /** {@link JavaType} representing {@code boolean[]} */
+    ArrayType BOOLEAN_ARRAY = new ArrayType(BOOLEAN);
+
+    /** {@link JavaType} representing {@code byte} */
+    PrimitiveType BYTE = new PrimitiveType(ConstantDescs.CD_byte);
+
+    /** {@link JavaType} representing {@link Byte} */
+    ClassType J_L_BYTE = new ClassType(ConstantDescs.CD_Byte);
+
+    /** {@link JavaType} representing {@code byte[]} */
+    ArrayType BYTE_ARRAY = new ArrayType(BYTE);
+
+    /** {@link JavaType} representing {@code char} */
+    PrimitiveType CHAR = new PrimitiveType(ConstantDescs.CD_char);
+
+    /** {@link JavaType} representing {@link Character} */
+    ClassType J_L_CHARACTER = new ClassType(ConstantDescs.CD_Character);
+
+    /** {@link JavaType} representing {@code char[]} */
+    ArrayType CHAR_ARRAY = new ArrayType(CHAR);
+
+    /** {@link JavaType} representing {@code short} */
+    PrimitiveType SHORT = new PrimitiveType(ConstantDescs.CD_short);
+
+    /** {@link JavaType} representing {@link Short} */
+    ClassType J_L_SHORT = new ClassType(ConstantDescs.CD_Short);
+
+    /** {@link JavaType} representing {@code short[]} */
+    ArrayType SHORT_ARRAY = new ArrayType(SHORT);
+
+    /** {@link JavaType} representing {@code int} */
+    PrimitiveType INT = new PrimitiveType(ConstantDescs.CD_int);
+
+    /** {@link JavaType} representing {@link Integer} */
+    ClassType J_L_INTEGER = new ClassType(ConstantDescs.CD_Integer);
+
+    /** {@link JavaType} representing {@code int[]} */
+    ArrayType INT_ARRAY = new ArrayType(INT);
+
+    /** {@link JavaType} representing {@code long} */
+    PrimitiveType LONG = new PrimitiveType(ConstantDescs.CD_long);
+
+    /** {@link JavaType} representing {@link Long} */
+    ClassType J_L_LONG = new ClassType(ConstantDescs.CD_Long);
+
+    /** {@link JavaType} representing {@code long[]} */
+    ArrayType LONG_ARRAY = new ArrayType(LONG);
+
+    /** {@link JavaType} representing {@code float} */
+    PrimitiveType FLOAT = new PrimitiveType(ConstantDescs.CD_float);
+
+    /** {@link JavaType} representing {@link Float} */
+    ClassType J_L_FLOAT = new ClassType(ConstantDescs.CD_Float);
+
+    /** {@link JavaType} representing {@code float[]} */
+    ArrayType FLOAT_ARRAY = new ArrayType(FLOAT);
+
+    /** {@link JavaType} representing {@code double} */
+    PrimitiveType DOUBLE = new PrimitiveType(ConstantDescs.CD_double);
+
+    /** {@link JavaType} representing {@link Double} */
+    ClassType J_L_DOUBLE = new ClassType(ConstantDescs.CD_Double);
+
+    /** {@link JavaType} representing {@code double[]} */
+    ArrayType DOUBLE_ARRAY = new ArrayType(DOUBLE);
+
+    /** {@link JavaType} representing {@link Object} */
+    ClassType J_L_OBJECT = new ClassType(ConstantDescs.CD_Object);
+
+    /** {@link JavaType} representing {@link Object[]} */
+    ArrayType J_L_OBJECT_ARRAY = new ArrayType(J_L_OBJECT);
+
+    /** {@link JavaType} representing {@link Class} */
+    ClassType J_L_CLASS = new ClassType(ConstantDescs.CD_Class);
+
+    /** {@link JavaType} representing {@link String} */
+    ClassType J_L_STRING = new ClassType(ConstantDescs.CD_String);
+
+    /** {@link JavaType} representing {@link List} */
+    ClassType J_U_LIST = new ClassType(ConstantDescs.CD_List);
+
+    // Conversions
+
+    /**
+     * {@return the basic type associated with this Java type}. A basic type is one of the following
+     * types:
+     * <ul>
+     *     <li>{@link JavaType#VOID}</li>
+     *     <li>{@link JavaType#INT}</li>
+     *     <li>{@link JavaType#LONG}</li>
+     *     <li>{@link JavaType#FLOAT}</li>
+     *     <li>{@link JavaType#DOUBLE}</li>
+     *     <li>{@link JavaType#J_L_OBJECT}</li>
+     * </ul>
+     *
+     */
+    JavaType toBasicType();
+
+    /**
+     * {@return the nominal descriptor associated with this Java type}
+     */
+    ClassDesc toNominalDescriptor();
+
+    /**
+     * Resolve this Java type to a reflective type mirror.
+     * @param lookup the lookup used to create the reflective type mirror
+     * @return a reflective type mirror for this type
+     * @throws ReflectiveOperationException if this Java type cannot be resolved
+     */
+    Type resolve(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
+
+    /**
+     * {@return the erasure of this Java type, as per JLS 4.6}
+     */
+    JavaType erasure();
+
+    // Factories
+
+    /**
+     * Constructs a Java type from a reflective type mirror.
+     *
+     * @param reflectiveType the reflective type mirror
+     */
+    static JavaType type(Type reflectiveType) {
+        return switch (reflectiveType) {
+            case Class<?> c -> type(c.describeConstable().get());
+            case ParameterizedType pt -> parameterized(type(pt.getRawType()),
+                    Stream.of(pt.getActualTypeArguments()).map(JavaType::type).toList());
+            case java.lang.reflect.WildcardType wt -> wt.getLowerBounds().length == 0 ?
+                    wildcard(BoundKind.EXTENDS, type(wt.getUpperBounds()[0])) :
+                    wildcard(BoundKind.SUPER, type(wt.getLowerBounds()[0]));
+            case TypeVariable<?> tv -> typeVarRef(tv.getName(), owner(tv.getGenericDeclaration()), type(tv.getBounds()[0]));
+            case GenericArrayType at -> array(type(at.getGenericComponentType()));
+            default -> throw new InternalError();
+        };
+    }
+
+    private static TypeVarRef.Owner owner(GenericDeclaration genDecl) {
+        return switch (genDecl) {
+            case Executable e -> MethodRef.method(e);
+            case Class<?> t -> (ClassType)type(t);
+            default -> throw new InternalError();
+        };
+    }
+
+    /**
+     * Constructs a Java type from a nominal descriptor.
+     *
+     * @param desc the nominal descriptor
+     */
+    static JavaType type(ClassDesc desc) {
+        if (desc.isPrimitive()) {
+            return switch (desc.descriptorString().charAt(0)) {
+                case 'V' -> JavaType.VOID;
+                case 'I' -> JavaType.INT;
+                case 'J' -> JavaType.LONG;
+                case 'C' -> JavaType.CHAR;
+                case 'S' -> JavaType.SHORT;
+                case 'B' -> JavaType.BYTE;
+                case 'F' -> JavaType.FLOAT;
+                case 'D' -> JavaType.DOUBLE;
+                case 'Z' -> JavaType.BOOLEAN;
+                default -> throw new InternalError();
+            };
+        } else if (desc.isArray()) {
+            return array(type(desc.componentType()));
+        } else {
+            // class
+            return new ClassType(desc, List.of());
+        }
+    }
+
+    /**
+     * Constructs a parameterized class type.
+     *
+     * @param type the base type of the parameterized type
+     * @param typeArguments the type arguments of the parameterized type
+     * @return a parameterized class type
+     * @throws IllegalArgumentException if {@code type} is not a {@linkplain ClassType class type}
+     * @throws IllegalArgumentException if {@code type} is {@linkplain ClassType class type} with
+     * a non-empty {@linkplain ClassType#typeArguments() type argument list}.
+     */
+    static ClassType parameterized(JavaType type, JavaType... typeArguments) {
+        return parameterized(type, List.of(typeArguments));
+    }
+
+    /**
+     * Constructs a parameterized class type.
+     *
+     * @param type the base type of the parameterized type
+     * @param typeArguments the type arguments of the parameterized type
+     * @return a parameterized class type
+     * @throws IllegalArgumentException if {@code type} is not a {@linkplain ClassType class type}
+     * @throws IllegalArgumentException if {@code type} is {@linkplain ClassType class type} with
+     * a non-empty {@linkplain ClassType#typeArguments() type argument list}.
+     */
+    static ClassType parameterized(JavaType type, List<JavaType> typeArguments) {
+        if (!(type instanceof ClassType ct)) {
+            throw new IllegalArgumentException("Not a class type: " + type);
+        }
+        if (ct.hasTypeArguments()) {
+            throw new IllegalArgumentException("Already parameterized: " + type);
+        }
+        return new ClassType(type.toNominalDescriptor(), typeArguments);
+    }
+
+    /**
+     * Constructs an array type.
+     *
+     * @param elementType the array type's element type.
+     * @return an array type.
+     */
+    static ArrayType array(JavaType elementType) {
+        Objects.requireNonNull(elementType);
+        return new ArrayType(elementType);
+    }
+
+    /**
+     * Constructs an array type.
+     *
+     * @param elementType the array type's element type.
+     * @param dims the array type dimension
+     * @return an array type.
+     * @throws IllegalArgumentException if {@code dims < 1}.
+     */
+    static ArrayType array(JavaType elementType, int dims) {
+        Objects.requireNonNull(elementType);
+        if (dims < 1) {
+            throw new IllegalArgumentException("Invalid dimension: " + dims);
+        }
+        for (int i = 1 ; i < dims ; i++) {
+            elementType = array(elementType);
+        }
+        return array(elementType);
+    }
+
+    /**
+     * Constructs an unbounded wildcard type.
+     *
+     * @return an unbounded wildcard type.
+     */
+    static WildcardType wildcard() {
+        return new WildcardType(BoundKind.EXTENDS, JavaType.J_L_OBJECT);
+    }
+
+    /**
+     * Constructs a bounded wildcard type of the given kind.
+     *
+     * @return a bounded wildcard type.
+     */
+    static WildcardType wildcard(BoundKind kind, JavaType bound) {
+        return new WildcardType(kind, bound);
+    }
+
+    /**
+     * Constructs a reference to a type-variable with the given owner.
+     *
+     * @param bound the type-variable bound.
+     * @param owner the type-variable owner.
+     * @return a type-variable reference.
+     */
+    static TypeVarRef typeVarRef(String name, TypeVarRef.Owner owner, JavaType bound) {
+        return new TypeVarRef(name, owner, bound);
+    }
+
+    /**
+     * Constructs a Java type from a string representation.
+     * @param s string representation
+     * @return a Java type corresponding to the provided string representation
+     */
+    // Copied code in jdk.compiler module throws UOE
+    static JavaType ofString(String s) {
+/*__throw new UnsupportedOperationException();__*/        return (JavaType) CoreTypeFactory.JAVA_TYPE_FACTORY.constructType(java.lang.reflect.code.parser.impl.DescParser.parseExTypeElem(s));
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/MethodRef.java b/src/java.base/share/classes/java/lang/reflect/code/type/MethodRef.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/MethodRef.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.constant.MethodTypeDesc;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.impl.MethodRefImpl;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.reflect.code.type.FunctionType.functionType;
+
+/**
+ * The symbolic reference to a Java method.
+ */
+// @@@ require invoke kind:
+//    special, static, virtual
+//    interface_special, interface_static, interface_virtual
+//  Otherwise it is not possible to generate correct bytecode invoke instruction with
+//  a symbolic reference to a method or an interface method, specifically a
+//  constant pool entry of CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info.
+//
+//  We can infer the kind, if we can resolve the types and lookup the declared method
+public sealed interface MethodRef extends TypeVarRef.Owner permits MethodRefImpl {
+
+    TypeElement refType();
+
+    String name();
+
+    FunctionType type();
+
+    // Resolutions and model access
+
+    Executable resolveToMember(MethodHandles.Lookup l) throws ReflectiveOperationException;
+
+    MethodHandle resolveToHandle(MethodHandles.Lookup l) throws ReflectiveOperationException;
+
+    Optional<CoreOp.FuncOp> codeModel(MethodHandles.Lookup l) throws ReflectiveOperationException;
+
+    // Factories
+
+    static MethodRef method(Executable e) {
+        return method(e.getDeclaringClass(), e.getName(),
+                e instanceof Method m ? m.getReturnType() : e.getDeclaringClass(),
+                e.getParameterTypes());
+    }
+
+    static MethodRef method(Class<?> refType, String name, MethodType mt) {
+        return method(refType, name, mt.returnType(), mt.parameterList());
+    }
+
+    static MethodRef method(Class<?> refType, String name, Class<?> retType, Class<?>... params) {
+        return method(refType, name, retType, List.of(params));
+    }
+
+    static MethodRef method(Class<?> refType, String name, Class<?> retType, List<Class<?>> params) {
+        return method(JavaType.type(refType), name, JavaType.type(retType), params.stream().map(JavaType::type).toList());
+    }
+
+
+    static MethodRef method(TypeElement refType, String name, FunctionType type) {
+        return new MethodRefImpl(refType, name, type);
+    }
+
+    static MethodRef method(TypeElement refType, String name, TypeElement retType, TypeElement... params) {
+        return method(refType, name, functionType(retType, params));
+    }
+
+    static MethodRef method(TypeElement refType, String name, TypeElement retType, List<? extends TypeElement> params) {
+        return method(refType, name, functionType(retType, params));
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    static MethodRef ofString(String s) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseMethodRef(s);
+    }
+
+
+    // MethodTypeDesc factories
+    // @@@ Where else to place them?
+
+    static FunctionType ofNominalDescriptor(MethodTypeDesc d) {
+        return FunctionType.functionType(
+                JavaType.type(d.returnType()),
+                d.parameterList().stream().map(JavaType::type).toList());
+    }
+
+    static MethodTypeDesc toNominalDescriptor(FunctionType t) {
+        return MethodTypeDesc.of(
+                toClassDesc(t.returnType()),
+                t.parameterTypes().stream().map(MethodRef::toClassDesc).toList());
+    }
+
+    private static ClassDesc toClassDesc(TypeElement e) {
+        if (!(e instanceof JavaType jt)) {
+            throw new IllegalArgumentException();
+        }
+
+        return jt.toNominalDescriptor();
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/PrimitiveType.java b/src/java.base/share/classes/java/lang/reflect/code/type/PrimitiveType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/PrimitiveType.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A primitive type.
+ */
+public final class PrimitiveType implements JavaType {
+    // Fully qualified name
+    private final ClassDesc type;
+
+    PrimitiveType(ClassDesc type) {
+        this.type = type;
+    }
+
+    @Override
+    public Type resolve(Lookup lookup) throws ReflectiveOperationException {
+        return type.resolveConstantDesc(lookup);
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(type.displayName(), List.of());
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PrimitiveType typeDesc = (PrimitiveType) o;
+
+        return type.equals(typeDesc.type);
+    }
+
+    @Override
+    public int hashCode() {
+        return type.hashCode();
+    }
+
+    @Override
+    public JavaType erasure() {
+        return this;
+    }
+
+    @Override
+    public JavaType toBasicType() {
+        return switch (type.descriptorString().charAt(0)) {
+            case 'V' -> JavaType.VOID;
+            case 'J' -> JavaType.LONG;
+            case 'F' -> JavaType.FLOAT;
+            case 'D' -> JavaType.DOUBLE;
+            default -> JavaType.INT;
+        };
+    }
+
+    /**
+     * {@return the boxed class type associated with this primitive type (if any)}
+     */
+    public Optional<ClassType> box() {
+        class LazyHolder {
+            static final Map<PrimitiveType, ClassType> primitiveToWrapper = Map.of(
+                    BYTE, J_L_BYTE,
+                    SHORT, J_L_SHORT,
+                    INT, J_L_INTEGER,
+                    LONG, J_L_LONG,
+                    FLOAT, J_L_FLOAT,
+                    DOUBLE, J_L_DOUBLE,
+                    CHAR, J_L_CHARACTER,
+                    BOOLEAN, J_L_BOOLEAN
+            );
+        }
+        return Optional.ofNullable(LazyHolder.primitiveToWrapper.get(this));
+    };
+
+    @Override
+    public ClassDesc toNominalDescriptor() {
+        return type;
+    }
+
+    /**
+     * {@return {@code true} if this type is {@link JavaType#VOID}}
+     */
+    public boolean isVoid() {
+        return toBasicType() == JavaType.VOID;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/RecordTypeRef.java b/src/java.base/share/classes/java/lang/reflect/code/type/RecordTypeRef.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/RecordTypeRef.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.type.impl.RecordTypeRefImpl;
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * The symbolic reference to a Java record type.
+ */
+public sealed interface RecordTypeRef permits RecordTypeRefImpl {
+    TypeElement recordType();
+
+    /**
+     * The symbolic reference to a Java record component.
+     * @param type the type of the component
+     * @param name the name of the component
+     */
+    record ComponentRef(TypeElement type, String name) {}
+
+    List<ComponentRef> components();
+
+    MethodRef methodForComponent(int i);
+
+    // Factories
+
+    static RecordTypeRef recordType(Class<? extends Record> c) {
+        List<ComponentRef> components = Stream.of(c.getRecordComponents())
+                .map(rc -> new ComponentRef(JavaType.type(rc.getType()), rc.getName()))
+                .toList();
+        return recordType(JavaType.type(c), components);
+    }
+
+    static RecordTypeRef recordType(TypeElement recordType, ComponentRef... components) {
+        return recordType(recordType, List.of(components));
+    }
+
+    static RecordTypeRef recordType(TypeElement recordType, List<ComponentRef> components) {
+        return new RecordTypeRefImpl(recordType, components);
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    static RecordTypeRef ofString(String s) {
+/*__throw new UnsupportedOperationException();__*/        return java.lang.reflect.code.parser.impl.DescParser.parseRecordTypeRef(s);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/TupleType.java b/src/java.base/share/classes/java/lang/reflect/code/type/TupleType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/TupleType.java
@@ -0,0 +1,91 @@
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.Value;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A tuple type.
+ */
+public final class TupleType implements TypeElement {
+    static final String NAME = "Tuple";
+
+    final List<TypeElement> componentTypes;
+
+    TupleType(List<? extends TypeElement> componentTypes) {
+        this.componentTypes = List.copyOf(componentTypes);
+    }
+
+    /**
+     * {@return the tuple's component types, in order}
+     */
+    public List<TypeElement> componentTypes() {
+        return componentTypes;
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME, componentTypes.stream().map(TypeElement::externalize).toList());
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof TupleType that && componentTypes.equals(that.componentTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return componentTypes.hashCode();
+    }
+
+    /**
+     * Constructs a tuple type.
+     *
+     * @param componentTypes the tuple type's component types.
+     * @return a tuple type.
+     */
+    public static TupleType tupleType(List<? extends TypeElement> componentTypes) {
+        Objects.requireNonNull(componentTypes);
+        return new TupleType(componentTypes);
+    }
+
+    /**
+     * Constructs a tuple type.
+     *
+     * @param componentTypes the tuple type's component types.
+     * @return a tuple type.
+     */
+    public static TupleType tupleType(TypeElement... componentTypes) {
+        return tupleType(List.of(componentTypes));
+    }
+
+    /**
+     * Constructs a tuple type whose components are the types of
+     * the given values.
+     *
+     * @param values the values.
+     * @return a tuple type.
+     */
+    public static TupleType tupleTypeFromValues(List<? extends Value> values) {
+        return tupleType(values.stream().map(Value::type).toList());
+    }
+
+    /**
+     * Constructs a tuple type whose components are the types of
+     * the given values.
+     *
+     * @param values the values.
+     * @return a tuple type.
+     */
+    public static TupleType tupleTypeFromValues(Value... values) {
+        return tupleType(Stream.of(values).map(Value::type).toList());
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/TypeElementFactory.java b/src/java.base/share/classes/java/lang/reflect/code/type/TypeElementFactory.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/TypeElementFactory.java
@@ -0,0 +1,39 @@
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.TypeElement;
+
+/**
+ * A type element factory for construction a {@link TypeElement} from its
+ * {@link TypeElement.ExternalizedTypeElement external content}.
+ */
+@FunctionalInterface
+public interface TypeElementFactory {
+
+    /**
+     * Constructs a {@link TypeElement} from its
+     * {@link TypeElement.ExternalizedTypeElement external content}.
+     * <p>
+     * If there is no mapping from the external content to a type
+     * element then this method returns {@code null}.
+     *
+     * @param tree the externalized type element.
+     * @return the type element.
+     */
+    TypeElement constructType(TypeElement.ExternalizedTypeElement tree);
+
+    /**
+     * Compose this type element factory with another type element factory.
+     * <p>
+     * If there is no mapping in this type element factory then the result
+     * of the other type element factory is returned.
+     *
+     * @param after the other type element factory.
+     * @return the composed type element factory.
+     */
+    default TypeElementFactory andThen(TypeElementFactory after) {
+        return t -> {
+            TypeElement te = constructType(t);
+            return te != null ? te : after.constructType(t);
+        };
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/TypeVarRef.java b/src/java.base/share/classes/java/lang/reflect/code/type/TypeVarRef.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/TypeVarRef.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+
+/**
+ * A type-variable reference.
+ */
+public final class TypeVarRef implements JavaType {
+
+    final String name;
+    final Owner owner;
+    final JavaType bound;
+
+    TypeVarRef(String name, Owner owner, JavaType bound) {
+        this.name = name;
+        this.owner = owner;
+        this.bound = bound;
+    }
+
+    @Override
+    public Type resolve(Lookup lookup) throws ReflectiveOperationException {
+        TypeVariable<?>[] typeVariables = switch (owner) {
+            case MethodRef methodRef -> {
+                Executable method = ((MethodRef)owner).resolveToMember(lookup);
+                yield method.getTypeParameters();
+            }
+            case JavaType type -> {
+                Class<?> erasedDecl = (Class<?>)type.resolve(lookup);
+                yield erasedDecl.getTypeParameters();
+            }
+        };
+        for (TypeVariable<?> tv : typeVariables) {
+            if (tv.getName().equals(name)) {
+                return tv;
+            }
+        }
+        throw new ReflectiveOperationException("Type-variable not found: " + name);
+    }
+
+    /**
+     * {@return the type-variable name}
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * {@return the type-variable bound}
+     */
+    public JavaType bound() {
+        return bound;
+    }
+
+    /**
+     * {@return the owner of this type-variable}
+     */
+    public Owner owner() {
+        return owner;
+    }
+
+    @Override
+    public JavaType erasure() {
+        return bound.erasure();
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(String.format("#%s::%s", owner, name),
+                List.of(bound.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof TypeVarRef that &&
+                name.equals(that.name) &&
+                bound.equals(that.bound);
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override
+    public JavaType toBasicType() {
+        return erasure().toBasicType();
+    }
+
+    @Override
+    public ClassDesc toNominalDescriptor() {
+        return erasure().toNominalDescriptor();
+    }
+
+    /**
+     * The owner of a type-variable - either a class or a method.
+     */
+    public sealed interface Owner permits ClassType, MethodRef { }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/VarType.java b/src/java.base/share/classes/java/lang/reflect/code/type/VarType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/VarType.java
@@ -0,0 +1,58 @@
+package java.lang.reflect.code.type;
+
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A variable type.
+ */
+public final class VarType implements TypeElement {
+    static final String NAME = "Var";
+
+    final TypeElement variableType;
+
+    VarType(TypeElement variableType) {
+        this.variableType = variableType;
+    }
+
+    /**
+     * {@return the variable type's value type}
+     */
+    public TypeElement valueType() {
+        return variableType;
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return new ExternalizedTypeElement(NAME, List.of(variableType.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof VarType that &&
+                variableType.equals(that.variableType);
+    }
+
+    @Override
+    public int hashCode() {
+        return variableType.hashCode();
+    }
+
+    /**
+     * Constructs a variable type.
+     *
+     * @param valueType the variable's value type.
+     * @return a variable type.
+     */
+    public static VarType varType(TypeElement valueType) {
+        Objects.requireNonNull(valueType);
+        return new VarType(valueType);
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/WildcardType.java b/src/java.base/share/classes/java/lang/reflect/code/type/WildcardType.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/WildcardType.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A wildcard type.
+ */
+public final class WildcardType implements JavaType {
+
+    final BoundKind kind;
+    final JavaType boundType;
+
+    WildcardType(BoundKind kind, JavaType boundType) {
+        this.kind = kind;
+        this.boundType = boundType;
+    }
+
+    @Override
+    public Type resolve(Lookup lookup) throws ReflectiveOperationException {
+        Type[] upperBounds = kind == BoundKind.EXTENDS ?
+                new Type[] { boundType.resolve(lookup) } : new Type[] { Object.class };
+        Type[] lowerBounds = kind == BoundKind.SUPER ?
+                new Type[] { boundType.resolve(lookup) } : new Type[0];
+        return makeReflectiveWildcard(upperBounds, lowerBounds);
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    private static java.lang.reflect.WildcardType makeReflectiveWildcard(Type[] upper, Type[] lower) {
+/*__throw new UnsupportedOperationException();__*/        return sun.reflect.generics.reflectiveObjects.WildcardTypeImpl.make(upper, lower);
+    }
+
+    /**
+     * {@return the wildcard type's bound type}
+     */
+    public JavaType boundType() {
+        return boundType;
+    }
+
+    /**
+     * {@return the wildcard type's bound kind}
+     */
+    public BoundKind boundKind() {
+        return kind;
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        String prefix = kind == BoundKind.EXTENDS ? "+" : "-";
+        return new ExternalizedTypeElement(prefix, List.of(boundType.externalize()));
+    }
+
+    @Override
+    public String toString() {
+        return externalize().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return o instanceof WildcardType that &&
+                kind.equals(that.kind) &&
+                boundType.equals(that.boundType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(boundType, kind);
+    }
+
+    @Override
+    public JavaType erasure() {
+        throw new UnsupportedOperationException("Wildcard type");
+    }
+
+    @Override
+    public JavaType toBasicType() {
+        throw new UnsupportedOperationException("Wildcard type");
+    }
+
+    @Override
+    public ClassDesc toNominalDescriptor() {
+        throw new UnsupportedOperationException("Wildcard type");
+    }
+
+    /**
+     * The bound kind of a wildcard type.
+     */
+    public enum BoundKind {
+        /** A bound kind representing a {@code ? extends} wildcard type*/
+        EXTENDS,
+        /** A bound kind representing a {@code ? super} wildcard type*/
+        SUPER
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/impl/FieldRefImpl.java b/src/java.base/share/classes/java/lang/reflect/code/type/impl/FieldRefImpl.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/impl/FieldRefImpl.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type.impl;
+
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.TypeElement;
+
+public final class FieldRefImpl implements FieldRef {
+    final TypeElement refType;
+    final String name;
+    final TypeElement type;
+
+    public FieldRefImpl(TypeElement refType, String name, TypeElement type) {
+        this.refType = refType;
+        this.name = name;
+        this.type = type;
+    }
+
+    @Override
+    public TypeElement refType() {
+        return refType;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public TypeElement type() {
+        return type;
+    }
+
+    @Override
+    public Field resolveToMember(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        Class<?> refC = resolve(l, refType);
+        Class<?> typeC = resolve(l, type);
+
+        Field f = refC.getDeclaredField(name);
+        if (!f.getType().equals(typeC)) {
+            throw new NoSuchFieldException();
+        }
+
+        return f;
+    }
+
+    @Override
+    public VarHandle resolveToHandle(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        Class<?> refC = resolve(l, refType);
+        Class<?> typeC = resolve(l, type);
+
+        VarHandle vh = null;
+        ReflectiveOperationException c = null;
+
+        try {
+            vh = l.findStaticVarHandle(refC, name, typeC);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            c = e;
+        }
+
+        if (c != null) {
+            c = null;
+            try {
+                vh = l.findVarHandle(refC, name, typeC);
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                c = e;
+            }
+        }
+
+        if (c != null) {
+            throw c;
+        }
+
+        assert vh != null;
+        return vh;
+    }
+
+    static Class<?> resolve(MethodHandles.Lookup l, TypeElement t) throws ReflectiveOperationException {
+        if (t instanceof JavaType jt) {
+            return (Class<?>)jt.erasure().resolve(l);
+        } else {
+            // @@@
+            throw new ReflectiveOperationException();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return refType + "::" + name + "()" + type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FieldRefImpl fieldDesc = (FieldRefImpl) o;
+
+        if (!refType.equals(fieldDesc.refType)) return false;
+        if (!name.equals(fieldDesc.name)) return false;
+        return type.equals(fieldDesc.type);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = refType.hashCode();
+        result = 31 * result + name.hashCode();
+        result = 31 * result + type.hashCode();
+        return result;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/impl/MethodRefImpl.java b/src/java.base/share/classes/java/lang/reflect/code/type/impl/MethodRefImpl.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/impl/MethodRefImpl.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type.impl;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleInfo;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.TypeElement;
+import java.util.Optional;
+
+import static java.util.stream.Collectors.joining;
+
+public final class MethodRefImpl implements MethodRef {
+    final TypeElement refType;
+    final String name;
+    final FunctionType type;
+
+    public MethodRefImpl(TypeElement refType, String name, FunctionType type) {
+        this.refType = refType;
+        this.name = name;
+        this.type = type;
+    }
+
+    @Override
+    public TypeElement refType() {
+        return refType;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public FunctionType type() {
+        return type;
+    }
+
+    @Override
+    public Method resolveToMember(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        // @@@ Constructor
+        MethodHandleInfo methodHandleInfo = l.revealDirect(resolveToHandle(l));
+        return methodHandleInfo.reflectAs(Method.class, l);
+    }
+
+    @Override
+    public MethodHandle resolveToHandle(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        // @@@ kind
+        Class<?> refC = resolve(l, refType);
+
+        MethodType mt = MethodRef.toNominalDescriptor(type).resolveConstantDesc(l);
+
+        MethodHandle mh = null;
+        ReflectiveOperationException c = null;
+
+        try {
+            mh = l.findStatic(refC, name, mt);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            c = e;
+        }
+
+        if (c != null) {
+            c = null;
+            try {
+                mh = l.findVirtual(refC, name, mt);
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                c = e;
+            }
+        }
+
+        if (c != null) {
+            throw c;
+        }
+
+        assert mh != null;
+        return mh;
+    }
+
+    static Class<?> resolve(MethodHandles.Lookup l, TypeElement t) throws ReflectiveOperationException {
+        if (t instanceof JavaType jt) {
+            return (Class<?>)jt.erasure().resolve(l);
+        } else {
+            // @@@
+            throw new ReflectiveOperationException();
+        }
+    }
+
+    // Copied code in jdk.compiler module throws UOE
+    @Override
+    public Optional<CoreOp.FuncOp> codeModel(MethodHandles.Lookup l) throws ReflectiveOperationException {
+/*__throw new UnsupportedOperationException();__*/        return resolveToMember(l).getCodeModel();
+    }
+
+    @Override
+    public String toString() {
+        return refType + "::" + name +
+            type.parameterTypes().stream().map(TypeElement::toString)
+                    .collect(joining(", ", "(", ")")) + type.returnType();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MethodRefImpl that = (MethodRefImpl) o;
+
+        if (!refType.equals(that.refType)) return false;
+        if (!name.equals(that.name)) return false;
+        return type.equals(that.type);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = refType.hashCode();
+        result = 31 * result + name.hashCode();
+        result = 31 * result + type.hashCode();
+        return result;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/impl/RecordTypeRefImpl.java b/src/java.base/share/classes/java/lang/reflect/code/type/impl/RecordTypeRefImpl.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/impl/RecordTypeRefImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.type.impl;
+
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.RecordTypeRef;
+import java.lang.reflect.code.TypeElement;
+import java.util.List;
+
+import static java.util.stream.Collectors.joining;
+
+public final class RecordTypeRefImpl implements RecordTypeRef {
+    final TypeElement recordType;
+    final List<ComponentRef> components;
+
+    public RecordTypeRefImpl(TypeElement recordType, List<ComponentRef> components) {
+        this.recordType = recordType;
+        this.components = List.copyOf(components);
+    }
+
+    @Override
+    public TypeElement recordType() {
+        return recordType;
+    }
+
+    @Override
+    public List<ComponentRef> components() {
+        return components;
+    }
+
+    @Override
+    public MethodRef methodForComponent(int i) {
+        if (i < 0 || i >= components.size()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        ComponentRef c = components.get(i);
+        return MethodRef.method(recordType, c.name(), c.type());
+    }
+
+    @Override
+    public String toString() {
+        return components.stream()
+                .map(c -> c.type().toString() + " " + c.name())
+                .collect(joining(", ", "(", ")")) +
+                recordType.toString();
+    }
+
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/type/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/type/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/type/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Types and symbolic references for code models.
+ */
+package java.lang.reflect.code.type;
diff a/src/java.base/share/classes/java/lang/reflect/code/writer/OpBuilder.java b/src/java.base/share/classes/java/lang/reflect/code/writer/OpBuilder.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/writer/OpBuilder.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.writer;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.OpFactory;
+import java.lang.reflect.code.op.ExternalizableOp;
+import java.lang.reflect.code.type.*;
+import java.util.*;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.*;
+
+/**
+ * A transformer of code models to models that build them.
+ * <p>
+ * A building code model when executed will construct the same code model it was transformed from.
+ * Such a building code model could be transformed to bytecode and stored in class files.
+ */
+public class OpBuilder {
+
+    static final JavaType J_C_O_EXTERNALIZED_OP = type(ExternalizableOp.ExternalizedOp.class);
+
+    static final MethodRef OP_FACTORY_CONSTRUCT = MethodRef.method(OpFactory.class, "constructOp",
+            Op.class, ExternalizableOp.ExternalizedOp.class);
+
+    static final MethodRef TYPE_ELEMENT_FACTORY_CONSTRUCT = MethodRef.method(TypeElementFactory.class, "constructType",
+            TypeElement.class, ExternalizedTypeElement.class);
+
+    static final MethodRef EX_TYPE_ELEMENT_OF_STRING = MethodRef.method(ExternalizedTypeElement.class, "ofString",
+            ExternalizedTypeElement.class, String.class);
+
+    static final MethodRef BODY_BUILDER_OF = MethodRef.method(Body.Builder.class, "of",
+            Body.Builder.class, Body.Builder.class, FunctionType.class);
+
+    static final MethodRef BODY_BUILDER_ENTRY_BLOCK = MethodRef.method(Body.Builder.class, "entryBlock",
+            Block.Builder.class);
+
+    static final MethodRef BLOCK_BUILDER_SUCCESSOR = MethodRef.method(Block.Builder.class, "successor",
+            Block.Reference.class, Value[].class);
+
+    static final MethodRef BLOCK_BUILDER_OP = MethodRef.method(Block.Builder.class, "op",
+            Op.Result.class, Op.class);
+
+    static final MethodRef BLOCK_BUILDER_BLOCK = MethodRef.method(Block.Builder.class, "block",
+            Block.Builder.class, TypeElement[].class);
+
+    static final MethodRef BLOCK_BUILDER_PARAMETER = MethodRef.method(Block.Builder.class, "parameter",
+            Block.Parameter.class, TypeElement.class);
+
+    static final MethodRef FUNCTION_TYPE_FUNCTION_TYPE = MethodRef.method(FunctionType.class, "functionType",
+            FunctionType.class, TypeElement.class, TypeElement[].class);
+
+    static final MethodRef METHOD_REF_OF_STRING = MethodRef.method(MethodRef.class, "ofString",
+            MethodRef.class, String.class);
+
+    static final MethodRef FIELD_REF_OF_STRING = MethodRef.method(FieldRef.class, "ofString",
+            FieldRef.class, String.class);
+
+    static final MethodRef RECORD_TYPE_REF_OF_STRING = MethodRef.method(RecordTypeRef.class, "ofString",
+            RecordTypeRef.class, String.class);
+
+
+    static final JavaType J_U_LIST = type(List.class);
+
+    static final MethodRef LIST_OF_ARRAY = MethodRef.method(J_U_LIST, "of",
+            J_U_LIST, array(J_L_OBJECT, 1));
+
+    static final JavaType J_U_MAP = type(Map.class);
+
+    static final JavaType J_U_HASH_MAP = type(HashMap.class);
+
+    static final JavaType J_U_MAP_ENTRY = type(Map.Entry.class);
+
+    static final MethodRef MAP_ENTRY = MethodRef.method(J_U_MAP, "entry",
+            J_U_MAP, J_L_OBJECT, J_L_OBJECT);
+
+    static final MethodRef MAP_OF = MethodRef.method(J_U_MAP, "of",
+            J_U_MAP);
+
+    static final MethodRef MAP_OF_ARRAY = MethodRef.method(J_U_MAP, "of",
+            J_U_MAP, array(J_U_MAP_ENTRY, 1));
+
+    static final MethodRef MAP_PUT = MethodRef.method(J_U_MAP, "put",
+            J_L_OBJECT, J_L_OBJECT, J_L_OBJECT);
+
+
+    static final FunctionType EXTERNALIZED_OP_F_TYPE = functionType(
+            J_C_O_EXTERNALIZED_OP,
+            J_L_STRING,
+            J_U_LIST,
+            J_U_LIST,
+            type(TypeElement.class),
+            J_U_MAP,
+            J_U_LIST);
+
+    static final FunctionType BUILDER_F_TYPE = functionType(type(Op.class),
+            type(OpFactory.class),
+            type(TypeElementFactory.class));
+
+
+    Map<Value, Value> valueMap;
+
+    Map<Block, Value> blockMap;
+
+    Block.Builder builder;
+
+    Value opFactory;
+
+    Value typeElementFactory;
+
+    /**
+     * Transform the given code model to one that builds it.
+     *
+     * @param op the code model
+     * @return the building code model.
+     */
+    public static FuncOp createBuilderFunction(Op op) {
+        return new OpBuilder().build(op);
+    }
+
+    OpBuilder() {
+        this.valueMap = new HashMap<>();
+        this.blockMap = new HashMap<>();
+    }
+
+    FuncOp build(Op op) {
+        Body.Builder body = Body.Builder.of(null, BUILDER_F_TYPE);
+
+        builder = body.entryBlock();
+        opFactory = builder.parameters().get(0);
+        typeElementFactory = builder.parameters().get(1);
+
+        Value ancestorBody = builder.op(constant(type(Body.Builder.class), null));
+        Value result = buildOp(ancestorBody, op);
+        builder.op(_return(result));
+
+        return func("builder." + op.opName(), body);
+    }
+
+
+    Value buildOp(Value ancestorBody, Op inputOp) {
+        List<Value> bodies = new ArrayList<>();
+        for (Body inputBody : inputOp.bodies()) {
+            Value body = buildBody(ancestorBody, inputBody);
+            bodies.add(body);
+        }
+
+        List<Value> operands = new ArrayList<>();
+        for (Value inputOperand : inputOp.operands()) {
+            Value operand = valueMap.get(inputOperand);
+            operands.add(operand);
+        }
+
+        List<Value> successors = new ArrayList<>();
+        for (Block.Reference inputSuccessor : inputOp.successors()) {
+            List<Value> successorArgs = new ArrayList<>();
+            for (Value inputOperand : inputSuccessor.arguments()) {
+                Value operand = valueMap.get(inputOperand);
+                successorArgs.add(operand);
+            }
+            Value referencedBlock = blockMap.get(inputSuccessor.targetBlock());
+
+            List<Value> args = new ArrayList<>();
+            args.add(referencedBlock);
+            args.addAll(successorArgs);
+            Value successor = builder.op(invoke(BLOCK_BUILDER_SUCCESSOR, args));
+            successors.add(successor);
+        }
+
+        Value opDef = buildOpDefinition(
+                inputOp.opName(),
+                operands,
+                successors,
+                inputOp.resultType(),
+                inputOp instanceof ExternalizableOp exop ? exop.attributes() : Map.of(),
+                bodies);
+        return builder.op(invoke(OP_FACTORY_CONSTRUCT, opFactory, opDef));
+    }
+
+
+    Value buildOpDefinition(String name,
+                            List<Value> operands,
+                            List<Value> successors,
+                            TypeElement resultType,
+                            Map<String, Object> attributes,
+                            List<Value> bodies) {
+        List<Value> args = List.of(
+                builder.op(constant(J_L_STRING, name)),
+                buildList(type(Value.class), operands),
+                buildList(type(Block.Reference.class), successors),
+                buildType(resultType),
+                buildAttributeMap(attributes),
+                buildList(type(Body.Builder.class), bodies));
+        return builder.op(_new(EXTERNALIZED_OP_F_TYPE, args));
+    }
+
+    Value buildBody(Value ancestorBodyValue, Body inputBody) {
+        Value yieldType = buildType(inputBody.yieldType());
+        Value bodyType = builder.op(invoke(FUNCTION_TYPE_FUNCTION_TYPE, yieldType));
+        Value body = builder.op(invoke(BODY_BUILDER_OF, ancestorBodyValue, bodyType));
+
+        Value entryBlock = null;
+        for (Block inputBlock : inputBody.blocks()) {
+            Value block;
+            if (inputBlock.isEntryBlock()) {
+                block = entryBlock = builder.op(invoke(BODY_BUILDER_ENTRY_BLOCK, body));
+            } else {
+                assert entryBlock != null;
+                block = builder.op(invoke(BLOCK_BUILDER_BLOCK, entryBlock));
+            }
+            blockMap.put(inputBlock, block);
+
+            for (Block.Parameter inputP : inputBlock.parameters()) {
+                Value type = buildType(inputP.type());
+                Value blockParameter = builder.op(invoke(BLOCK_BUILDER_PARAMETER, block, type));
+                valueMap.put(inputP, blockParameter);
+            }
+        }
+
+        for (Block inputBlock : inputBody.blocks()) {
+            Value block = blockMap.get(inputBlock);
+            for (Op inputOp : inputBlock.ops()) {
+                Value op = buildOp(body, inputOp);
+                Value result = builder.op(invoke(BLOCK_BUILDER_OP, block, op));
+                valueMap.put(inputOp.result(), result);
+            }
+        }
+
+        return body;
+    }
+
+    Value buildType(TypeElement t) {
+        Value typeString = builder.op(constant(J_L_STRING, t.toString()));
+        Value exTypeElem = builder.op(invoke(EX_TYPE_ELEMENT_OF_STRING, typeString));
+        return builder.op(invoke(TYPE_ELEMENT_FACTORY_CONSTRUCT, typeElementFactory, exTypeElem));
+    }
+
+    Value buildAttributeMap(Map<String, Object> attributes) {
+        List<Value> keysAndValues = new ArrayList<>();
+        for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+            Value key = builder.op(constant(J_L_STRING, entry.getKey()));
+            Value value = buildAttributeValue(entry.getValue());
+            keysAndValues.add(key);
+            keysAndValues.add(value);
+        }
+        return buildMap(J_L_STRING, J_L_OBJECT, keysAndValues);
+    }
+
+    Value buildAttributeValue(Object value) {
+        return switch (value) {
+            case Boolean v -> {
+                yield builder.op(constant(BOOLEAN, value));
+            }
+            case Byte v -> {
+                yield builder.op(constant(BYTE, value));
+            }
+            case Short v -> {
+                yield builder.op(constant(SHORT, value));
+            }
+            case Character v -> {
+                yield builder.op(constant(CHAR, value));
+            }
+            case Integer v -> {
+                yield builder.op(constant(INT, value));
+            }
+            case Long v -> {
+                yield builder.op(constant(LONG, value));
+            }
+            case Float v -> {
+                yield builder.op(constant(FLOAT, value));
+            }
+            case Double v -> {
+                yield builder.op(constant(DOUBLE, value));
+            }
+            case Class<?> v -> {
+                yield buildType(JavaType.type(v));
+            }
+            case String s -> {
+                yield builder.op(constant(J_L_STRING, value));
+            }
+            case MethodRef r -> {
+                Value string = builder.op(constant(J_L_STRING, value.toString()));
+                yield builder.op(invoke(METHOD_REF_OF_STRING, string));
+            }
+            case FieldRef r -> {
+                Value string = builder.op(constant(J_L_STRING, value.toString()));
+                yield builder.op(invoke(FIELD_REF_OF_STRING, string));
+            }
+            case RecordTypeRef r -> {
+                Value string = builder.op(constant(J_L_STRING, value.toString()));
+                yield builder.op(invoke(RECORD_TYPE_REF_OF_STRING, string));
+            }
+            case TypeElement f -> {
+                yield buildType(f);
+            }
+            case Location l -> {
+                // @@@ Construct location explicitly
+                yield builder.op(constant(J_L_STRING, l.toString()));
+            }
+            case Object o when value == ExternalizableOp.NULL_ATTRIBUTE_VALUE -> {
+                yield builder.op(fieldLoad(FieldRef.field(ExternalizableOp.class,
+                        "NULL_ATTRIBUTE_VALUE", Object.class)));
+            }
+            default -> {
+                // @@@ use the result of value.toString()?
+                throw new UnsupportedOperationException("Unsupported attribute value: " + value);
+            }
+        };
+    }
+
+
+    Value buildMap(JavaType keyType, JavaType valueType, List<Value> keysAndValues) {
+        JavaType mapType = parameterized(J_U_MAP, keyType, valueType);
+        if (keysAndValues.isEmpty()) {
+            return builder.op(invoke(MAP_OF));
+        } else {
+            Value map = builder.op(_new(mapType, functionType(J_U_HASH_MAP)));
+            for (int i = 0; i < keysAndValues.size(); i += 2) {
+                Value key = keysAndValues.get(i);
+                Value value = keysAndValues.get(i + 1);
+                builder.op(invoke(MAP_PUT, map, key, value));
+            }
+            return map;
+        }
+    }
+
+
+    Value buildList(JavaType elementType, List<Value> elements) {
+        JavaType listType = parameterized(J_U_LIST, elementType);
+        if (elements.size() < 11) {
+            MethodRef listOf = MethodRef.method(J_U_LIST, "of",
+                    J_U_LIST, Collections.nCopies(elements.size(), J_L_OBJECT));
+            return builder.op(invoke(listType, listOf, elements));
+        } else {
+            Value array = buildArray(elementType, elements);
+            return builder.op(invoke(listType, LIST_OF_ARRAY, array));
+        }
+    }
+
+
+    Value buildArray(JavaType elementType, List<Value> elements) {
+        Value array = builder.op(newArray(elementType,
+                builder.op(constant(INT, elements.size()))));
+        for (int i = 0; i < elements.size(); i++) {
+            builder.op(arrayStoreOp(array, elements.get(i),
+                    builder.op(constant(INT, i))));
+        }
+        return array;
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/writer/OpWriter.java b/src/java.base/share/classes/java/lang/reflect/code/writer/OpWriter.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/writer/OpWriter.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.reflect.code.writer;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.ExternalizableOp;
+import java.lang.reflect.code.type.JavaType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A writer of code models to the textual form.
+ * <p>
+ * A code model in textual form may be parsed back into the runtime form by parsing it.
+ */
+// @@@ We cannot link to OpParser since this code is copied into the jdk.compiler module
+public final class OpWriter {
+
+    static final class GlobalValueBlockNaming implements Function<CodeItem, String> {
+        final Map<CodeItem, String> gn;
+        int valueOrdinal = 0;
+        int blockOrdinal = 0;
+
+        GlobalValueBlockNaming() {
+            this.gn = new HashMap<>();
+        }
+
+        @Override
+        public String apply(CodeItem codeItem) {
+            return switch (codeItem) {
+                case Block block -> gn.computeIfAbsent(block, _b -> "block_" + blockOrdinal++);
+                case Value value -> gn.computeIfAbsent(value, _v -> String.valueOf(valueOrdinal++));
+                default -> throw new IllegalStateException("Unexpected code item: " + codeItem);
+            };
+        }
+    }
+
+    static final class AttributeMapper {
+        static String toString(Object value) {
+            return value == ExternalizableOp.NULL_ATTRIBUTE_VALUE
+                    ? "null"
+                    : "\"" + quote(value.toString()) + "\"";
+        }
+    }
+
+    // Copied from com.sun.tools.javac.util.Convert
+    static String quote(String s) {
+        StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            buf.append(quote(s.charAt(i)));
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Escapes a character if it has an escape sequence or is
+     * non-printable ASCII.  Leaves non-ASCII characters alone.
+     */
+    static String quote(char ch) {
+        return switch (ch) {
+            case '\b' -> "\\b";
+            case '\f' -> "\\f";
+            case '\n' -> "\\n";
+            case '\r' -> "\\r";
+            case '\t' -> "\\t";
+            case '\'' -> "\\'";
+            case '\"' -> "\\\"";
+            case '\\' -> "\\\\";
+            default -> (isPrintableAscii(ch))
+                    ? String.valueOf(ch)
+                    : String.format("\\u%04x", (int) ch);
+        };
+    }
+
+    /**
+     * Is a character printable ASCII?
+     */
+    static boolean isPrintableAscii(char ch) {
+        return ch >= ' ' && ch <= '~';
+    }
+
+    static final class IndentWriter extends Writer {
+        static final int INDENT = 2;
+
+        final Writer w;
+        int indent;
+        boolean writeIndent = true;
+
+        IndentWriter(Writer w) {
+            this(w, 0);
+        }
+
+        IndentWriter(Writer w, int indent) {
+            this.w = w;
+            this.indent = indent;
+        }
+
+        @Override
+        public void write(char[] cbuf, int off, int len) throws IOException {
+            if (writeIndent) {
+                w.write(" ".repeat(indent));
+                writeIndent = false;
+            }
+            w.write(cbuf, off, len);
+            if (len > 0 && cbuf[off + len - 1] == '\n') {
+                writeIndent = true;
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            w.flush();
+        }
+
+        @Override
+        public void close() throws IOException {
+            w.close();
+        }
+
+        void in() {
+            in(INDENT);
+        }
+
+        void in(int i) {
+            indent += i;
+        }
+
+        void out() {
+            out(INDENT);
+        }
+
+        void out(int i) {
+            indent -= i;
+        }
+    }
+
+    /**
+     * Computes global names for blocks and values in a code model.
+     * <p>
+     * The code model is traversed in the same order as if the model
+     * was written. Therefore, the names in the returned map will the
+     * same as the names that are written. This can be useful for debugging
+     * and testing.
+     *
+     * @param root the code model
+     * @return the map of computed names, modifiable
+     */
+    public static Function<CodeItem, String> computeGlobalNames(Op root) {
+        OpWriter w = new OpWriter(Writer.nullWriter());
+        w.writeOp(root);
+        return w.namer();
+    }
+
+    /**
+     * Writes a code model (an operation) to the character stream.
+     * <p>
+     * The character stream will be flushed after the model is writen.
+     *
+     * @param w the character stream
+     * @param op the code model
+     */
+    public static void writeTo(Writer w, Op op) {
+        OpWriter ow = new OpWriter(w);
+        ow.writeOp(op);
+        try {
+            w.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Writes a code model (an operation) to the character stream.
+     * <p>
+     * The character stream will be flushed after the model is writen.
+     *
+     * @param w the character stream
+     * @param op the code model
+     * @param options the writer options
+     */
+    public static void writeTo(Writer w, Op op, Option... options) {
+        OpWriter ow = new OpWriter(w, options);
+        ow.writeOp(op);
+        try {
+            // @@@ Is this needed?
+            w.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Writes a code model (an operation) to a string.
+     *
+     * @param op the code model
+     */
+    public static String toText(Op op) {
+        StringWriter w = new StringWriter();
+        writeTo(w, op);
+        return w.toString();
+    }
+
+    /**
+     * Writes a code model (an operation) to a string.
+     *
+     * @param op the code model
+     * @param options the writer options
+     */
+    public static String toText(Op op, OpWriter.Option... options) {
+        StringWriter w = new StringWriter();
+        writeTo(w, op, options);
+        return w.toString();
+    }
+
+    /**
+     * An option that affects the writing operations.
+     */
+    public sealed interface Option {
+    }
+
+    /**
+     * An option describing the function to use for naming code items.
+     */
+    public sealed interface CodeItemNamerOption extends Option
+            permits NamerOptionImpl {
+
+        static CodeItemNamerOption of(Function<CodeItem, String> named) {
+            return new NamerOptionImpl(named);
+        }
+
+        static CodeItemNamerOption defaultValue() {
+            return of(new GlobalValueBlockNaming());
+        }
+
+        Function<CodeItem, String> namer();
+    }
+    private record NamerOptionImpl(Function<CodeItem, String> namer) implements CodeItemNamerOption {
+    }
+
+    /**
+     * An option describing whether location information should be written or dropped.
+     */
+    public enum LocationOption implements Option {
+        /** Writes location */
+        WRITE_LOCATION,
+        /** Drops location */
+        DROP_LOCATION;
+
+        public static LocationOption defaultValue() {
+            return WRITE_LOCATION;
+        }
+    }
+
+    /**
+     * An option describing whether an operation's descendant code elements should be written or dropped.
+     */
+    public enum OpDescendantsOption implements Option {
+        /** Writes descendants of an operation, if any */
+        WRITE_DESCENDANTS,
+        /** Drops descendants of an operation, if any */
+        DROP_DESCENDANTS;
+
+        public static OpDescendantsOption defaultValue() {
+            return WRITE_DESCENDANTS;
+        }
+    }
+
+    /**
+     * An option describing whether an operation's result be written or dropped if its type is void.
+     */
+    public enum VoidOpResultOption implements Option {
+        /** Writes void operation result */
+        WRITE_VOID,
+        /** Drops void operation result */
+        DROP_VOID;
+
+        public static VoidOpResultOption defaultValue() {
+            return DROP_VOID;
+        }
+    }
+
+    final Function<CodeItem, String> namer;
+    final IndentWriter w;
+    final boolean dropLocation;
+    final boolean dropOpDescendants;
+    final boolean writeVoidOpResult;
+
+    /**
+     * Creates a writer of code models (operations) to their textual form.
+     *
+     * @param w the character stream writer to write the textual form.
+     */
+    public OpWriter(Writer w) {
+        this.w = new IndentWriter(w);
+        this.namer = new GlobalValueBlockNaming();
+        this.dropLocation = false;
+        this.dropOpDescendants = false;
+        this.writeVoidOpResult = false;
+    }
+
+    /**
+     * Creates a writer of code models (operations) to their textual form.
+     *
+     * @param w the character stream writer to write the textual form.
+     * @param options the writer options
+     */
+    public OpWriter(Writer w, Option... options) {
+        Function<CodeItem, String> namer = null;
+        boolean dropLocation = false;
+        boolean dropOpDescendants = false;
+        boolean writeVoidOpResult = false;
+        for (Option option : options) {
+            switch (option) {
+                case CodeItemNamerOption namerOption -> {
+                    namer = namerOption.namer();
+                }
+                case LocationOption locationOption -> {
+                    dropLocation = locationOption ==
+                            LocationOption.DROP_LOCATION;
+                }
+                case OpDescendantsOption opDescendantsOption -> {
+                    dropOpDescendants = opDescendantsOption ==
+                            OpDescendantsOption.DROP_DESCENDANTS;
+                }
+                case VoidOpResultOption voidOpResultOption -> {
+                    writeVoidOpResult = voidOpResultOption == VoidOpResultOption.WRITE_VOID;
+                }
+            }
+        }
+
+        this.w = new IndentWriter(w);
+        this.namer = (namer == null) ? new GlobalValueBlockNaming() : namer;
+        this.dropLocation = dropLocation;
+        this.dropOpDescendants = dropOpDescendants;
+        this.writeVoidOpResult = writeVoidOpResult;
+    }
+
+    /**
+     * {@return the function that names blocks and values.}
+     */
+    public Function<CodeItem, String> namer() {
+        return namer;
+    }
+
+    /**
+     * Writes a code model, an operation, to the character stream.
+     *
+     * @param op the code model
+     */
+    public void writeOp(Op op) {
+        if (op.parent() != null) {
+            Op.Result opr = op.result();
+            if (writeVoidOpResult || !opr.type().equals(JavaType.VOID)) {
+                writeValueDeclaration(opr);
+                write(" = ");
+            }
+        }
+        write(op.opName());
+
+        if (!op.operands().isEmpty()) {
+            write(" ");
+            writeSpaceSeparatedList(op.operands(), this::writeValueUse);
+        }
+
+        if (!op.successors().isEmpty()) {
+            write(" ");
+            writeSpaceSeparatedList(op.successors(), this::writeSuccessor);
+        }
+
+        Map<String, Object> attributes = op instanceof ExternalizableOp exop ? exop.attributes() : Map.of();
+        if (dropLocation && !attributes.isEmpty() &&
+                attributes.containsKey(ExternalizableOp.ATTRIBUTE_LOCATION)) {
+            attributes = new HashMap<>(attributes);
+            attributes.remove(ExternalizableOp.ATTRIBUTE_LOCATION);
+        }
+        if (!attributes.isEmpty()) {
+            write(" ");
+            writeSpaceSeparatedList(attributes.entrySet(), e -> writeAttribute(e.getKey(), e.getValue()));
+        }
+
+        if (!dropOpDescendants && !op.bodies().isEmpty()) {
+            int nBodies = op.bodies().size();
+            if (nBodies == 1) {
+                write(" ");
+            } else {
+                write("\n");
+                w.in();
+                w.in();
+            }
+            boolean first = true;
+            for (Body body : op.bodies()) {
+                if (!first) {
+                    write("\n");
+                }
+                writeBody(body);
+                first = false;
+            }
+            if (nBodies > 1) {
+                w.out();
+                w.out();
+            }
+        }
+
+        write(";");
+    }
+
+    void writeSuccessor(Block.Reference successor) {
+        writeBlockName(successor.targetBlock());
+        if (!successor.arguments().isEmpty()) {
+            write("(");
+            writeCommaSeparatedList(successor.arguments(), this::writeValueUse);
+            write(")");
+        }
+    }
+
+    void writeAttribute(String name, Object value) {
+        write("@");
+        if (!name.isEmpty()) {
+            write(name);
+            write("=");
+        }
+        write(AttributeMapper.toString(value));
+    }
+
+    void writeBody(Body body) {
+        Block eb = body.entryBlock();
+        write("(");
+        writeCommaSeparatedList(eb.parameters(), this::writeValueDeclaration);
+        write(")");
+        writeType(body.bodyType().returnType());
+        write(" -> {\n");
+        w.in();
+        for (Block b : body.blocks()) {
+            if (!b.isEntryBlock()) {
+                write("\n");
+            }
+            writeBlock(b);
+        }
+        w.out();
+        write("}");
+    }
+
+    void writeBlock(Block block) {
+        if (!block.isEntryBlock()) {
+            writeBlockName(block);
+            if (!block.parameters().isEmpty()) {
+                write("(");
+                writeCommaSeparatedList(block.parameters(), this::writeValueDeclaration);
+                write(")");
+            }
+            write(":\n");
+        }
+        w.in();
+        for (Op op : block.ops()) {
+            writeOp(op);
+            write("\n");
+        }
+        w.out();
+    }
+
+    void writeBlockName(Block b) {
+        write("^");
+        write(namer.apply(b));
+    }
+
+    void writeValueUse(Value v) {
+        write("%");
+        write(namer.apply(v));
+    }
+
+    void writeValueDeclaration(Value v) {
+        write("%");
+        write(namer.apply(v));
+        write(" : ");
+        writeType(v.type());
+    }
+
+    <T> void writeSpaceSeparatedList(Iterable<T> l, Consumer<T> c) {
+        writeSeparatedList(" ", l, c);
+    }
+
+    <T> void writeCommaSeparatedList(Iterable<T> l, Consumer<T> c) {
+        writeSeparatedList(", ", l, c);
+    }
+
+    <T> void writeSeparatedList(String separator, Iterable<T> l, Consumer<T> c) {
+        boolean first = true;
+        for (T t : l) {
+            if (!first) {
+                write(separator);
+            }
+            c.accept(t);
+            first = false;
+        }
+    }
+
+    void writeType(TypeElement te) {
+        write(te.externalize().toString());
+    }
+
+    void write(String s) {
+        try {
+            w.write(s);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff a/src/java.base/share/classes/java/lang/reflect/code/writer/package-info.java b/src/java.base/share/classes/java/lang/reflect/code/writer/package-info.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/reflect/code/writer/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Functionality for writing and storing code models.
+ */
+package java.lang.reflect.code.writer;
diff a/src/java.base/share/classes/java/lang/runtime/CodeReflection.java b/src/java.base/share/classes/java/lang/runtime/CodeReflection.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/runtime/CodeReflection.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.runtime;
+
+import java.lang.annotation.*;
+
+/**
+ * Enables code reflection for a method annotated with this annotation.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CodeReflection {
+}
diff a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java
--- a/src/java.base/share/classes/module-info.java
+++ b/src/java.base/share/classes/module-info.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -88,10 +88,18 @@
     exports java.lang.foreign;
     exports java.lang.invoke;
     exports java.lang.module;
     exports java.lang.ref;
     exports java.lang.reflect;
+    exports java.lang.reflect.code;
+    exports java.lang.reflect.code.analysis;
+    exports java.lang.reflect.code.bytecode;
+    exports java.lang.reflect.code.interpreter;
+    exports java.lang.reflect.code.op;
+    exports java.lang.reflect.code.parser;
+    exports java.lang.reflect.code.type;
+    exports java.lang.reflect.code.writer;
     exports java.lang.runtime;
     exports java.math;
     exports java.net;
     exports java.net.spi;
     exports java.nio;
@@ -372,10 +380,11 @@
         java.desktop,
         java.logging,
         java.prefs;
     exports sun.util.resources to
         jdk.localedata;
+    exports java.lang.reflect.code.type.impl;
 
     // the service types defined by the APIs in this module
 
     uses java.lang.System.LoggerFinder;
     uses java.net.ContentHandlerFactory;
diff a/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/WildcardTypeImpl.java b/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/WildcardTypeImpl.java
--- a/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/WildcardTypeImpl.java
+++ b/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/WildcardTypeImpl.java
@@ -51,12 +51,12 @@
 
     /** The lower bounds.  Lazily converted from FieldTypeSignature[] to Type[]. */
     private volatile Object[] lowerBounds;
 
     // constructor is private to enforce access through static factory
-    private WildcardTypeImpl(FieldTypeSignature[] ubs,
-                             FieldTypeSignature[] lbs,
+    private WildcardTypeImpl(Object[] ubs,
+                             Object[] lbs,
                              GenericsFactory f) {
         super(f);
         upperBounds = ubs;
         lowerBounds = lbs;
     }
@@ -75,10 +75,21 @@
                                         FieldTypeSignature[] lbs,
                                         GenericsFactory f) {
         return new WildcardTypeImpl(ubs, lbs, f);
     }
 
+    /**
+     * Eager factory method.
+     * @param ubs - an array of types representing the upper bounds
+     * @param lbs - an array of types representing the lower bounds
+     * @return a wild card type with the requested bounds
+     */
+    public static WildcardTypeImpl make(Type[] ubs,
+                                        Type[] lbs) {
+        return new WildcardTypeImpl(ubs, lbs, null);
+    }
+
     /**
      * Returns an array of {@code Type} objects representing the upper
      * bound(s) of this type variable.  Note that if no upper bound is
      * explicitly declared, the upper bound is {@code Object}.
      *
diff a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
--- a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
@@ -26,10 +26,11 @@
 package javax.lang.model.util;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.LinkedHashSet;
 import java.util.Objects;
 
 import javax.lang.model.AnnotatedConstruct;
@@ -966,6 +967,22 @@
      * @since 18
      */
     default javax.tools.JavaFileObject getFileObjectOf(Element e) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Returns the code model of provided executable element (if any).
+     * <p>
+     * If the executable element has a code model then it will be an instance of
+     * {@code java.lang.reflect.code.op.CoreOps.FuncOp}.
+     * Note: due to circular dependencies we cannot refer to the type explicitly.
+     *
+     * @implSpec The default implementation unconditionally returns an empty optional.
+     * @param e the executable element.
+     * @return the code model of the provided executable element (if any).
+     * @since 99
+     */
+    default Optional<Object> getBody(ExecutableElement e) {
+        return Optional.empty();
+    }
 }
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotGenerator.java b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotGenerator.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotGenerator.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.dot;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.Value;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class DotGenerator {
+
+    final Writer w;
+
+    interface NodeProperty {
+        String key();
+
+        String value();
+
+        default String toText() {
+            return key() + "=" + value();
+        }
+    }
+
+    static NodeProperty property(String key, String value) {
+        return new NodeProperty() {
+            @Override
+            public String key() {
+                return key;
+            }
+
+            @Override
+            public String value() {
+                return value;
+            }
+        };
+    }
+
+    static String properties(List<? extends NodeProperty> properties) {
+        return properties.stream().map(NodeProperty::toText).collect(Collectors.joining(" ", "[", "]"));
+    }
+
+    static NodeProperty label(String name) {
+        return new NodeProperty() {
+            @Override
+            public String key() {
+                return "label";
+            }
+
+            @Override
+            public String value() {
+                return "\"" + name + "\"";
+            }
+        };
+    }
+
+    enum Shape implements NodeProperty {
+        BOX("box"),
+        ELLIPSE("ellipse"),
+        HEXAGONE("hexagon"),
+        INVERTED_TRAPEZIUM("invtrapezium");
+
+        final String value;
+
+        Shape(String value) {
+            this.value = value;
+        }
+
+
+        @Override
+        public String key() {
+            return "shape";
+        }
+
+        @Override
+        public String value() {
+            return value;
+        }
+    }
+
+    private DotGenerator(Writer w) {
+        this.w = w;
+    }
+
+    void digraph() {
+        write("digraph G {\n");
+    }
+
+    void node(Object o, String properties) {
+        write("%s %s;\n", System.identityHashCode(o), properties);
+    }
+
+    void node(Object o, NodeProperty... properties) {
+        node(o, List.of(properties));
+    }
+
+    void node(Object o, List<? extends NodeProperty> properties) {
+        node(o, properties(properties));
+    }
+
+    void edge(Object from, Object to) {
+        write("%s -> %s;\n", System.identityHashCode(from), System.identityHashCode(to));
+    }
+
+    void write(String format, Object... args) {
+        write(w, format, args);
+    }
+
+    void end() {
+        write(w, "}\n");
+        try {
+            w.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    static void write(Writer w, String format, Object... args) {
+        try {
+            w.write(String.format(format, args));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+
+    /**
+     * Generates the representation tree for a given operation.
+     *
+     * @param op the operation
+     * @param w  the writer to write the sr.dot file
+     */
+    public static void representationTree(Op op, Writer w) {
+        DotGenerator dg = new DotGenerator(w);
+
+        dg.digraph();
+
+        op.traverse(null, (t, codeElement) -> switch (codeElement) {
+            case Body b -> {
+                dg.node(b, label(""), Shape.HEXAGONE, property("style", "filled"));
+
+                dg.edge(b.parentOp(), b);
+
+                yield null;
+            }
+            case Block b -> {
+                dg.node(b, label(""), Shape.BOX);
+
+                dg.edge(b.parentBody(), b);
+
+                yield null;
+            }
+            case Op o -> {
+                List<NodeProperty> ps;
+                if (o instanceof Op.Terminating) {
+                    ps = List.of(label(o.opName()), Shape.ELLIPSE, property("style", "filled"));
+                } else {
+                    ps = List.of(label(o.opName()), Shape.ELLIPSE);
+                }
+                dg.node(o, ps);
+                if (o.parentBlock() != null) {
+                    dg.edge(o.parentBlock(), o);
+                }
+
+                yield null;
+            }
+        });
+
+        dg.end();
+    }
+
+    /**
+     * Generates a body graph (CFG) for a given body.
+     *
+     * @param body the body
+     * @param w    the writer to write the sr.dot file
+     */
+    public static void bodyGraph(Body body, Writer w) {
+        DotGenerator dg = new DotGenerator(w);
+
+        dg.digraph();
+
+        Block eb = body.entryBlock();
+        Deque<Block> stack = new ArrayDeque<>();
+        Set<Block> visited = new HashSet<>();
+        stack.push(eb);
+        while (!stack.isEmpty()) {
+            Block b = stack.pop();
+            if (!visited.add(b)) {
+                continue;
+            }
+
+            dg.node(b, label(""), Shape.BOX);
+
+            List<Block.Reference> successors = b.terminatingOp().successors();
+            for (Block.Reference s : successors) {
+                dg.edge(b, s.targetBlock());
+
+                stack.push(s.targetBlock());
+            }
+        }
+
+        dg.end();
+    }
+
+    /**
+     * Generates a body dominator tree for a given body.
+     *
+     * @param body the body
+     * @param w    the writer to write the sr.dot file
+     */
+    public static void bodyDominatorTree(Body body, Writer w) {
+        DotGenerator dg = new DotGenerator(w);
+
+        dg.digraph();
+
+        Block eb = body.entryBlock();
+        Map<Block, Block> idoms = body.immediateDominators();
+
+        for (Map.Entry<Block, Block> e : idoms.entrySet()) {
+            Block child = e.getKey();
+            Block parent = e.getValue();
+
+            dg.node(child, label(""), Shape.BOX);
+
+            if (child != eb) {
+                dg.edge(parent, child);
+            }
+        }
+
+        dg.end();
+    }
+
+    /**
+     * Generates a body dominator tree for a given body, with the dominance
+     * frontier set presented for each block.
+     * <p>
+     * The dominance frontier of a block, b say, is the set of blocks where the b's
+     * dominance stops.
+     *
+     * @param body the body
+     * @param w    the writer to write the sr.dot file
+     */
+    public static void bodyDominanceFrontierTree(Body body, Writer w) {
+        DotGenerator dg = new DotGenerator(w);
+
+        dg.digraph();
+
+        Block eb = body.entryBlock();
+        Map<Block, Block> idoms = body.immediateDominators();
+        Map<Block, Set<Block>> df = body.dominanceFrontier();
+
+        for (Map.Entry<Block, Block> e : idoms.entrySet()) {
+            Block child = e.getKey();
+            Block parent = e.getValue();
+
+            Set<Block> frontiers = df.get(child);
+
+            String s = frontiers == null || frontiers.isEmpty()
+                    ? "[-]"
+                    : frontiers.stream().map(b -> String.valueOf(b.index())).collect(Collectors.joining(",", "[", "]"));
+            dg.node(child, label("" + "\n" + s), Shape.BOX);
+
+            if (child != eb) {
+                dg.edge(parent, child);
+            }
+        }
+
+        dg.end();
+    }
+
+    /**
+     * Generates a body data dependence dag for a given body.
+     *
+     * @param body  the body
+     * @param names a map of block arguments to names
+     * @param w     the writer to write the sr.dot file
+     */
+    public static void dataDependenceGraph(Body body, Map<Block.Parameter, String> names, Writer w) {
+        dataDependenceGraph(body, names, false, w);
+    }
+
+    /**
+     * Generates a body data dependence graph for a given body.
+     *
+     * @param body              the body
+     * @param names             a map of block arguments to names
+     * @param traverseblockArgs true if a graph is produced, otherwise a DAG
+     * @param w                 the writer to write the sr.dot file
+     */
+    public static void dataDependenceGraph(Body body, Map<Block.Parameter, String> names,
+                                           boolean traverseblockArgs, Writer w) {
+        DotGenerator dg = new DotGenerator(w);
+
+        dg.digraph();
+
+        record Edge(Value from, Value to) {
+        }
+
+        Set<Value> visted = new HashSet<>();
+        Set<Edge> vistedEdges = new HashSet<>();
+        Deque<Value> stack = new ArrayDeque<>(getValues(body));
+        while (!stack.isEmpty()) {
+            Value v = stack.pop();
+            if (!visted.add(v)) {
+                continue;
+            }
+
+            if (v instanceof Op.Result or) {
+                if (!or.op().operands().isEmpty() || !(or.op() instanceof Op.Terminating)) {
+                    dg.node(v, label(or.op().opName()), Shape.INVERTED_TRAPEZIUM);
+                }
+            } else if (v instanceof Block.Parameter ba) {
+                String n = names.get(v);
+                if (n != null) {
+                    dg.node(v, label(n), Shape.INVERTED_TRAPEZIUM,
+                            property("style", "filled"));
+                } else {
+                    Block b = ba.declaringBlock();
+                    dg.node(v, label("(" + b.parameters().indexOf(ba) + ")"), Shape.BOX,
+                            property("style", "filled"));
+                }
+            }
+
+            Set<Op.Result> uses = v.uses();
+            stack.addAll(uses);
+            for (Op.Result use : uses) {
+                if (traverseblockArgs && use.op() instanceof Op.Terminating) {
+                    for (Block.Reference s : use.op().successors()) {
+                        int i = s.arguments().indexOf(v);
+                        if (i != -1) {
+                            Block.Parameter ba = s.targetBlock().parameters().get(i);
+
+                            if (vistedEdges.add(new Edge(v, ba))) {
+                                dg.edge(v, ba);
+                            }
+                            stack.add(ba);
+                        }
+                    }
+                }
+
+                if (use.op().operands().contains(v)) {
+                    if (vistedEdges.add(new Edge(v, use))) {
+                        dg.edge(v, use);
+                    }
+                }
+            }
+        }
+
+        dg.end();
+    }
+
+    static List<Value> getValues(Body r) {
+        return r.traverse(new ArrayList<>(), (values, codeElement) -> switch (codeElement) {
+            case Block b -> {
+                values.addAll(b.parameters());
+                yield values;
+            }
+            case Op o -> {
+                values.add(o.result());
+                yield values;
+            }
+            default -> values;
+        });
+    }
+
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotRenderer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotRenderer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotRenderer.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.dot;
+
+import jdk.code.tools.renderer.CommonRenderer;
+
+import java.io.Writer;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.util.*;
+
+/**
+ * Created by gfrost
+ * http://www.graphviz.org/Documentation/dotguide.pdf
+ */
+public class DotRenderer extends CommonRenderer<DotRenderer> {
+    public DotRenderer() {
+        super();
+    }
+
+    static String sysident(Object o) {
+        return Integer.toString(System.identityHashCode(o));
+    }
+
+    DotRenderer end() {
+        return out().cbrace().nl();
+    }
+
+    public DotRenderer start(String name) {
+        return append("digraph").space().append(name).obrace().in().nl();
+    }
+
+    public DotRenderer rankdir(String s) {
+        return append("rankdir").equal().append(s).semicolon().nl();
+    }
+
+    public DotRenderer concentrate() {
+        return append("concentrate=true").nl();
+    }
+
+    public DotRenderer newrank() {
+        return append("newrank=true").nl();
+    }
+
+    public DotRenderer edgesFirst() {
+        return append("outputorder=edgesfirst").nl();
+    }
+
+
+    public <T extends CommonRenderer<T>> DotRenderer graph(NestedRendererSAM<GraphRenderer> nb) {
+
+        nb.build(new GraphRenderer(this)).end();
+        return self();
+    }
+
+
+    public static class GraphRenderer extends CommonRenderer<GraphRenderer> {
+        public GraphRenderer(DotRenderer dotRenderer) {
+            super(dotRenderer);
+        }
+
+        GraphRenderer end() {
+            return self();
+        }
+
+        public GraphRenderer node(String name, String shape, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, shape)).end();
+            return self();
+        }
+
+        public GraphRenderer node(String name, String shape) {
+            return node(name, shape, (n) -> n);
+        }
+
+        public GraphRenderer record(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "record")).end();
+            return self();
+        }
+
+        public GraphRenderer record(String name) {
+            return record(name, (n) -> n);
+        }
+
+        public GraphRenderer ellipse(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "ellipse")).end();
+            return self();
+        }
+
+        public GraphRenderer ellipse(String name) {
+            return ellipse(name, (n) -> n);
+        }
+
+        public GraphRenderer ellipse(Object o, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, sysident(o), "ellipse")).end();
+            return self();
+        }
+
+        public GraphRenderer ellipse(Object o) {
+            return ellipse(o, (n) -> n);
+        }
+
+        public GraphRenderer circle(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "circle")).end();
+            return self();
+        }
+
+        public GraphRenderer circle(String name) {
+            return circle(name, (n) -> n);
+        }
+
+        public GraphRenderer invertedtrapezium(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "invtrapezium")).end();
+            return self();
+        }
+
+        public GraphRenderer invertedtrapezium(String name) {
+            return invertedtrapezium(name, (n) -> n);
+        }
+
+        public GraphRenderer invertedtrapezium(Object o, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, sysident(o), "invtrapezium")).end();
+            return self();
+        }
+
+        public GraphRenderer invertedtrapezium(Object o) {
+            return invertedtrapezium(o, (n) -> n);
+        }
+
+        public GraphRenderer box(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "box")).end();
+            return self();
+        }
+
+        public GraphRenderer box(String name) {
+            return box(name, (n) -> n);
+        }
+
+        public GraphRenderer box(Object o, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, sysident(o), "box")).end();
+            return self();
+        }
+
+        public GraphRenderer box(Object o) {
+            return box(o, (n) -> n);
+        }
+
+        public GraphRenderer hexagon(String name, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, name, "hexagon")).end();
+            return self();
+        }
+
+        public GraphRenderer hexagon(String name) {
+            return hexagon(name, (n) -> n);
+        }
+
+        public GraphRenderer hexagon(Object o, NestedRendererSAM<NodeRenderer> sam) {
+            sam.build(new NodeRenderer(this, sysident(o), "hexagon")).end();
+            return self();
+        }
+
+        public GraphRenderer hexagon(Object o) {
+            return hexagon(o, (n) -> n);
+        }
+
+        public static class NodeRenderer extends CommonRenderer<NodeRenderer> {
+            NodeRenderer(GraphRenderer graphRenderer, String name, String shape) {
+                super(graphRenderer);
+                append(name).osbrace().append("shape").equal().oquot().append(shape).cquot().space();
+                // append(name).osbrace().append("shape").equal().append(shape).space();
+                //   append(name).osbrace();//.append("shape").equal().append(shape).space();
+            }
+
+            public NodeRenderer end() {
+                return csbrace().semicolon().nl();
+            }
+
+            NodeRenderer label(String label, NestedRendererSAM<LabelRenderer> sam) {
+                LabelRenderer renderer = new LabelRenderer(this, label);
+                sam.build(renderer).end();
+                return self();
+            }
+
+            NodeRenderer label(NestedRendererSAM<LabelRenderer> sam) {
+                LabelRenderer renderer = new LabelRenderer(this, "");
+                sam.build(renderer).end();
+                return self();
+            }
+
+            NodeRenderer label(String label) {
+                return label(label, (l) -> l);
+            }
+
+
+            public NodeRenderer color(String color) {
+                return append("color").equal().oquot().append(color).cquot().space();
+            }
+
+            public NodeRenderer style(String style) {
+                return append("style").equal().oquot().append(style).cquot().space();
+            }
+
+            public static class LabelRenderer extends CommonRenderer<LabelRenderer> {
+                int count = 0;
+
+                LabelRenderer(NodeRenderer nodeRenderer, String label) {
+                    super(nodeRenderer);
+                    append("label").equal().oquot().append(label);
+                }
+
+                public LabelRenderer end() {
+                    return cquot().space();
+                }
+
+                LabelRenderer port(String label, String text) {
+                    if (count > 0) {
+                        pipe();
+                    }
+                    count++;
+                    return lt().append(label).gt().append(text);
+                }
+
+                LabelRenderer label(String label, String text) {
+                    if (count > 0) {
+                        pipe();
+                    }
+                    count++;
+                    return append(text);
+                }
+
+                LabelRenderer box(NestedRendererSAM<BoxRenderer> sam) {
+                    sam.build(new BoxRenderer(this)).end();
+                    count = 0;
+                    return self();
+                }
+
+                static class BoxRenderer extends CommonRenderer<BoxRenderer> {
+                    int count = 0;
+
+                    BoxRenderer(LabelRenderer labelRenderer) {
+                        super(labelRenderer);
+                        pipe().obrace();
+                    }
+
+                    BoxRenderer(BoxRenderer boxRenderer) {
+                        super(boxRenderer);
+                        pipe().obrace();
+                    }
+
+                    BoxRenderer end() {
+                        return cbrace().pipe();
+                    }
+
+                    BoxRenderer port(String label, String text) {
+                        if (count > 0) {
+                            pipe();
+                        }
+                        count++;
+                        return lt().append(label).gt().append(text);
+                    }
+
+                    BoxRenderer label(String text) {
+                        if (count > 0) {
+                            pipe();
+                        }
+                        count++;
+                        return append(text);
+                    }
+
+                    BoxRenderer box(NestedRendererSAM<BoxRenderer> sam) {
+                        sam.build(new BoxRenderer(this)).end();
+                        count = 0;
+                        return self();
+                    }
+                }
+            }
+        }
+
+        public GraphRenderer edge(String from, String to, NestedRendererSAM<EdgeRenderer> sam) {
+            sam.build(new EdgeRenderer(this, from, to)).end();
+            return self();
+        }
+
+        public GraphRenderer edge(String from, String to) {
+            return edge(from, to, (n) -> n);
+        }
+
+        public GraphRenderer edge(Object from, Object to, NestedRendererSAM<EdgeRenderer> sam) {
+            sam.build(new EdgeRenderer(this, sysident(from), sysident(to))).end();
+            return self();
+        }
+
+        public GraphRenderer edge(Object from, Object to) {
+            return edge(from, to, (n) -> n);
+        }
+
+        public static class EdgeRenderer extends CommonRenderer<EdgeRenderer> {
+            EdgeRenderer(GraphRenderer graphRenderer, String from, String to) {
+                super(graphRenderer);
+                append(from).rarrow().append(to).osbrace();
+            }
+
+            public EdgeRenderer end() {
+                return csbrace().semicolon().nl().self();
+            }
+
+            EdgeRenderer label(String label, NestedRendererSAM<LabelRenderer> sam) {
+                LabelRenderer renderer = new LabelRenderer(this, label);
+                sam.build(renderer).end();
+                return self();
+            }
+
+
+            EdgeRenderer label(String label) {
+                return label(label, (l) -> l);
+            }
+
+            public static class LabelRenderer extends CommonRenderer<LabelRenderer> {
+
+                LabelRenderer(EdgeRenderer edgeRenderer, String label) {
+                    super(edgeRenderer);
+                    append("label").equal().oquot().append(label);
+                }
+
+                public LabelRenderer end() {
+                    return cquot().space();
+                }
+
+            }
+        }
+    }
+
+    public static void representationTree(Op op, Writer w) {
+        new DotRenderer().writer(w).start("g").graph((g) -> {
+            op.traverse(null, (t, codeElement) -> switch (codeElement) {
+                case Body b -> {
+                    g.hexagon(b, (n) -> n.label("").style("filled"));
+                    g.edge(b.parentOp(), b);
+                    yield null;
+                }
+                case Block b -> {
+                    g.box(b, (n) -> n.label(""));
+                    g.edge(b.parentBody(), b);
+                    yield null;
+                }
+                case Op o -> {
+                    if (o instanceof Op.Terminating) {
+                        g.ellipse(o, (n) -> n.label(o.opName()).style("filled"));
+                    } else {
+                        g.ellipse(o, (n) -> n.label(o.opName()));
+                    }
+                    if (o.parentBlock() != null) {
+                        g.edge(o.parentBlock(), o);
+                    }
+                    yield null;
+                }
+            });
+            return g;
+        }).end();
+
+    }
+
+    public static void bodyGraph(Body body, Writer w) {
+        Block eb = body.entryBlock();
+        Deque<Block> stack = new ArrayDeque<>();
+        Set<Block> visited = new HashSet<>();
+        stack.push(eb);
+        new DotRenderer().writer(w).start("g").graph((g) -> {
+            while (!stack.isEmpty()) {
+                Block b = stack.pop();
+                if (!visited.add(b)) {
+                    continue;
+                }
+
+                g.box(b, (box) -> box.label(""));
+
+                List<Block.Reference> successors = b.terminatingOp().successors();
+                for (Block.Reference s : successors) {
+                    g.edge(b, s.targetBlock());
+
+                    stack.push(s.targetBlock());
+                }
+            }
+            return g;
+        }).end();
+
+    }
+
+    public static void bodyDominatorTree(Body body, Writer w) {
+        Block eb = body.entryBlock();
+        Map<Block, Block> idoms = body.immediateDominators();
+
+        new DotRenderer().writer(w).start("g").graph((g) -> {
+            for (Map.Entry<Block, Block> e : idoms.entrySet()) {
+                Block child = e.getKey();
+                Block parent = e.getValue();
+
+                g.box(child, (b) -> b.label(""));
+
+                if (child != eb) {
+                    g.edge(parent, child);
+                }
+            }
+            return g;
+        }).end();
+    }
+
+    /**
+     * Generates a body data dependence graph for a given body.
+     *
+     * @param body              the body
+     * @param names             a map of block arguments to names
+     * @param traverseblockArgs true if a graph is produced, otherwise a DAG
+     * @param w                 the writer to write the sr.dot file
+     */
+    public static void dataDependenceGraph(Body body, Map<Block.Parameter, String> names,
+                                           boolean traverseblockArgs, Writer w) {
+
+
+        record Edge(Value from, Value to) {
+        }
+        new DotRenderer().writer(w).start("SR").graph((g) -> {
+            Set<Value> visted = new HashSet<>();
+            Set<Edge> vistedEdges = new HashSet<>();
+            Deque<Value> stack = new ArrayDeque<>(getValues(body));
+            while (!stack.isEmpty()) {
+                Value v = stack.pop();
+                if (!visted.add(v)) {
+                    continue;
+                }
+
+                if (v instanceof Op.Result or) {
+                    if (!or.op().operands().isEmpty() || !(or.op() instanceof Op.Terminating)) {
+                        g.invertedtrapezium(v, (node) -> node.label(or.op().opName()));
+                    }
+                } else if (v instanceof Block.Parameter ba) {
+                    String n = names.get(v);
+                    if (n != null) {
+                        g.invertedtrapezium(v, (node) -> node.label(n).style("filled"));
+                    } else {
+                        Block b = ba.declaringBlock();
+
+                        g.box(v, (node) -> node.label("(" + b.parameters().indexOf(ba) + ")").style("filled"));
+                    }
+                }
+
+                Set<Op.Result> uses = v.uses();
+                stack.addAll(uses);
+                for (Op.Result use : uses) {
+                    if (traverseblockArgs && use.op() instanceof Op.Terminating) {
+                        for (Block.Reference s : use.op().successors()) {
+                            int i = s.arguments().indexOf(v);
+                            if (i != -1) {
+                                Block.Parameter ba = s.targetBlock().parameters().get(i);
+
+                                if (vistedEdges.add(new Edge(v, ba))) {
+                                    g.edge(v, ba);
+                                }
+                                stack.add(ba);
+                            }
+                        }
+                    }
+
+                    if (use.op().operands().contains(v)) {
+                        if (vistedEdges.add(new Edge(v, use))) {
+                            g.edge(v, use);
+                        }
+                    }
+                }
+            }
+            return g;
+        }).end();
+    }
+
+    private static List<Value> getValues(Body r) {
+        return r.traverse(new ArrayList<>(), (values, codeElement) -> switch (codeElement) {
+            case Block b -> {
+                values.addAll(b.parameters());
+                yield values;
+            }
+            case Op op -> {
+                values.add(op.result());
+                yield values;
+            }
+            default -> values;
+        });
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotViewer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotViewer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/dot/DotViewer.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.dot;
+
+import jdk.code.tools.renderer.ProcessRunner;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by gfrost
+ * http://graphviz.org/Documentation.php
+ * http://graphviz.org/content/arrow-shapes
+ * http://graphviz.org/content/rootNode-shapes
+ * http://graphviz.org/content/attrs
+ * http://www.graphviz.org/Documentation/dotguide.pdf
+ */
+public final class DotViewer {
+    private DotViewer() {
+    }
+
+    // Look at Path.of() ..... ;)  I think I may have reimplemented it
+    static String[] svgViewers = new String[]{
+            System.getenv("SVG_VIEWER_PATH"),
+            System.getProperty("SVG_VIEWER_PATH"),
+            "/usr/bin/google-chrome",
+            "/snap/chromium/1466/usr/lib/chromium-browser/chrome",
+            "/usr/bin/gpicview",
+            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
+            "/usr/bin/xdg-open"
+    };
+    static String[] dotLocations = new String[]{
+            System.getenv("DOT_PATH"),
+            System.getProperty("DOT_PATH"),
+            "/usr/bin/dot",
+            "/usr/local/bin/dot" // mac
+    };
+
+    static String getLocation(String[] possibles) {
+        for (String s : possibles) {
+            if (s != null && !s.equals("")) {
+                File file = new File(s);
+                if (file.exists() && file.canExecute()) {
+                    return s;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static void view(String dotSource) {
+
+        String dot = getLocation(dotLocations);
+        System.out.println(dotSource);
+        String svgViewer = getLocation(svgViewers);
+
+        if (dot != null && svgViewer != null) {
+            try {
+                File tempFile = File.createTempFile("ast", ".svg");
+                ProcessRunner.run(dot)
+                        .opt("-Tsvg")
+                        .opt("-o").file(tempFile)
+                        .temp("ast", "dot", dotSource)
+                        .go(false);
+                ProcessRunner.run(svgViewer)
+                        .file(tempFile)
+                        .go(false);
+
+            } catch (IOException ioe) {
+                ioe.printStackTrace();
+            }
+        }
+
+    }
+
+    public static void viewNoWait(String dotSource) {
+        String dot = getLocation(dotLocations);
+        String svgViewer = getLocation(svgViewers);
+
+        if (dot != null && svgViewer != null) {
+            try {
+                File tempDotFile = File.createTempFile("ast", "dot");
+                FileWriter tempDotFileWriter = new FileWriter(tempDotFile);
+                tempDotFileWriter.append(dotSource);
+                tempDotFileWriter.close();
+                File tempPngFile = File.createTempFile("ast", "svg");
+                List<String> dotCommand = new ArrayList<>();
+                dotCommand.add(dot);
+                dotCommand.add("-Tsvg");
+                dotCommand.add("-o");
+                dotCommand.add(tempPngFile.getAbsolutePath());
+                dotCommand.add(tempDotFile.getAbsolutePath());
+
+                ProcessBuilder dotBuilder = new ProcessBuilder(dotCommand);
+                Process dotProcess = dotBuilder.start();
+                dotProcess.waitFor();
+
+                List<String> fehCommand = new ArrayList<>();
+                fehCommand.add(svgViewer);
+                //  fehCommand.add("-t");
+                fehCommand.add(tempPngFile.getAbsolutePath());
+                ProcessBuilder fehBuilder = new ProcessBuilder(fehCommand);
+                Process fehProcess = fehBuilder.start();
+
+            } catch (IOException | InterruptedException ioe) {
+                ioe.printStackTrace();
+            }
+        } else if (dot == null) {
+            System.out.println("Sorry can't find /usr/bin/dot (sudo apt-get install graphviz)");
+        } else {
+            System.out.println("Sorry can't find a suitable SVG Viewer");
+        }
+
+    }
+
+    public static void main(String[] args) throws IOException {
+        // https://renenyffenegger.ch/notes/tools/Graphviz/examples/index
+
+        StringWriter dotw = new StringWriter();
+        // http://magjac.com/graphviz-visual-editor/
+        new DotRenderer().writer(dotw).start("mine").graph(
+                (g) -> g
+                        .box("A",
+                                (box) -> box
+                                        .label("Snarlywang")
+                                        .color("lightyellow")
+                                        .style("filled")
+                        )
+                        .record("B",
+                                (record) -> record
+                                        .color("lightgreen")
+                                        .style("filled")
+                                        .label((label) -> label
+                                                .port("left", "left")
+                                                .box(
+                                                        (vertical) -> vertical
+                                                                .port("top", "top")
+                                                                .label("center")
+                                                                .port("bottom", "bottom")
+                                                )
+                                                .port("right", "right")
+                                        )
+                        )
+                        .edge("A", "B:top:nw", (e) -> e.label("1"))
+                        .edge("A", "B:bottom:se", (e) -> e.label("2"))
+                        .edge("A", "B:left:w", (e) -> e.label("3"))
+                        .edge("A", "B:right:e", (e) -> e.label("4"))
+        ).end();
+        DotViewer.view(dotw.toString());
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CStyleRenderer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CStyleRenderer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CStyleRenderer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.io.StringWriter;
+
+public final class CStyleRenderer extends CommonRenderer<CStyleRenderer> {
+    public CStyleRenderer() {
+        super();
+    }
+
+    public CStyleRenderer func(String identifier, TextRenderer.NestedRendererSAM<CStyleRenderer> args,
+                               TextRenderer.NestedRendererSAM<CStyleRenderer> body) {
+        return keyword("func").space().identifier(identifier).parenthesized(args).braced(body).nl();
+    }
+
+    public CStyleRenderer forLoop(TextRenderer.NestedRendererSAM<CStyleRenderer> init, TextRenderer.NestedRendererSAM<CStyleRenderer> cond,
+                                  TextRenderer.NestedRendererSAM<CStyleRenderer> mutator, TextRenderer.NestedRendererSAM<CStyleRenderer> body) {
+        return forKeyword().oparen().nest(init).semicolon().space().nest(cond).semicolon().space().
+                nest(mutator).cparen().braced(body).nl();
+    }
+
+    public CStyleRenderer whileLoop(TextRenderer.NestedRendererSAM<CStyleRenderer> cond, TextRenderer.NestedRendererSAM<CStyleRenderer> body) {
+        return whileKeyword().oparen().nest(cond).cparen().braced(body).nl();
+    }
+
+    public CStyleRenderer ifCondRaw(TextRenderer.NestedRendererSAM<CStyleRenderer> cond, TextRenderer.NestedRendererSAM<CStyleRenderer> thenBody) {
+        return ifKeyword().oparen().nest(cond).cparen().braced(thenBody);
+    }
+
+    public CStyleRenderer ifCond(TextRenderer.NestedRendererSAM<CStyleRenderer> cond, TextRenderer.NestedRendererSAM<CStyleRenderer> thenBody) {
+        return ifCondRaw(cond, thenBody).nl();
+    }
+
+    public CStyleRenderer ifCond(TextRenderer.NestedRendererSAM<CStyleRenderer> cond, TextRenderer.NestedRendererSAM<CStyleRenderer> thenBody,
+                                 TextRenderer.NestedRendererSAM<CStyleRenderer> elseBody) {
+        return ifCondRaw(cond, thenBody).elseKeyword().braced(elseBody).nl();
+    }
+
+    public CStyleRenderer var(Class<?> clazz, String name) {
+        return type(clazz.getName()).space().identifier(name);
+    }
+
+    public CStyleRenderer assign(String identifier) {
+        return identifier(identifier).equal();
+    }
+
+    static public void main(String[] args) {
+        StringWriter writer = new StringWriter();
+        CStyleRenderer renderer = new CStyleRenderer().writer(writer).colorize();
+        renderer.lineComment("A new function");
+        renderer.func("funcName",
+                (as) -> as.var(int.class, "name").comma().space()
+                        .var(int.class, "name2"),
+                (fb) -> fb.lineComment("Inside body of func")
+                        .append("here;\nis;\nsome text").semicolon().nl()
+                        .forLoop(
+                                (in) -> in.var(int.class, "a").equal().decLiteral(0),
+                                (cc) -> cc.identifier("a").op("<").decLiteral(10),
+                                (mu) -> mu.assign("a").identifier("a").op("+").decLiteral(1),
+                                (lb) -> lb.lineComment("in loop")
+                                        .ifCond(
+                                                (cc) -> cc.identifier("a").op(">").decLiteral(2),
+                                                (th) -> th.lineComment("positive"),
+                                                (el) -> el.lineComment("not so much ")
+                                        )
+                        )
+                        .nl()
+        ).nl();
+        System.out.println(writer);
+
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CommonRenderer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CommonRenderer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/CommonRenderer.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+/**
+ * Created by gfrost
+ */
+public class CommonRenderer<T extends CommonRenderer<T>> extends TextRenderer<T> {
+
+    public CommonRenderer() {
+    }
+
+    public CommonRenderer(TextRenderer<?> renderer) {
+        super(renderer);
+    }
+
+    public T semicolon() {
+        return op(";");
+    }
+
+    public T comma() {
+        return op(",");
+    }
+
+    public T commaSeparatedList() {
+        return startList();
+    }
+
+    public T commaSeparator() {
+        if (!first()) {
+            comma().space();
+        }
+        return self();
+    }
+
+    public T commaSpaceSeparatedList() {
+        return startList();
+    }
+
+    public T commaSpaceSeparator() {
+        if (!first()) {
+            comma().space();
+        }
+        return self();
+    }
+
+    public T spaceSeparatedList() {
+        return startList();
+    }
+
+    public T spaceSeparator() {
+        if (!first()) {
+            space();
+        }
+        return self();
+    }
+
+    public T newlineSeparatedList() {
+        return startList();
+    }
+
+    public T newlineSeparator() {
+        if (!first()) {
+            nl();
+        }
+        return self();
+    }
+
+    public T semicolonSeparatedList() {
+        return startList();
+    }
+
+    public T semicolonSeparator() {
+        if (!first()) {
+            semicolon();
+        }
+        return self();
+    }
+
+    public T semicolonSpaceSeparatedList() {
+        return startList();
+    }
+
+    public T semicolonSpaceSeparator() {
+        if (!first()) {
+            semicolon().space();
+        }
+        return self();
+    }
+
+    public T dot() {
+        return op(".");
+    }
+
+
+    public T equal() {
+        return op("=");
+    }
+
+
+    public T plusplus() {
+        return op("++");
+    }
+
+
+    public T minusminus() {
+        return op("--");
+    }
+
+    public T lineComment(String line) {
+        return op("//").space().comment(line).nl();
+    }
+
+
+    public T blockComment(String block) {
+        return op("/*").nl().comment(block).nl().op("*/").nl();
+    }
+
+    public T newKeyword() {
+        return keyword("new");
+    }
+
+
+    public T staticKeyword() {
+        return keyword("static");
+    }
+
+
+    public T constKeyword() {
+        return keyword("const");
+    }
+
+    public T ifKeyword() {
+        return keyword("if");
+
+    }
+
+
+    public T whileKeyword() {
+        return keyword("while");
+    }
+
+
+    public T breakKeyword() {
+        return keyword("break");
+
+    }
+
+
+    public T continueKeyword() {
+        return keyword("continue");
+    }
+
+
+    public T query() {
+        return op("?");
+    }
+
+
+    public T colon() {
+        return op(":");
+    }
+
+
+    public T nullKeyword() {
+        return keyword("null");
+
+    }
+
+
+    public T elseKeyword() {
+        return keyword("else");
+    }
+
+
+    public T returnKeyword() {
+        return keyword("return");
+    }
+
+
+    public T switchKeyword() {
+        return keyword("switch");
+    }
+
+
+    public T caseKeyword() {
+        return keyword("case");
+    }
+
+
+    public T defaultKeyword() {
+        return keyword("default");
+    }
+
+    public T doKeyword() {
+        return keyword("do");
+    }
+
+    public T forKeyword() {
+        return keyword("for");
+    }
+
+    public T ampersand() {
+        return op("&");
+    }
+
+    public T braced(NestedRendererSAM<T> nb) {
+        return nb.build(obrace().nl().in()).out().cbrace().self();
+    }
+
+    public T osbrace() {
+        return open("[");
+    }
+
+
+    public T csbrace() {
+        return close("]");
+    }
+
+
+    public T parenthesized(NestedRendererSAM<T> nb) {
+        return nb.build(oparen().in()).out().cparen().self();
+    }
+
+    public T underscore() {
+        return op("_");
+    }
+
+    public T oparen() {
+        return open("(");
+    }
+
+    public T cparen() {
+        return close(")");
+    }
+
+    public T obrace() {
+        return open("{");
+    }
+
+    public T cbrace() {
+        return close("}");
+    }
+
+    public T at() {
+        return op("@").self();
+    }
+
+    public T caret() {
+        return op("^").self();
+    }
+
+    public T percent() {
+        return op("%").self();
+    }
+
+    public T pipe() {
+        return op("|").self();
+    }
+
+    public T rarrow() {
+        return op("->").self();
+    }
+
+    public T larrow() {
+        return op("<-").self();
+    }
+
+    public T lt() {
+        return op("<").self();
+    }
+
+    public T gt() {
+        return op(">").self();
+    }
+
+    public T asterisk() {
+        return op("*").self();
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/Diff.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/Diff.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/Diff.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by gfrost
+ */
+public final class Diff {
+    private Diff() {
+    }
+
+    public static void annotate(File file, int line, int col, String msg) {
+        List<String> lines = getLines(file);
+        for (int i = Math.max(line - 2, 0); i < (line - 1); i++) {
+            System.out.printf("    %2d:%s\n", i + 1, lines.get(i));
+        }
+        String text = lines.get(line - 1);
+        System.out.printf(" -> %2d:%s\n      ", line, text);
+        for (int i = 0; i < col; i++) {
+            System.out.print(" ");
+        }
+        System.out.println("^ " + msg);
+        for (int i = line; i < Math.min(line + 2, lines.size() - 1); i++) {
+            System.out.printf("    %2d:%s\n", i + 1, lines.get(i));
+        }
+
+    }
+
+    static List<String> getLines(File file) {
+        List<String> lines = new ArrayList<>();
+        try {
+            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+            for (String line = br.readLine(); line != null; line = br.readLine()) {
+                lines.add(line);
+            }
+            br.close();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return lines;
+    }
+
+    public static class DiffResult {
+        String lhs;
+        String rhs;
+        public String result;
+        public int exitStatus;
+
+        public DiffResult(String lhs, String rhs, String result, int exitStatus) {
+            this.lhs = lhs;
+            this.rhs = rhs;
+            this.result = result;
+            this.exitStatus = exitStatus;
+        }
+    }
+
+    public static DiffResult diff(String lhs, String rhs, int width) {
+        try {
+            File lhsFile = File.createTempFile("lhs", "txt");
+            FileWriter lhsw = new FileWriter(lhsFile);
+            lhsw.append(lhs);
+            lhsw.close();
+            File rhsFile = File.createTempFile("rhs", "txt");
+            FileWriter rhsw = new FileWriter(rhsFile);
+            rhsw.append(rhs);
+            rhsw.close();
+
+            List<String> command = new ArrayList<>();
+            command.add("sdiff");
+            command.add("--expand-tabs");
+            command.add("--ignore-all-space");
+            command.add("--width=" + width);
+            command.add("--ignore-blank-lines");
+            command.add(lhsFile.getAbsolutePath());
+            command.add(rhsFile.getAbsolutePath());
+
+            ProcessBuilder builder = new ProcessBuilder(command);
+            final Process process = builder.start();
+            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            StringBuilder out = new StringBuilder();
+            String line;
+            while ((line = br.readLine()) != null) {
+                if (line.contains("|")) {
+                    out.append(TerminalColors.Color.RED.colorize(line)).append("\n");
+                } else {
+                    out.append(TerminalColors.Color.GREEN.colorize(line)).append("\n");
+                }
+
+            }
+            process.waitFor();
+            br.close();
+            lhsFile.delete();
+            rhsFile.delete();
+            return new DiffResult(lhs, rhs, out.toString(), process.exitValue());
+        } catch (IOException | InterruptedException ioe) {
+            ioe.printStackTrace();
+        }
+        return null;
+    }
+
+
+    public static File write(File file, String text) {
+        try {
+            PrintWriter pw = new PrintWriter(file);
+            pw.append(text);
+            pw.append("\n");
+            pw.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return file;
+    }
+
+
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/GlobalValueBlockNaming.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/GlobalValueBlockNaming.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/GlobalValueBlockNaming.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Value;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class GlobalValueBlockNaming {
+    final Map<Value, String> gvn;
+    final Map<Block, String> gbn;
+    int valueOrdinal = 0;
+    int blockOrdinal = 0;
+
+    GlobalValueBlockNaming() {
+        this.gvn = new HashMap<>();
+        this.gbn = new HashMap<>();
+    }
+
+    String getValueName(Value _v) {
+        return gvn.computeIfAbsent(_v, v -> String.valueOf(valueOrdinal++));
+    }
+
+    String getBlockName(Block _b) {
+        return gbn.computeIfAbsent(_b, b -> "block_" + blockOrdinal++);
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/ProcessRunner.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/ProcessRunner.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/ProcessRunner.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Created by gfrost
+ */
+public class ProcessRunner {
+
+    static public ProcessRunner run(String prog) {
+        return new ProcessRunner(prog);
+    }
+
+    static public ProcessRunner run(File file) {
+        return new ProcessRunner(file);
+    }
+
+    List<String> args = new ArrayList<>();
+
+    @SuppressWarnings("this-escape")
+    public ProcessRunner(String prog) {
+        opt(prog);
+    }
+
+    @SuppressWarnings("this-escape")
+    public ProcessRunner(File prog) {
+        file(prog);
+    }
+
+    public ProcessRunner opt(String... argsToAdd) {
+        for (String s : argsToAdd) {
+            args.add(s);
+        }
+        return this;
+    }
+
+    public ProcessRunner fileOpt(String opt, File file) {
+        return opt(opt).file(file);
+    }
+
+    public ProcessRunner dirOpt(String opt, File dir) {
+        dir.mkdir();
+        return fileOpt(opt, dir);
+    }
+
+    public ProcessRunner file(File file) {
+        opt(file.getAbsolutePath());
+        return this;
+    }
+
+    public ProcessRunner files(File[] files) {
+        for (File f : files) {
+            file(f);
+        }
+        return this;
+    }
+
+    public void files(List<File> rsFiles) {
+        files(rsFiles.toArray(new File[0]));
+    }
+
+    public static class Result {
+        public Result() {
+        }
+
+        public void scan(Pattern pattern, Scanner scanner) {
+            for (List<String> list : streams) { //stdout then stderr
+                for (String text : list) {
+                    Matcher matcher;
+                    if ((matcher = pattern.matcher(text)).matches()) {
+                        scanner.process(matcher);
+                    }
+                }
+            }
+        }
+
+        public interface Scanner {
+            void process(Matcher m);
+        }
+
+        public int status = -1;
+        public boolean ok = false;
+        public String commandLine = "";
+
+        public List<String> stdout = new ArrayList<>();
+        public List<String> stderr = new ArrayList<>();
+        public List<List<String>> streams = List.of(stdout, stderr);
+    }
+
+
+    public Result go(boolean verbose) {
+        Result result = new Result();
+
+        StringBuilder commandBuilder = new StringBuilder();
+
+        for (String arg : args) {
+            commandBuilder.append(arg + " ");
+            if (verbose) {
+                System.out.print(arg + " ");
+            }
+        }
+
+        result.commandLine = commandBuilder.toString();
+        ProcessBuilder processBuilder = new ProcessBuilder(args);
+        try {
+            Process process = processBuilder.start();
+            Thread stdout = new StreamReader("OUT", process.getErrorStream(), result.stdout, verbose).thread;
+            Thread stderr = new StreamReader("ERR", process.getInputStream(), result.stderr, verbose).thread;
+            result.status = process.waitFor();
+            stdout.join();
+            stderr.join();
+            result.ok = result.status == 0;
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public ProcessRunner temp(String prefix, String suffix, String text) {
+        try {
+            File tempFile = File.createTempFile(prefix, suffix);
+            FileWriter tempDotFileWriter = new FileWriter(tempFile);
+            tempDotFileWriter.append(text);
+            tempDotFileWriter.close();
+            file(tempFile);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return this;
+    }
+
+
+    static class StreamReader {
+        Thread thread;
+
+        StreamReader(final String prefix, InputStream is, final List<String> out, final boolean verbose) {
+            final BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            thread = new Thread(() -> {
+                try {
+                    for (String string = br.readLine(); string != null; string = br.readLine()) {
+                        if (verbose) {
+                            System.out.println(prefix + ":" + string);
+                        }
+                        out.add(string);
+                    }
+                    br.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            });
+            thread.start();
+
+        }
+
+    }
+
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/SRRenderer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/SRRenderer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/SRRenderer.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Body;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+
+import java.io.*;
+import java.lang.reflect.code.op.ExternalizableOp;
+import java.lang.reflect.code.type.JavaType;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Created by gfrost
+ */
+public final class SRRenderer extends CommonRenderer<SRRenderer> {
+
+    static class AttributeMapper {
+        static String toString(Object value) {
+            if (value instanceof Integer i && i >= 0) {
+                return Integer.toString(i);
+            } else if (value == null) {
+                return "null";
+            } else {
+                return "\"" + quote(value.toString()) + "\"";
+            }
+        }
+    }
+
+    // Copied from com.sun.tools.javac.util.Convert
+    static String quote(String s) {
+        StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            buf.append(quote(s.charAt(i)));
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Escapes a character if it has an escape sequence or is
+     * non-printable ASCII.  Leaves non-ASCII characters alone.
+     */
+    static String quote(char ch) {
+        switch (ch) {
+            case '\b':  return "\\b";
+            case '\f':  return "\\f";
+            case '\n':  return "\\n";
+            case '\r':  return "\\r";
+            case '\t':  return "\\t";
+            case '\'':  return "\\'";
+            case '\"':  return "\\\"";
+            case '\\':  return "\\\\";
+            default:
+                return (isPrintableAscii(ch))
+                        ? String.valueOf(ch)
+                        : String.format("\\u%04x", (int) ch);
+        }
+    }
+
+    /**
+     * Is a character printable ASCII?
+     */
+    static boolean isPrintableAscii(char ch) {
+        return ch >= ' ' && ch <= '~';
+    }
+
+    SRRenderer() {
+        super();
+    }
+
+    SRRenderer caretLabelTarget(String name) {
+        return caret().labelTarget(name).self();
+    }
+
+    SRRenderer atIdentifier(String name) {
+        return at().identifier(name).self();
+    }
+
+    SRRenderer percentLiteral(String name) {
+        return percent().literal(name).self();
+    }
+
+    SRRenderer spaceColonSpace() {
+        return space().colon().space();
+    }
+
+    SRRenderer spaceEqualSpace() {
+        return space().equal().space();
+    }
+
+    public void write(Op op) {
+        GlobalValueBlockNaming gn = new GlobalValueBlockNaming();
+        write(gn, op);
+        nl();
+    }
+
+    public void write(GlobalValueBlockNaming gn, Block.Reference successor) {
+        caretLabelTarget(gn.getBlockName(successor.targetBlock()));
+        if (!successor.arguments().isEmpty()) {
+            oparen().commaSeparatedList();
+            for (var a : successor.arguments()) {
+                commaSeparator();
+                percentLiteral(gn.getValueName(a));
+            }
+            cparen();
+        }
+
+    }
+
+    public void write(GlobalValueBlockNaming gn, Op op) {
+        keyword(op.opName());
+        if (!op.operands().isEmpty()) {
+            space().spaceSeparatedList();
+            for (var v : op.operands()) {
+                spaceSeparator();
+                percentLiteral(gn.getValueName(v));
+            }
+        }
+        if (!op.successors().isEmpty()) {
+            space().spaceSeparatedList();
+            for (Block.Reference sb : op.successors()) {
+                spaceSeparator();
+                write(gn, sb);
+            }
+        }
+
+        if (op instanceof ExternalizableOp exop) {
+            if (!exop.attributes().isEmpty()) {
+                space().spaceSeparatedList();
+                for (var e : exop.attributes().entrySet()) {
+                    spaceSeparator();
+                    String name = e.getKey();
+                    if (!name.isEmpty()) {
+                        atIdentifier(name).equal().identifier(AttributeMapper.toString(e.getValue()));
+                    } else {
+                        atIdentifier(AttributeMapper.toString(e.getValue()));
+                    }
+                }
+            }
+        }
+
+        if (!op.bodies().isEmpty()) {
+            int nBodies = op.bodies().size();
+            if (nBodies == 1) {
+                space();
+            } else {
+                nl().in().in();
+            }
+            // @@@ separated list state does not nest as state.first gets overwritten
+            boolean first = true;
+            for (Body body : op.bodies()) {
+                if (!first) {
+                    nl();
+                }
+                write(gn, body);
+                first = false;
+            }
+            if (nBodies > 1) {
+                out().out();
+            }
+        }
+
+        semicolon();
+    }
+
+    public void write(GlobalValueBlockNaming gn, Block block, boolean isEntryBlock) {
+        if (!isEntryBlock) {
+            caretLabelTarget(gn.getBlockName(block));
+            if (!block.parameters().isEmpty()) {
+                oparen().commaSpaceSeparatedList();
+                for (var v : block.parameters()) {
+                    commaSpaceSeparator();
+                    writeValueDecl(gn, v);
+                }
+                cparen();
+            }
+            colon().nl();
+        }
+        in();
+        for (Op op : block.ops()) {
+            Op.Result or = op.result();
+            if (!or.type().equals(JavaType.VOID)) {
+                writeValueDecl(gn, or);
+                spaceEqualSpace();
+            }
+            write(gn, op);
+            nl();
+        }
+        out();
+    }
+
+    public void write(GlobalValueBlockNaming gn, Body body) {
+        Block eb = body.entryBlock();
+        oparen().commaSpaceSeparatedList();
+        for (var v : eb.parameters()) {
+            commaSpaceSeparator();
+            writeValueDecl(gn, v);
+        }
+        cparen().type(body.bodyType().returnType().toString()).space().rarrow().space().obrace().nl();
+        in();
+        boolean isEntryBlock = true;
+        for (Block b : body.blocks()) {
+            if (!isEntryBlock) {
+                nl();
+            }
+            write(gn, b, isEntryBlock);
+            isEntryBlock = false;
+        }
+        out();
+        cbrace();
+    }
+
+    public void writeValueDecl(GlobalValueBlockNaming gn, Value v) {
+        percentLiteral(gn.getValueName(v)).spaceColonSpace().type(v.type().toString());
+    }
+
+    // @@@ Not used
+    public void write(GlobalValueBlockNaming gn, CoreOp.FuncOp fRep) {
+        this.append(fRep.opName());// w.write(name);
+        if (!fRep.operands().isEmpty()) {
+            space().spaceSeparatedList();
+            for (var v : fRep.operands()) {
+                spaceSeparator();
+                percentLiteral(gn.getValueName(v));
+            }
+        }
+        if (!fRep.successors().isEmpty()) {
+            space().spaceSeparatedList();
+            for (Block.Reference sb : fRep.successors()) {
+                spaceSeparator();
+                write(gn, sb);
+            }
+        }
+        if (!fRep.attributes().isEmpty()) {
+            space();
+            for (var e : fRep.attributes().entrySet()) {
+                String name = e.getKey();
+                String value = AttributeMapper.toString(e.getValue());
+                op("@");
+                if (name.isEmpty()) {
+                    literal(value);
+                } else {
+                    identifier(name).equal().literal(value);
+                }
+            }
+        }
+        if (!fRep.bodies().isEmpty()) {
+            space().newlineSeparatedList();
+            for (Body body : fRep.bodies()) {
+                newlineSeparator();
+                write(gn, body);
+            }
+        }
+
+    }
+
+    public static void write(Writer writer, Op op) {
+        new SRRenderer().writer(writer).write(op);
+    }
+
+    public static void write(OutputStream out, Op op) {
+        write(new OutputStreamWriter(out, StandardCharsets.UTF_8), op);
+    }
+
+    public static String stringify(Op op) {
+        StringWriter sw = new StringWriter();
+        write(sw, op);
+        return sw.toString();
+    }
+
+    public static String colorize(TextRenderer.TokenColorMap tokenColorMap, Op op) {
+        StringWriter sw = new StringWriter();
+        new SRRenderer().writer(sw).colorize(tokenColorMap).write(op);
+        return sw.toString();
+    }
+
+    public static String colorize(Op op) {
+        return colorize(new TextRenderer.TokenColorMap(), op);
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TerminalColors.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TerminalColors.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TerminalColors.java
@@ -0,0 +1,28 @@
+package jdk.code.tools.renderer;
+
+
+public final class TerminalColors {
+    private TerminalColors() {
+    }
+
+    public interface Colorizer {
+        String colorize(String text);
+    }
+
+    public enum Color implements Colorizer {
+        // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#8-colors
+        NONE("0"),
+        BLACK("38;5;0"), DARKGREEN("38;5;22"), DARKBLUE("38;5;27"),
+        GREY("38;5;247"), RED("38;5;1"), GREEN("38;5;77"), YELLOW("38;5;185"),
+        BLUE("38;5;31"), WHITE("38;5;251"), ORANGE("38;5;208"), PURPLE("38;5;133");
+        final String escSequence;
+
+        Color(String seq) {
+            escSequence = "\u001b[" + seq + "m";
+        }
+
+        public String colorize(String string) {
+            return (this == NONE) ? string : escSequence + string + NONE.escSequence;
+        }
+    }
+}
diff a/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TextRenderer.java b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TextRenderer.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/jdk/code/tools/renderer/TextRenderer.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.code.tools.renderer;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.util.Map;
+
+public class TextRenderer<T extends TextRenderer<T>> {
+
+    public interface NestedRendererSAM<T> {
+        T build(T nb);
+    }
+
+    public enum TokenType {
+        WHITESPACE, OP, TYPE, CONTROL, LITERAL, COMMENT, KEYWORD, IDENTIFIER, LABELTARGET, NONE
+    }
+
+    public static class TokenColorMap {
+
+
+        private final Map<TokenType, TerminalColors.Colorizer> map;
+
+        public TokenColorMap(Map<TokenType, TerminalColors.Colorizer> map) {
+            this.map = map;
+        }
+
+        public TokenColorMap() {
+            this(Map.of(
+                    TokenType.NONE, TerminalColors.Color.WHITE,
+                    TokenType.IDENTIFIER, TerminalColors.Color.YELLOW,
+                    TokenType.LABELTARGET, TerminalColors.Color.BLUE,
+                    TokenType.TYPE, TerminalColors.Color.WHITE,
+                    TokenType.COMMENT, TerminalColors.Color.GREEN,
+                    TokenType.KEYWORD, TerminalColors.Color.ORANGE,
+                    TokenType.CONTROL, TerminalColors.Color.GREY,
+                    TokenType.LITERAL, TerminalColors.Color.GREEN,
+                    TokenType.OP, TerminalColors.Color.WHITE,
+                    TokenType.WHITESPACE, TerminalColors.Color.WHITE));
+        }
+
+        public String colorize(TokenType tokenType, String string) {
+            if (map.containsKey(tokenType)) {
+                return map.get(tokenType).colorize(string);
+            } else {
+                return string;
+            }
+        }
+    }
+
+    public static class State {
+        public Writer writer;
+        public int indent;
+        public TokenColorMap tokenColorMap = null;
+        public boolean isFirst = false;
+
+        public boolean newLined = false;
+
+        State() {
+            this.writer = null;
+            this.indent = 0;
+            this.tokenColorMap = null;
+            this.isFirst = false;
+            this.newLined = true;
+        }
+    }
+
+    public T writer(Writer writer) {
+        this.state.writer = writer;
+        return self();
+    }
+
+    public T colorize(TokenColorMap tokenColorMap) {
+        this.state.tokenColorMap = tokenColorMap;
+        return self();
+    }
+
+    public T colorize() {
+        return colorize(new TokenColorMap());
+    }
+
+    public State state;
+
+    protected TextRenderer() {
+        this.state = new State();
+    }
+
+    protected TextRenderer(TextRenderer<?> renderer) {
+        this.state = renderer.state;
+    }
+
+    @SuppressWarnings("unchecked")
+    public T self() {
+        return (T) this;
+    }
+
+
+    public boolean first() {
+        var was = state.isFirst;
+        state.isFirst = false;
+        return was;
+    }
+
+    public T startList() {
+        state.isFirst = true;
+        return self();
+    }
+
+    public T append(String text) {
+        try {
+            // While we do expect appends text to be simple tokens. We can handle newlines.
+            var lines = text.split("\n");
+            for (int i = 0; i < lines.length - 1; i++) {
+                state.writer.append(" ".repeat(state.indent) + lines[i] + "\n");
+                state.newLined = true;
+            }
+            if (state.newLined) {
+                state.writer.append(" ".repeat(state.indent));
+            }
+            state.writer.append(lines[lines.length - 1]);
+            state.newLined = false;
+            return self();
+        } catch (IOException ioe) {
+            throw new UncheckedIOException(ioe);
+        }
+    }
+
+    public T identifier(String ident) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.IDENTIFIER, ident));
+        } else {
+            return append(ident);
+        }
+    }
+
+    public T type(String typeName) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.TYPE, typeName));
+        } else {
+            return append(typeName);
+        }
+    }
+
+    public T keyword(String keyword) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.KEYWORD, keyword));
+        } else {
+            return append(keyword);
+        }
+    }
+
+    public T literal(String literal) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.LITERAL, literal));
+        } else {
+            return append(literal);
+        }
+    }
+
+    public T ws(String whitespace) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.WHITESPACE, whitespace));
+        } else {
+            return append(whitespace);
+        }
+    }
+
+    public T op(String op) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.OP, op));
+        } else {
+            return append(op);
+        }
+    }
+
+    public T control(String control) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.CONTROL, control));
+        } else {
+            return append(control);
+        }
+    }
+
+    public T labelTarget(String labelTarget) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.LABELTARGET, labelTarget));
+        } else {
+            return append(labelTarget);
+        }
+    }
+
+    public T comment(String comment) {
+        if (state.tokenColorMap != null) {
+            return append(state.tokenColorMap.colorize(TokenType.COMMENT, comment));
+        } else {
+            return append(comment);
+        }
+    }
+
+    public T strLiteral(String s) {
+        return oquot().literal(s).cquot();
+    }
+
+    public T oquot() {
+        return literal("\"");
+    }
+
+    public T cquot() {
+        return literal("\"");
+    }
+
+    public T decLiteral(int i) {
+        return literal(String.format("%d", i));
+    }
+
+    public T hexLiteral(int i) {
+        return literal(String.format("%x", i));
+    }
+
+    public T in() {
+        state.indent += 2;
+        return self();
+    }
+
+    public T out() {
+        state.indent -= 2;
+        return self();
+    }
+
+    public T flush() {
+        try {
+            state.writer.flush();
+            return self();
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    public T nl() {
+        try {
+            // note we go directly to the underlying writer!
+            state.writer.append("\n");
+            state.newLined = true;
+            return flush().self();
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    public T space() {
+        return ws(" ");
+    }
+
+    public T nest(NestedRendererSAM<T> nb) {
+        return nb.build(self());
+    }
+
+    public T open(String op) {
+        control(op);
+        return self();
+    }
+
+    public T close(String op) {
+        control(op);
+        return self();
+    }
+}
+
diff a/src/jdk.code.tools/share/classes/module-info.java b/src/jdk.code.tools/share/classes/module-info.java
--- /dev/null
+++ b/src/jdk.code.tools/share/classes/module-info.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Tools module for code reflection, providing useful ancillary functionality
+ * that may not be necessary in the java.base module.
+ *
+ */
+module jdk.code.tools {
+    exports jdk.code.tools.dot;
+    exports jdk.code.tools.renderer;
+}
+
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
@@ -253,10 +253,11 @@
         IMPLICIT_CLASSES(JDK21, Fragments.FeatureImplicitClasses, DiagKind.PLURAL),
         WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
         UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
         PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL),
         SUPER_INIT(JDK22, Fragments.FeatureSuperInit, DiagKind.NORMAL),
+        REFLECT_METHODS(JDK22, Fragments.FeatureReflectMethods, DiagKind.NORMAL),
         ;
 
         enum DiagKind {
             NORMAL,
             PLURAL;
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
@@ -43,10 +43,11 @@
 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
 import com.sun.tools.javac.code.Symbol.PackageSymbol;
 import com.sun.tools.javac.code.Symbol.RootPackageSymbol;
 import com.sun.tools.javac.code.Symbol.TypeSymbol;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Type.ArrayType;
 import com.sun.tools.javac.code.Type.BottomType;
 import com.sun.tools.javac.code.Type.ClassType;
 import com.sun.tools.javac.code.Type.ErrorType;
 import com.sun.tools.javac.code.Type.JCPrimitiveType;
 import com.sun.tools.javac.code.Type.JCVoidType;
@@ -240,10 +241,22 @@
     // For externalization lint checking
     public final Type externalizableType;
     public final Type objectInputType;
     public final Type objectOutputType;
 
+    // For code reflection
+    public final Type codeReflectionType;
+    public final Type quotedType;
+    public final Type quotableType;
+    public final Type closureOpType;
+    public final Type lambdaOpType;
+    public final Type opInterpreterType;
+    public final Type opParserType;
+    public final Type opType;
+    public final MethodSymbol opInterpreterInvoke;
+    public final MethodSymbol opParserFromString;
+
     /** The symbol representing the length field of an array.
      */
     public final VarSymbol lengthVar;
 
     /** The symbol representing the final finalize method on enums */
@@ -623,16 +636,37 @@
         ioExceptionType = enterClass("java.io.IOException");
         objectStreamExceptionType = enterClass("java.io.ObjectStreamException");
         externalizableType = enterClass("java.io.Externalizable");
         objectInputType  = enterClass("java.io.ObjectInput");
         objectOutputType = enterClass("java.io.ObjectOutput");
+        // For code reflection
+        codeReflectionType = enterClass("java.lang.runtime.CodeReflection");
+        quotedType = enterClass("java.lang.reflect.code.Quoted");
+        quotableType = enterClass("java.lang.reflect.code.Quotable");
+        closureOpType = enterClass("java.lang.reflect.code.op.CoreOp$ClosureOp");
+        lambdaOpType = enterClass("java.lang.reflect.code.op.CoreOp$LambdaOp");
+        opInterpreterType = enterClass("java.lang.reflect.code.interpreter.Interpreter");
+        opType = enterClass("java.lang.reflect.code.Op");
+        opInterpreterInvoke = new MethodSymbol(PUBLIC | STATIC | VARARGS,
+                names.fromString("invoke"),
+                new MethodType(List.of(opType, new ArrayType(objectType, arrayClass)), objectType,
+                        List.nil(), methodClass),
+                opInterpreterType.tsym);
+        opParserType = enterClass("java.lang.reflect.code.parser.OpParser");
+        opParserFromString = new MethodSymbol(PUBLIC | STATIC,
+                names.fromString("fromStringOfFuncOp"),
+                new MethodType(List.of(stringType), opType,
+                        List.nil(), methodClass),
+                opParserType.tsym);
         synthesizeEmptyInterfaceIfMissing(autoCloseableType);
         synthesizeEmptyInterfaceIfMissing(cloneableType);
         synthesizeEmptyInterfaceIfMissing(serializableType);
         synthesizeEmptyInterfaceIfMissing(lambdaMetafactory);
         synthesizeEmptyInterfaceIfMissing(serializedLambdaType);
         synthesizeEmptyInterfaceIfMissing(stringConcatFactory);
+        synthesizeEmptyInterfaceIfMissing(quotedType);
+        synthesizeEmptyInterfaceIfMissing(quotableType);
         synthesizeBoxTypeIfMissing(doubleType);
         synthesizeBoxTypeIfMissing(floatType);
         synthesizeBoxTypeIfMissing(voidType);
 
         // Enter a synthetic class that is used to mark internal
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -196,19 +196,73 @@
         UPWARDS() {
             @Override
             ProjectionKind complement() {
                 return DOWNWARDS;
             }
+
+            @Override
+            boolean allowIntersectionTypes() {
+                return true;
+            }
+
+            @Override
+            ProjectionKind withIntersectionTypes(boolean allowIntersectionTypes) {
+                return allowIntersectionTypes ? this : UPWARDS_NO_INTERSECTION;
+            }
         },
         DOWNWARDS() {
             @Override
             ProjectionKind complement() {
                 return UPWARDS;
             }
+
+            @Override
+            boolean allowIntersectionTypes() {
+                return true;
+            }
+
+            @Override
+            ProjectionKind withIntersectionTypes(boolean allowIntersectionTypes) {
+                return allowIntersectionTypes ? this : DOWNWARDS_NO_INTERSECTION;
+            }
+        },
+        UPWARDS_NO_INTERSECTION() {
+            @Override
+            ProjectionKind complement() {
+                return DOWNWARDS_NO_INTERSECTION;
+            }
+
+            @Override
+            boolean allowIntersectionTypes() {
+                return false;
+            }
+
+            @Override
+            ProjectionKind withIntersectionTypes(boolean allowIntersectionTypes) {
+                return allowIntersectionTypes ? UPWARDS : this;
+            }
+        },
+        DOWNWARDS_NO_INTERSECTION() {
+            @Override
+            ProjectionKind complement() {
+                return UPWARDS_NO_INTERSECTION;
+            }
+
+            @Override
+            boolean allowIntersectionTypes() {
+                return false;
+            }
+
+            @Override
+            ProjectionKind withIntersectionTypes(boolean allowIntersectionTypes) {
+                return allowIntersectionTypes ? DOWNWARDS : this;
+            }
         };
 
         abstract ProjectionKind complement();
+        abstract boolean allowIntersectionTypes();
+        abstract ProjectionKind withIntersectionTypes(boolean allowIntersectionTypes);
     }
 
     /**
      * This visitor performs upwards and downwards projections on types.
      *
@@ -243,15 +297,22 @@
             this.vars = vars;
         }
 
         @Override
         public Type visitClassType(ClassType t, ProjectionKind pkind) {
-            if (t.isCompound()) {
-                List<Type> components = directSupertypes(t);
-                List<Type> components1 = components.map(c -> c.map(this, pkind));
-                if (components == components1) return t;
-                else return makeIntersectionType(components1);
+            if (t.isUnion() || t.isIntersection()) {
+                if (pkind.allowIntersectionTypes()) {
+                    List<Type> components = directSupertypes(t);
+                    List<Type> components1 = components.map(c -> c.map(this, pkind));
+                    if (components == components1) return t;
+                    else return makeIntersectionType(components1);
+                } else if (t.isIntersection()) {
+                    return visit(((IntersectionClassType)t).getExplicitComponents().head, pkind);
+                } else {
+                    Assert.check(t.isUnion());
+                    return visit(((UnionClassType)t).getLub(), pkind);
+                }
             } else {
                 Type outer = t.getEnclosingType();
                 Type outer1 = visit(outer, pkind);
                 List<Type> typarams = t.getTypeArguments();
                 List<Type> formals = t.tsym.type.getTypeArguments();
@@ -302,13 +363,15 @@
                 if (seen.add(t)) {
                     try {
                         final Type bound;
                         switch (pkind) {
                             case UPWARDS:
+                            case UPWARDS_NO_INTERSECTION:
                                 bound = t.getUpperBound();
                                 break;
                             case DOWNWARDS:
+                            case DOWNWARDS_NO_INTERSECTION:
                                 bound = (t.getLowerBound() == null) ?
                                         syms.botType :
                                         t.getLowerBound();
                                 break;
                             default:
@@ -319,20 +382,20 @@
                     } finally {
                         seen.remove(t);
                     }
                 } else {
                     //cycle
-                    return pkind == ProjectionKind.UPWARDS ?
+                    return (pkind == ProjectionKind.UPWARDS || pkind == ProjectionKind.UPWARDS_NO_INTERSECTION) ?
                             syms.objectType : syms.botType;
                 }
             } else {
                 return t;
             }
         }
 
         private Type mapTypeArgument(Type site, Type declaredBound, Type t, ProjectionKind pkind) {
-            return t.containsAny(vars) ?
+            return (t.containsAny(vars) || (!pkind.allowIntersectionTypes() && !chk.checkDenotable(t))) ?
                     t.map(new TypeArgumentProjection(site, declaredBound), pkind) :
                     t;
         }
 
         class TypeArgumentProjection extends TypeMapping<ProjectionKind> {
@@ -346,25 +409,25 @@
             }
 
             @Override
             public Type visitType(Type t, ProjectionKind pkind) {
                 //type argument is some type containing restricted vars
-                if (pkind == ProjectionKind.DOWNWARDS) {
+                if (pkind == ProjectionKind.DOWNWARDS || pkind == ProjectionKind.DOWNWARDS_NO_INTERSECTION) {
                     //not defined
                     return syms.botType;
                 }
-                Type upper = t.map(TypeProjection.this, ProjectionKind.UPWARDS);
+                Type upper = t.map(TypeProjection.this, ProjectionKind.UPWARDS.withIntersectionTypes(pkind.allowIntersectionTypes()));
                 Type lower = t.map(TypeProjection.this, ProjectionKind.DOWNWARDS);
                 List<Type> formals = site.tsym.type.getTypeArguments();
                 BoundKind bk;
                 Type bound;
                 if (!isSameType(upper, syms.objectType) &&
                         (declaredBound.containsAny(formals) ||
                          !isSubtype(declaredBound, upper))) {
                     bound = upper;
                     bk = EXTENDS;
-                } else if (!lower.hasTag(BOT)) {
+                } else if (!lower.hasTag(BOT) && (!lower.isIntersection() || pkind.allowIntersectionTypes())) {
                     bound = lower;
                     bk = SUPER;
                 } else {
                     bound = syms.objectType;
                     bk = UNBOUND;
@@ -383,12 +446,12 @@
                         if (bound.hasTag(BOT)) {
                             return syms.botType;
                         }
                         break;
                     case SUPER:
-                        bound = wt.type.map(TypeProjection.this, pkind.complement());
-                        if (bound.hasTag(BOT)) {
+                        bound = wt.type.map(TypeProjection.this, pkind.withIntersectionTypes(true).complement());
+                        if (bound.hasTag(BOT) || (bound.isIntersection() && !pkind.allowIntersectionTypes())) {
                             bound = syms.objectType;
                             bk = UNBOUND;
                         }
                         break;
                 }
@@ -412,11 +475,24 @@
      * @param t the type to be projected
      * @param vars the set of type variables to be mapped
      * @return the type obtained as result of the projection
      */
     public Type upward(Type t, List<Type> vars) {
-        return t.map(new TypeProjection(vars), ProjectionKind.UPWARDS);
+        return upward(t, true, vars);
+    }
+
+    /**
+     * Computes an upward projection of given type, and vars. See {@link TypeProjection}.
+     *
+     * @param t the type to be projected
+     * @param allowIntersection whether intersection types should be allowed in the projection
+     * @param vars the set of type variables to be mapped
+     * @return the type obtained as result of the projection
+     */
+    public Type upward(Type t, boolean allowIntersection, List<Type> vars) {
+        return t.map(new TypeProjection(vars),
+                allowIntersection ? ProjectionKind.UPWARDS : ProjectionKind.UPWARDS_NO_INTERSECTION);
     }
 
     /**
      * Computes the set of captured variables mentioned in a given type. See {@link CaptureScanner}.
      * This routine is typically used to computed the input set of variables to be used during
@@ -5320,6 +5396,12 @@
         isDerivedRawCache.clear();
         implCache._map.clear();
         membersCache._map.clear();
         closureCache.clear();
     }
+
+    // code reflection
+
+    public boolean isQuoted(Type type) {
+        return type.tsym == syms.quotedType.tsym;
+    }
 }
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -27,10 +27,11 @@
 
 import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.code.Symtab;
 import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Types;
 import com.sun.tools.javac.code.Types.FunctionDescriptorLookupError;
 import com.sun.tools.javac.comp.Attr.ResultInfo;
 import com.sun.tools.javac.comp.Attr.TargetInfo;
 import com.sun.tools.javac.comp.Check.CheckContext;
 import com.sun.tools.javac.comp.DeferredAttr.AttrMode;
@@ -102,10 +103,12 @@
     private final JCDiagnostic.Factory diags;
     private final Attr attr;
     private final Symtab syms;
     private final Log log;
 
+    private final Types types;
+
     /** Attribution environment to be used. */
     private Env<AttrContext> env;
 
     /** Result of method attribution. */
     Type result;
@@ -126,10 +129,11 @@
         deferredAttr = DeferredAttr.instance(context);
         diags = JCDiagnostic.Factory.instance(context);
         attr = Attr.instance(context);
         syms = Symtab.instance(context);
         log = Log.instance(context);
+        types = Types.instance(context);
     }
 
     /**
      * Set the results of method attribution.
      */
@@ -557,21 +561,26 @@
             });
         }
 
         @Override
         Type overloadCheck(ResultInfo resultInfo, DeferredAttrContext deferredAttrContext) {
-            try {
-                //compute target-type; this logic could be shared with Attr
-                TargetInfo targetInfo = attr.getTargetInfo(speculativeTree, resultInfo, argtypes());
-                Type lambdaType = targetInfo.descriptor;
-                Type currentTarget = targetInfo.target;
-                //check compatibility
-                checkLambdaCompatible(lambdaType, resultInfo);
-                return currentTarget;
-            } catch (FunctionDescriptorLookupError ex) {
-                resultInfo.checkContext.report(null, ex.getDiagnostic());
-                return null; //cannot get here
+            if (types.isQuoted(resultInfo.pt)) {
+                // quoted lambda - always correct
+                return resultInfo.pt;
+            } else {
+                try {
+                    //compute target-type; this logic could be shared with Attr
+                    TargetInfo targetInfo = attr.getTargetInfo(speculativeTree, resultInfo, argtypes());
+                    Type lambdaType = targetInfo.descriptor;
+                    Type currentTarget = targetInfo.target;
+                    //check compatibility
+                    checkLambdaCompatible(lambdaType, resultInfo);
+                    return currentTarget;
+                } catch (FunctionDescriptorLookupError ex) {
+                    resultInfo.checkContext.report(null, ex.getDiagnostic());
+                    return null; //cannot get here
+                }
             }
         }
 
         /** Check lambda against given target result */
         private void checkLambdaCompatible(Type descriptor, ResultInfo resultInfo) {
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
@@ -26,17 +26,19 @@
 package com.sun.tools.javac.comp;
 
 import java.util.*;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Stream;
 
 import javax.lang.model.element.ElementKind;
 import javax.tools.JavaFileObject;
 
 import com.sun.source.tree.CaseTree;
 import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LambdaExpressionTree.BodyKind;
 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
 import com.sun.source.tree.MemberSelectTree;
 import com.sun.source.tree.TreeVisitor;
 import com.sun.source.util.SimpleTreeVisitor;
 import com.sun.tools.javac.code.*;
@@ -45,12 +47,15 @@
 import com.sun.tools.javac.code.Source.Feature;
 import com.sun.tools.javac.code.Symbol.*;
 import com.sun.tools.javac.code.Type.*;
 import com.sun.tools.javac.code.Types.FunctionDescriptorLookupError;
 import com.sun.tools.javac.comp.ArgumentAttr.LocalCacheContext;
+import com.sun.tools.javac.comp.Attr.ResultInfo;
 import com.sun.tools.javac.comp.Check.CheckContext;
+import com.sun.tools.javac.comp.Check.NestedCheckContext;
 import com.sun.tools.javac.comp.DeferredAttr.AttrMode;
+import com.sun.tools.javac.comp.DeferredAttr.LambdaReturnScanner;
 import com.sun.tools.javac.comp.MatchBindingsComputer.MatchBindings;
 import com.sun.tools.javac.jvm.*;
 
 import static com.sun.tools.javac.resources.CompilerProperties.Fragments.Diamond;
 import static com.sun.tools.javac.resources.CompilerProperties.Fragments.DiamondInvalidArg;
@@ -59,10 +64,11 @@
 import com.sun.tools.javac.resources.CompilerProperties.Errors;
 import com.sun.tools.javac.resources.CompilerProperties.Fragments;
 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
 import com.sun.tools.javac.tree.*;
 import com.sun.tools.javac.tree.JCTree.*;
+import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
 import com.sun.tools.javac.tree.JCTree.JCPolyExpression.*;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.DefinedBy.Api;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
 import com.sun.tools.javac.util.JCDiagnostic.Error;
@@ -421,10 +427,23 @@
             log.useSource(prev);
         }
         return env;
     }
 
+    public <R> R runWithAttributedMethod(Env<AttrContext> env, JCMethodDecl tree, Function<JCBlock, R> attributedAction) {
+        JavaFileObject prevSource = log.useSource(env.toplevel.sourcefile);
+        try {
+            JCBlock dupTree = (JCBlock)deferredAttr.attribSpeculative(tree.body, env, statInfo,
+                    null, DeferredAttr.AttributionMode.ATTRIB_TO_TREE,
+                    argumentAttr.withLocalCacheContext());
+            return attributedAction.apply(dupTree);
+        } finally {
+            attrRecover.doRecovery();
+            log.useSource(prevSource);
+        }
+    }
+
     private JCTree breakTree = null;
 
     private static class BreakAttr extends RuntimeException {
         static final long serialVersionUID = -6924771130405446405L;
         private transient Env<AttrContext> env;
@@ -3113,10 +3132,18 @@
                 log.error(that.pos(), Errors.UnexpectedLambda);
             }
             resultInfo = recoveryInfo;
             wrongContext = true;
         }
+        if (types.isQuoted(pt())) {
+            attribQuotedLambda(that);
+        } else {
+            attribFunctionalLambda(that, wrongContext);
+        }
+    }
+
+    void attribFunctionalLambda(JCLambda that, boolean wrongContext) {
         //create an environment for attribution of the lambda expression
         final Env<AttrContext> localEnv = lambdaEnv(that, env);
         boolean needsRecovery =
                 resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.CHECK;
         try {
@@ -3256,10 +3283,103 @@
                     }
                 }
             }
         }
     }
+
+    void attribQuotedLambda(JCLambda that) {
+        // create an environment for attribution of the lambda expression
+        final Env<AttrContext> localEnv = lambdaEnv(that, env);
+        try {
+            // if quoted lambda is implicit, issue error, and recover
+            if (that.paramKind == ParameterKind.IMPLICIT) {
+                log.error(that, Errors.QuotedLambdaMustBeExplicit);
+                // recovery
+                List<JCVariableDecl> params = that.params;
+                while (params.nonEmpty()) {
+                    Type argType = syms.errType;
+                    if (params.head.isImplicitlyTyped()) {
+                        setSyntheticVariableType(params.head, argType);
+                    }
+                    params = params.tail;
+                }
+            }
+            // attribute lambda parameters
+            attribStats(that.params, localEnv);
+            List<Type> explicitParamTypes = TreeInfo.types(that.params);
+
+            ListBuffer<Type> restypes = new ListBuffer<>();
+            ListBuffer<DiagnosticPosition> resPositions = new ListBuffer<>();
+            ResultInfo bodyResultInfo = localEnv.info.returnResult = unknownExprInfo;
+
+            // type-check lambda body, and capture return types
+            if (that.getBodyKind() == JCLambda.BodyKind.EXPRESSION) {
+                attribTree(that.getBody(), localEnv, bodyResultInfo);
+                restypes.add(that.getBody().type);
+            } else {
+                JCBlock body = (JCBlock)that.body;
+                if (body == breakTree &&
+                        resultInfo.checkContext.deferredAttrContext().mode == AttrMode.CHECK) {
+                    breakTreeFound(copyEnv(localEnv));
+                }
+                attribStats(body.stats, localEnv);
+                new LambdaReturnScanner() {
+                    @Override
+                    public void visitReturn(JCReturn tree) {
+                        if (tree.expr != null) {
+                            resPositions.add(tree);
+                        }
+                        restypes.add(tree.expr == null ? syms.voidType : tree.expr.type);
+                    }
+                }.scan(body);
+            }
+
+            // check if lambda body can complete normally
+            preFlow(that);
+            flow.analyzeLambda(localEnv, that, make, false);
+
+            final Type restype;
+            if (that.getBodyKind() == BodyKind.STATEMENT) {
+                if (that.canCompleteNormally) {
+                    // a lambda that completes normally has an implicit void return
+                    restypes.add(syms.voidType);
+                }
+
+                boolean hasNonVoidReturn = restypes.toList()
+                        .stream().anyMatch(t -> t != syms.voidType);
+                boolean hasVoidReturn = restypes.toList()
+                        .stream().anyMatch(t -> t == syms.voidType);
+
+                if (hasVoidReturn && hasNonVoidReturn) {
+                    // void vs. non-void mismatch
+                    log.error(that.body, Errors.CantInferQuotedLambdaReturnType(restypes.toList()));
+                    restype = syms.errorType;
+                } else if (hasVoidReturn) {
+                    restype = syms.voidType;
+                } else {
+                    restype = condType(resPositions.toList(), restypes.toList());
+                }
+            } else {
+                restype = restypes.first();
+            }
+
+            // infer lambda return type using lub
+            if (restype.hasTag(ERROR)) {
+                // some other error occurred
+                log.error(that.body, Errors.CantInferQuotedLambdaReturnType(restypes.toList()));
+            }
+
+            // infer thrown types
+            List<Type> thrownTypes = flow.analyzeLambdaThrownTypes(localEnv, that, make);
+
+            // set up target descriptor with explicit parameter types, and inferred thrown/return types
+            that.target = new MethodType(explicitParamTypes, restype, thrownTypes, syms.methodClass);
+            result = that.type = pt();
+        } finally {
+            localEnv.info.scope.leave();
+        }
+    }
     //where
         class TargetInfo {
             Type target;
             Type descriptor;
 
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -832,11 +832,11 @@
 
             @Override
             public void visitLambda(JCLambda tree) {
                 Check.CheckContext checkContext = resultInfo.checkContext;
                 Type pt = resultInfo.pt;
-                if (!inferenceContext.inferencevars.contains(pt)) {
+                if (!types.isQuoted(pt) && !inferenceContext.inferencevars.contains(pt)) {
                     //must be a functional descriptor
                     Type descriptorType = null;
                     try {
                         descriptorType = types.findDescriptorType(pt);
                     } catch (Types.FunctionDescriptorLookupError ex) {
@@ -1224,11 +1224,11 @@
         @Override
         public void visitLambda(JCLambda tree) {
             if (inferenceContext.inferenceVars().contains(pt)) {
                 stuckVars.add(pt);
             }
-            if (!types.isFunctionalInterface(pt)) {
+            if (types.isQuoted(pt) || !types.isFunctionalInterface(pt)) {
                 return;
             }
             Type descType = types.findDescriptorType(pt);
             List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
             if (tree.paramKind == JCLambda.ParameterKind.IMPLICIT &&
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -136,10 +136,13 @@
     public static final int FLAG_MARKERS = 1 << 1;
 
     /** Flag for alternate metafactories indicating the lambda object requires multiple bridges */
     public static final int FLAG_BRIDGES = 1 << 2;
 
+    /** Flag for alternate metafactories indicating the lambda object is intended to be quotable */
+    public static final int FLAG_QUOTABLE = 1 << 3;
+
     // <editor-fold defaultstate="collapsed" desc="Instantiating">
     protected static final Context.Key<LambdaToMethod> unlambdaKey = new Context.Key<>();
 
     public static LambdaToMethod instance(Context context) {
         LambdaToMethod instance = context.get(unlambdaKey);
@@ -437,10 +440,16 @@
         for (Symbol fv : localContext.getSymbolMap(CAPTURED_OUTER_THIS).keySet()) {
             JCExpression captured_local = make.QualThis(fv.type);
             syntheticInits.append(captured_local);
         }
 
+        if (context.isQuotable()) {
+            for (JCExpression capturedArg : tree.codeReflectionInfo.capturedArgs()) {
+                syntheticInits.append(capturedArg);
+            }
+        }
+
         //then, determine the arguments to the indy call
         List<JCExpression> indy_args = translate(syntheticInits.toList(), localContext.prev);
 
         //convert to an invokedynamic call
         result = makeMetafactoryIndyCall(context, sym.asHandle(), indy_args);
@@ -518,10 +527,16 @@
                 throw new InternalError("Should not have an invalid kind");
         }
 
         List<JCExpression> indy_args = init==null? List.nil() : translate(List.of(init), localContext.prev);
 
+        if (context.isQuotable()) {
+            for (JCExpression capturedArg : tree.codeReflectionInfo.capturedArgs()) {
+                indy_args = indy_args.append(capturedArg);
+            }
+        }
+
 
         //build a sam instance using an indy call to the meta-factory
         result = makeMetafactoryIndyCall(localContext, refSym.asHandle(), indy_args);
     }
 
@@ -919,10 +934,11 @@
                 JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
                         ? expressionInvoke(rcvr)
                         : expressionNew();
 
                 JCLambda slam = make.Lambda(params.toList(), expr);
+                slam.codeReflectionInfo = tree.codeReflectionInfo;
                 slam.target = tree.target;
                 slam.type = tree.type;
                 slam.pos = tree.pos;
                 return slam;
             } finally {
@@ -1138,16 +1154,18 @@
                     types.directSupertypes(tree.target) :
                     List.nil();
             for (Type t : targets) {
                 t = types.erasure(t);
                 if (t.tsym != syms.serializableType.tsym &&
+                    t.tsym != syms.quotableType.tsym &&
                     t.tsym != tree.type.tsym &&
                     t.tsym != syms.objectType.tsym) {
                     markers.append(t);
                 }
             }
             int flags = context.isSerializable() ? FLAG_SERIALIZABLE : 0;
+            flags |= context.isQuotable() ? FLAG_QUOTABLE : 0;
             boolean hasMarkers = markers.nonEmpty();
             boolean hasBridges = context.bridges.nonEmpty();
             if (hasMarkers) {
                 flags |= FLAG_MARKERS;
             }
@@ -1166,10 +1184,14 @@
                     if (!types.isSameType(s_erasure, samSym.erasure(types))) {
                         staticArgs = staticArgs.append(((MethodType)s.erasure(types)));
                     }
                 }
             }
+            if (context.isQuotable()) {
+                VarSymbol reflectField = (VarSymbol)tree.codeReflectionInfo.quotedField();
+                staticArgs = staticArgs.append(reflectField.asMethodHandle(true));
+            }
             if (context.isSerializable()) {
                 int prevPos = make.pos;
                 try {
                     make.at(kInfo.clazz);
                     addDeserializationCase(refSym, tree.type, samSym,
@@ -1860,20 +1882,25 @@
 
             /** does this functional expression need to be created using alternate metafactory? */
             boolean needsAltMetafactory() {
                 return tree.target.isIntersection() ||
                         isSerializable() ||
+                        isQuotable() ||
                         bridges.length() > 1;
             }
 
             /** does this functional expression require serialization support? */
             boolean isSerializable() {
                 if (forceSerializable) {
                     return true;
                 }
                 return types.asSuper(tree.target, syms.serializableType.tsym) != null;
             }
+
+            boolean isQuotable() {
+                return tree.codeReflectionInfo != null;
+            }
 
             /**
              * @return Name of the enclosing method to be folded into synthetic
              * method name
              */
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ReflectMethods.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ReflectMethods.java
--- /dev/null
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ReflectMethods.java
@@ -0,0 +1,2635 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.javac.comp;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Kinds.Kind;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ArrayType;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.code.Type.MethodType;
+import com.sun.tools.javac.code.Type.TypeVar;
+import com.sun.tools.javac.code.Type.WildcardType;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.comp.DeferredAttr.FilterScanner;
+import com.sun.tools.javac.jvm.ByteCodes;
+import com.sun.tools.javac.jvm.Gen;
+import com.sun.tools.javac.resources.CompilerProperties.Notes;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
+import com.sun.tools.javac.tree.JCTree.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCBinary;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
+import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression.CodeReflectionInfo;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCLambda;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCReturn;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.JCAssert;
+import com.sun.tools.javac.tree.JCTree.Tag;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.TreeTranslator;
+import com.sun.tools.javac.util.Assert;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+import com.sun.tools.javac.util.ListBuffer;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
+import com.sun.tools.javac.util.Options;
+import jdk.internal.java.lang.reflect.code.*;
+import jdk.internal.java.lang.reflect.code.op.CoreOp;
+import jdk.internal.java.lang.reflect.code.op.ExtendedOp;
+import jdk.internal.java.lang.reflect.code.type.*;
+import jdk.internal.java.lang.reflect.code.type.WildcardType.BoundKind;
+
+import javax.lang.model.element.Modifier;
+import java.lang.constant.ClassDesc;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static com.sun.tools.javac.code.Flags.PARAMETER;
+import static com.sun.tools.javac.code.Flags.SYNTHETIC;
+import static com.sun.tools.javac.code.TypeTag.BOT;
+import static com.sun.tools.javac.code.TypeTag.METHOD;
+import static com.sun.tools.javac.code.TypeTag.NONE;
+import static com.sun.tools.javac.main.Option.G_CUSTOM;
+
+/**
+ * This a tree translator that adds the code model to all method declaration marked
+ * with the {@code CodeReflection} annotation. The model is expressed using the code
+ * reflection API (see jdk.internal.java.lang.reflect.code).
+ */
+public class ReflectMethods extends TreeTranslator {
+    protected static final Context.Key<ReflectMethods> reflectMethodsKey = new Context.Key<>();
+
+    public static ReflectMethods instance(Context context) {
+        ReflectMethods instance = context.get(reflectMethodsKey);
+        if (instance == null)
+            instance = new ReflectMethods(context);
+        return instance;
+    }
+
+    private final Types types;
+    private final Names names;
+    private final Symtab syms;
+    private final Gen gen;
+    private final Log log;
+    private final boolean dumpIR;
+    private final boolean lineDebugInfo;
+
+    // @@@ Separate out mutable state
+    private TreeMaker make;
+    private ListBuffer<JCTree> classOps;
+    // Also used by BodyScanner
+    private Symbol.ClassSymbol currentClassSym;
+    private int lambdaCount;
+
+    @SuppressWarnings("this-escape")
+    protected ReflectMethods(Context context) {
+        context.put(reflectMethodsKey, this);
+        Options options = Options.instance(context);
+        dumpIR = options.isSet("dumpIR");
+        lineDebugInfo =
+                options.isUnset(G_CUSTOM) ||
+                        options.isSet(G_CUSTOM, "lines");
+        names = Names.instance(context);
+        syms = Symtab.instance(context);
+        types = Types.instance(context);
+        gen = Gen.instance(context);
+        log = Log.instance(context);
+
+
+    }
+
+    // Cannot compute within constructor due to circular dependencies on bootstrap compilation
+    // syms.objectType == null
+    private Map<JavaType, Type> primitiveAndBoxTypeMap;
+    Map<JavaType, Type> primitiveAndBoxTypeMap() {
+        Map<JavaType, Type> m = primitiveAndBoxTypeMap;
+        if (m == null) {
+            m = primitiveAndBoxTypeMap = Map.ofEntries(
+                    Map.entry(JavaType.BOOLEAN, syms.booleanType),
+                    Map.entry(JavaType.BYTE, syms.byteType),
+                    Map.entry(JavaType.SHORT, syms.shortType),
+                    Map.entry(JavaType.CHAR, syms.charType),
+                    Map.entry(JavaType.INT, syms.intType),
+                    Map.entry(JavaType.LONG, syms.longType),
+                    Map.entry(JavaType.FLOAT, syms.floatType),
+                    Map.entry(JavaType.DOUBLE, syms.doubleType),
+                    Map.entry(JavaType.J_L_OBJECT, syms.objectType),
+                    Map.entry(JavaType.J_L_BOOLEAN, types.boxedTypeOrType(syms.booleanType)),
+                    Map.entry(JavaType.J_L_BYTE, types.boxedTypeOrType(syms.byteType)),
+                    Map.entry(JavaType.J_L_SHORT, types.boxedTypeOrType(syms.shortType)),
+                    Map.entry(JavaType.J_L_CHARACTER, types.boxedTypeOrType(syms.charType)),
+                    Map.entry(JavaType.J_L_INTEGER, types.boxedTypeOrType(syms.intType)),
+                    Map.entry(JavaType.J_L_LONG, types.boxedTypeOrType(syms.longType)),
+                    Map.entry(JavaType.J_L_FLOAT, types.boxedTypeOrType(syms.floatType)),
+                    Map.entry(JavaType.J_L_DOUBLE, types.boxedTypeOrType(syms.doubleType))
+            );
+        }
+        return m;
+    }
+
+    @Override
+    public void visitMethodDef(JCMethodDecl tree) {
+        if (tree.sym.attribute(syms.codeReflectionType.tsym) != null) {
+            // if the method is annotated, scan it
+            BodyScanner bodyScanner = new BodyScanner(tree);
+            try {
+                CoreOp.FuncOp funcOp = bodyScanner.scanMethod();
+                if (dumpIR) {
+                    // dump the method IR if requested
+                    log.note(Notes.MethodIrDump(tree.sym.enclClass(), tree.sym, funcOp.toText()));
+                }
+                // create a static final field holding the op' string text.
+                // The name of the field is foo$op, where 'foo' is the name of the corresponding method.
+                classOps.add(opFieldDecl(tree.name, tree.getModifiers().flags, funcOp));
+            } catch (UnsupportedASTException ex) {
+                // whoops, some AST node inside the method body were not supported. Log it and move on.
+                log.note(ex.tree, Notes.MethodIrSkip(tree.sym.enclClass(), tree.sym, ex.tree.getTag().toString()));
+            }
+        }
+        super.visitMethodDef(tree);
+    }
+
+    @Override
+    public void visitClassDef(JCClassDecl tree) {
+        ListBuffer<JCTree> prevClassOps = classOps;
+        Symbol.ClassSymbol prevClassSym = currentClassSym;
+        int prevLambdaCount = lambdaCount;
+        try {
+            lambdaCount = 0;
+            currentClassSym = tree.sym;
+            classOps = new ListBuffer<>();
+            super.visitClassDef(tree);
+            tree.defs = tree.defs.prependList(classOps.toList());
+        } finally {
+            lambdaCount = prevLambdaCount;
+            classOps = prevClassOps;
+            currentClassSym = prevClassSym;
+            result = tree;
+        }
+    }
+
+    @Override
+    public void visitLambda(JCLambda tree) {
+        FunctionalExpressionKind kind = functionalKind(tree);
+        if (kind.isQuoted) {
+            // quoted lambda - scan it
+            BodyScanner bodyScanner = new BodyScanner(tree, kind);
+            try {
+                CoreOp.FuncOp funcOp = bodyScanner.scanLambda();
+                if (dumpIR) {
+                    // dump the method IR if requested
+                    log.note(Notes.QuotedIrDump(funcOp.toText()));
+                }
+                // create a static final field holding the op' string text.
+                // The name of the field is foo$op, where 'foo' is the name of the corresponding method.
+                JCVariableDecl opField = opFieldDecl(lambdaName(), 0, funcOp);
+                classOps.add(opField);
+                ListBuffer<JCExpression> capturedArgs = quotedCapturedArgs(tree, bodyScanner, null);
+
+                switch (kind) {
+                    case QUOTED_STRUCTURAL -> {
+                        JCIdent opFieldId = make.Ident(opField.sym);
+                        ListBuffer<JCExpression> interpreterArgs = new ListBuffer<>();
+                        JCMethodInvocation parsedOp = make.App(make.Ident(syms.opParserFromString), com.sun.tools.javac.util.List.of(opFieldId));
+                        interpreterArgs.append(parsedOp);
+                        // append captured vars
+                        interpreterArgs.appendList(capturedArgs.toList());
+
+                        JCMethodInvocation interpreterInvoke = make.App(make.Ident(syms.opInterpreterInvoke), interpreterArgs.toList());
+                        interpreterInvoke.varargsElement = syms.objectType;
+                        super.visitLambda(tree);
+                        result = interpreterInvoke;
+                    }
+                    case QUOTABLE -> {
+                        // leave the lambda in place, but also leave a trail for LambdaToMethod
+                        tree.codeReflectionInfo = new CodeReflectionInfo(opField.sym, capturedArgs.toList());
+                        super.visitLambda(tree);
+                    }
+                }
+            } catch (UnsupportedASTException ex) {
+                // whoops, some AST node inside the quoted lambda body were not supported. Log it and move on.
+                log.note(ex.tree, Notes.QuotedIrSkip(ex.tree.getTag().toString()));
+                result = tree;
+            }
+        } else {
+            super.visitLambda(tree);
+        }
+    }
+
+    @Override
+    public void visitReference(JCMemberReference tree) {
+        FunctionalExpressionKind kind = functionalKind(tree);
+        Assert.check(kind != FunctionalExpressionKind.QUOTED_STRUCTURAL,
+                "structural quoting not supported for method references");
+        MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
+        JCVariableDecl recvDecl = memberReferenceToLambda.receiverVar();
+        JCLambda lambdaTree = memberReferenceToLambda.lambda();
+
+        if (kind.isQuoted) {
+            // quoted lambda - scan it
+            BodyScanner bodyScanner = new BodyScanner(lambdaTree, kind);
+            try {
+                CoreOp.FuncOp funcOp = bodyScanner.scanLambda();
+                if (dumpIR) {
+                    // dump the method IR if requested
+                    log.note(Notes.QuotedIrDump(funcOp.toText()));
+                }
+                // create a static final field holding the op' string text.
+                // The name of the field is foo$op, where 'foo' is the name of the corresponding method.
+                JCVariableDecl opField = opFieldDecl(lambdaName(), 0, funcOp);
+                classOps.add(opField);
+                ListBuffer<JCExpression> capturedArgs = quotedCapturedArgs(tree, bodyScanner, recvDecl);
+                tree.codeReflectionInfo = new CodeReflectionInfo(opField.sym, capturedArgs.toList());
+                super.visitReference(tree);
+                if (recvDecl != null) {
+                    result = copyReferenceWithReceiverVar(tree, recvDecl);
+                }
+            } catch (UnsupportedASTException ex) {
+                // whoops, some AST node inside the quoted lambda body were not supported. Log it and move on.
+                log.note(ex.tree, Notes.QuotedIrSkip(ex.tree.getTag().toString()));
+                result = tree;
+            }
+        } else {
+            super.visitReference(tree);
+        }
+    }
+
+    ListBuffer<JCExpression> quotedCapturedArgs(DiagnosticPosition pos, BodyScanner bodyScanner, JCVariableDecl recvDecl) {
+        ListBuffer<JCExpression> capturedArgs = new ListBuffer<>();
+        for (Symbol capturedSym : bodyScanner.stack.localToOp.keySet()) {
+            if (capturedSym.kind == Kind.TYP) {
+                // captured this
+                capturedArgs.add(make.at(pos).This(capturedSym.type));
+            } else if (recvDecl != null && capturedSym == recvDecl.sym) {
+                // captured method reference receiver
+                capturedArgs.add(make.at(pos).Ident(recvDecl.sym));
+            } else if (capturedSym.kind == Kind.VAR) {
+                // captured var
+                VarSymbol var = (VarSymbol)capturedSym;
+                if (var.getConstValue() == null) {
+                    capturedArgs.add(make.at(pos).Ident(capturedSym));
+                } else {
+                    capturedArgs.add(make.at(pos).Literal(var.getConstValue()));
+                }
+            } else {
+                throw new AssertionError("Unexpected captured symbol: " + capturedSym);
+            }
+        }
+        return capturedArgs;
+    }
+
+    /*
+     * Creates a let expression of the kind:
+     * let $recv in $recv::memberRef
+     *
+     * This is required to make sure that LambdaToMethod doesn't end up emitting the
+     * code for capturing the bound method reference receiver twice.
+     */
+    JCExpression copyReferenceWithReceiverVar(JCMemberReference ref, JCVariableDecl recvDecl) {
+        JCMemberReference newRef = make.at(ref).Reference(ref.mode, ref.name, make.Ident(recvDecl.sym), ref.typeargs);
+        newRef.type = ref.type;
+        newRef.target = ref.target;
+        newRef.refPolyKind = ref.refPolyKind;
+        newRef.referentType = ref.referentType;
+        newRef.kind = ref.kind;
+        newRef.varargsElement = ref.varargsElement;
+        newRef.ownerAccessible = ref.ownerAccessible;
+        newRef.sym = ref.sym;
+        newRef.codeReflectionInfo = ref.codeReflectionInfo;
+        return make.at(ref).LetExpr(recvDecl, newRef).setType(newRef.type);
+    }
+
+    Name lambdaName() {
+        return names.fromString("lambda").append('$', names.fromString(String.valueOf(lambdaCount++)));
+    }
+
+    private JCVariableDecl opFieldDecl(Name prefix, long flags, CoreOp.FuncOp op) {
+        VarSymbol opFieldSym = new VarSymbol(flags | Flags.STATIC | Flags.FINAL | Flags.SYNTHETIC,
+                prefix.append('$', names.fromString("op")),
+                syms.stringType,
+                currentClassSym);
+
+        currentClassSym.members().enter(opFieldSym);
+        JCLiteral opText = make.Literal(op.toText());
+        JCVariableDecl opFieldTree = make.VarDef(opFieldSym, opText);
+        return opFieldTree;
+    }
+
+    public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
+        // note that this method does NOT support recursion.
+        this.make = make;
+        JCTree res = translate(cdef);
+        return res;
+    }
+
+    public CoreOp.FuncOp getMethodBody(Symbol.ClassSymbol classSym, JCMethodDecl methodDecl, JCBlock attributedBody, TreeMaker make) {
+        // if the method is annotated, scan it
+        // Called from JavacElements::getBody
+        try {
+            this.make = make;
+            currentClassSym = classSym;
+            BodyScanner bodyScanner = new BodyScanner(methodDecl, attributedBody);
+            return bodyScanner.scanMethod();
+        } finally {
+            currentClassSym = null;
+            this.make = null;
+        }
+    }
+
+    static class BodyStack {
+        final BodyStack parent;
+
+        // Tree associated with body
+        final JCTree tree;
+
+        // Body to add blocks
+        final Body.Builder body;
+        // Current block to add operations
+        Block.Builder block;
+
+        // Map of symbols (method arguments and local variables) to varOp values
+        final Map<Symbol, Value> localToOp;
+
+        // Label
+        Map.Entry<String, Op.Result> label;
+
+        BodyStack(BodyStack parent, JCTree tree, FunctionType bodyType) {
+            this.parent = parent;
+
+            this.tree = tree;
+
+            this.body = Body.Builder.of(parent != null ? parent.body : null, bodyType);
+            this.block = body.entryBlock();
+
+            this.localToOp = new LinkedHashMap<>(); // order is important for captured values
+        }
+
+        public void setLabel(String labelName, Op.Result labelValue) {
+            if (label != null) {
+                throw new IllegalStateException("Label already defined: " + labelName);
+            }
+            label = Map.entry(labelName, labelValue);
+        }
+    }
+
+    class BodyScanner extends FilterScanner {
+        private final JCTree body;
+        private final Name name;
+        private final BodyStack top;
+        private BodyStack stack;
+        private Op lastOp;
+        private Value result;
+        private Type pt = Type.noType;
+        private boolean isQuoted;
+        private Type bodyTarget;
+        private JCTree currentNode;
+
+        // Only few AST nodes supported for now
+        private static final Set<JCTree.Tag> SUPPORTED_TAGS =
+                Set.of(Tag.VARDEF,
+                        Tag.RETURN, Tag.THROW, Tag.BREAK, Tag.CONTINUE,
+                        Tag.PLUS, Tag.MINUS, Tag.MUL, Tag.DIV, Tag.MOD,
+                        Tag.NEG, Tag.NOT,
+                        Tag.BITOR, Tag.BITAND, Tag.BITXOR,
+                        Tag.BITOR_ASG, Tag.BITAND_ASG, Tag.BITXOR_ASG,
+                        Tag.SL, Tag.SR, Tag.USR,
+                        Tag.SL_ASG, Tag.SR_ASG, Tag.USR_ASG,
+                        Tag.PLUS_ASG, Tag.MINUS_ASG, Tag.MUL_ASG, Tag.DIV_ASG, Tag.MOD_ASG,
+                        Tag.POSTINC, Tag.PREINC, Tag.POSTDEC, Tag.PREDEC,
+                        Tag.EQ, Tag.NE, Tag.LT, Tag.LE, Tag.GT, Tag.GE,
+                        Tag.AND, Tag.OR,
+                        Tag.LITERAL, Tag.IDENT, Tag.TYPEIDENT, Tag.ASSIGN, Tag.EXEC, Tag.PARENS,
+                        Tag.SELECT, Tag.INDEXED, Tag.APPLY,
+                        Tag.TYPECAST, Tag.TYPETEST,
+                        Tag.NEWCLASS, Tag.NEWARRAY, Tag.LAMBDA, Tag.REFERENCE,
+                        Tag.BLOCK, Tag.IF, Tag.WHILELOOP, Tag.DOLOOP, Tag.FOREACHLOOP, Tag.FORLOOP, Tag.TRY,
+                        Tag.SWITCH_EXPRESSION, Tag.YIELD,
+                        Tag.CONDEXPR,
+                        Tag.ASSERT,
+                        Tag.LABELLED,
+                        Tag.SKIP,
+                        Tag.TYPEARRAY);
+
+        BodyScanner(JCMethodDecl tree) {
+            this(tree, tree.body);
+        }
+
+        BodyScanner(JCMethodDecl tree, JCBlock body) {
+            super(SUPPORTED_TAGS);
+
+            this.currentNode = tree;
+            this.body = body;
+            this.name = tree.name;
+            this.isQuoted = false;
+
+            List<TypeElement> parameters = new ArrayList<>();
+            int blockArgOffset = 0;
+            // Instance methods model "this" as an additional argument occurring
+            // before all other arguments.
+            // @@@ Inner classes.
+            // We need to capture all "this", in nested order, as arguments.
+            if (!tree.getModifiers().getFlags().contains(Modifier.STATIC)) {
+                parameters.add(typeToTypeElement(tree.sym.owner.type));
+                blockArgOffset++;
+            }
+            tree.sym.type.getParameterTypes().stream().map(this::typeToTypeElement).forEach(parameters::add);
+
+            FunctionType bodyType = FunctionType.functionType(
+                    typeToTypeElement(tree.sym.type.getReturnType()), parameters);
+
+            this.stack = this.top = new BodyStack(null, tree.body, bodyType);
+
+            // @@@ this as local variable? (it can never be stored to)
+            for (int i = 0 ; i < tree.params.size() ; i++) {
+                Op.Result paramOp = append(CoreOp.var(
+                        tree.params.get(i).name.toString(),
+                        top.block.parameters().get(blockArgOffset + i)));
+                top.localToOp.put(tree.params.get(i).sym, paramOp);
+            }
+
+            bodyTarget = tree.sym.type.getReturnType();
+        }
+
+        BodyScanner(JCLambda tree, FunctionalExpressionKind kind) {
+            super(SUPPORTED_TAGS);
+            assert kind != FunctionalExpressionKind.NOT_QUOTED;
+
+            this.currentNode = tree;
+            this.body = tree;
+            this.name = names.fromString("quotedLambda");
+            this.isQuoted = true;
+
+            com.sun.tools.javac.util.List<Type> nil = com.sun.tools.javac.util.List.nil();
+            MethodType mtype = new MethodType(nil, syms.quotedType, nil, syms.methodClass);
+            FunctionType mtDesc = FunctionType.functionType(typeToTypeElement(mtype.restype),
+                    mtype.getParameterTypes().map(this::typeToTypeElement));
+
+            this.stack = this.top = new BodyStack(null, tree.body, mtDesc);
+
+            bodyTarget = tree.target.getReturnType();
+        }
+
+        @Override
+        public void scan(JCTree tree) {
+            JCTree prev = currentNode;
+            currentNode = tree;
+            try {
+                super.scan(tree);
+            } finally {
+                currentNode = prev;
+            }
+        }
+
+        void pushBody(JCTree tree, FunctionType bodyType) {
+            stack = new BodyStack(stack, tree, bodyType);
+            lastOp = null; // reset
+        }
+
+        void popBody() {
+            stack = stack.parent;
+        }
+
+        Value varOpValue(Symbol sym) {
+            BodyStack s = stack;
+            while (s != null) {
+                Value v = s.localToOp.get(sym);
+                if (v != null) {
+                    return v;
+                }
+                s = s.parent;
+            }
+            if (isQuoted) {
+                return capturedOpValue(sym);
+            } else {
+                throw new NoSuchElementException(sym.toString());
+            }
+        }
+
+        Value capturedOpValue(Symbol sym) {
+            var capturedVar = top.localToOp.get(sym);
+            if (capturedVar == null) {
+                var capturedArg = top.block.parameter(typeToTypeElement(sym.type));
+                capturedVar = top.block.op(CoreOp.var(sym.name.toString(), capturedArg));
+                top.localToOp.put(sym, capturedVar);
+            }
+            return capturedVar;
+        }
+
+        Value thisValue() { // @@@: outer this?
+            if (isQuoted) {
+                // capture this - add captured class symbol to the stack top local mappings
+                var capturedThis = top.localToOp.get(currentClassSym);
+                if (capturedThis == null) {
+                    capturedThis = top.block.parameter(typeToTypeElement(currentClassSym.type));
+                    top.localToOp.put(currentClassSym, capturedThis);
+                }
+                return capturedThis;
+            } else {
+                return top.block.parameters().get(0);
+            }
+        }
+
+        Value getLabel(String labelName) {
+            BodyStack s = stack;
+            while (s != null) {
+                if (s.label != null && s.label.getKey().equals(labelName)) {
+                    return s.label.getValue();
+                }
+                s = s.parent;
+            }
+            throw new NoSuchElementException(labelName);
+        }
+
+        private Op.Result append(Op op) {
+            return append(op, generateLocation(currentNode, false), stack);
+        }
+
+        private Op.Result append(Op op, Location l) {
+            return append(op, l, stack);
+        }
+
+        private Op.Result append(Op op, Location l, BodyStack stack) {
+            lastOp = op;
+            op.setLocation(l);
+            return stack.block.apply(op);
+        }
+
+        Location generateLocation(JCTree node, boolean includeSourceReference) {
+            if (!lineDebugInfo) {
+                return Location.NO_LOCATION;
+            }
+
+            int pos = node.getStartPosition();
+            int line = log.currentSource().getLineNumber(pos);
+            int col = log.currentSource().getColumnNumber(pos, false);
+            String path;
+            if (includeSourceReference) {
+                path = log.currentSource().getFile().toUri().toString();
+            } else {
+                path = null;
+            }
+            return new Location(path, line, col);
+        }
+
+        private <O extends Op & Op.Terminating> void appendTerminating(Supplier<O> sop) {
+            // Append only if an existing terminating operation is not present
+            if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
+                append(sop.get());
+            }
+        }
+
+        public Value toValue(JCTree tree) {
+            return toValue(tree, Type.noType);
+        }
+
+        public Value toValue(JCTree tree, Type target) {
+            result = null; // reset
+            Type prevPt = pt;
+            try {
+                pt = target;
+                scan(tree);
+                return result != null ?
+                        coerce(result, tree.type, target) :
+                        null;
+            } finally {
+                pt = prevPt;
+            }
+        }
+
+        Value coerce(Value sourceValue, Type sourceType, Type targetType) {
+            if (sourceType.isReference() && targetType.isReference() &&
+                    !types.isSubtype(types.erasure(sourceType), types.erasure(targetType))) {
+                return append(CoreOp.cast(typeToTypeElement(targetType), sourceValue));
+            } else {
+                return convert(sourceValue, targetType);
+            }
+        }
+
+        Value boxIfNeeded(Value exprVal) {
+            Type source = typeElementToType(exprVal.type());
+            return source.hasTag(NONE) ?
+                    exprVal : convert(exprVal, types.boxedTypeOrType(source));
+        }
+
+        Value unboxIfNeeded(Value exprVal) {
+            Type source = typeElementToType(exprVal.type());
+            return source.hasTag(NONE) ?
+                    exprVal : convert(exprVal, types.unboxedTypeOrType(source));
+        }
+
+        Value convert(Value exprVal, Type target) {
+            Type source = typeElementToType(exprVal.type());
+            boolean sourcePrimitive = source.isPrimitive();
+            boolean targetPrimitive = target.isPrimitive();
+            if (target.hasTag(NONE)) {
+                return exprVal;
+            } else if (sourcePrimitive == targetPrimitive) {
+                if (!sourcePrimitive || types.isSameType(source, target)) {
+                    return exprVal;
+                } else {
+                    // implicit primitive conversion
+                    return append(CoreOp.conv(typeToTypeElement(target), exprVal));
+                }
+            } else if (sourcePrimitive) {
+                // we need to box
+                Type unboxedTarget = types.unboxedType(target);
+                if (!unboxedTarget.hasTag(NONE)) {
+                    // non-Object target
+                    return box(exprVal, target);
+                } else {
+                    // Object target
+                    return box(exprVal, types.boxedClass(source).type);
+                }
+            } else {
+                // we need to unbox
+                return unbox(exprVal, source, target, types.unboxedType(source));
+            }
+        }
+
+        Value box(Value valueExpr, Type box) {
+            // Boxing is a static method e.g., java.lang.Integer::valueOf(int)java.lang.Integer
+            MethodRef boxMethod = MethodRef.method(typeToTypeElement(box), names.valueOf.toString(),
+                    FunctionType.functionType(typeToTypeElement(box), typeToTypeElement(types.unboxedType(box))));
+            return append(CoreOp.invoke(boxMethod, valueExpr));
+        }
+
+        Value unbox(Value valueExpr, Type box, Type primitive, Type unboxedType) {
+            if (unboxedType.hasTag(NONE)) {
+                // Object target, first downcast to correct wrapper type
+                unboxedType = primitive;
+                box = types.boxedClass(unboxedType).type;
+                valueExpr = append(CoreOp.cast(typeToTypeElement(box), valueExpr));
+            }
+            // Unboxing is a virtual method e.g., java.lang.Integer::intValue()int
+            MethodRef unboxMethod = MethodRef.method(typeToTypeElement(box),
+                    unboxedType.tsym.name.append(names.Value).toString(),
+                    FunctionType.functionType(typeToTypeElement(unboxedType)));
+            return append(CoreOp.invoke(unboxMethod, valueExpr));
+        }
+
+        @Override
+        void skip(JCTree tree) {
+            // this method is called for unsupported AST nodes (see 'SUPPORTED_TAGS')
+            throw unsupported(tree);
+        }
+
+        @Override
+        public void visitVarDef(JCVariableDecl tree) {
+            Value initOp;
+            if (tree.init != null) {
+                initOp = toValue(tree.init, tree.type);
+            } else {
+                initOp = append(defaultValue(tree.type));
+            }
+            result = append(CoreOp.var(tree.name.toString(), typeToTypeElement(tree.type), initOp));
+            stack.localToOp.put(tree.sym, result);
+        }
+
+        @Override
+        public void visitAssign(JCAssign tree) {
+            // Consume top node that applies to write access
+            JCTree lhs = TreeInfo.skipParens(tree.lhs);
+            Type target = tree.lhs.type;
+            switch (lhs.getTag()) {
+                case IDENT: {
+                    JCIdent assign = (JCIdent) lhs;
+
+                    // Scan the rhs, the assign expression result is its input
+                    result = toValue(tree.rhs, target);
+
+                    Symbol sym = assign.sym;
+                    switch (sym.getKind()) {
+                        case LOCAL_VARIABLE, PARAMETER -> {
+                            Value varOp = varOpValue(sym);
+                            append(CoreOp.varStore(varOp, result));
+                        }
+                        case FIELD -> {
+                            FieldRef fd = symbolToFieldRef(sym, symbolSiteType(sym));
+                            if (sym.isStatic()) {
+                                append(CoreOp.fieldStore(fd, result));
+                            } else {
+                                append(CoreOp.fieldStore(fd, thisValue(), result));
+                            }
+                        }
+                        default -> {
+                            // @@@ Cannot reach here?
+                            throw unsupported(tree);
+                        }
+                    }
+                    break;
+                }
+                case SELECT: {
+                    JCFieldAccess assign = (JCFieldAccess) lhs;
+
+                    Value receiver = toValue(assign.selected);
+
+                    // Scan the rhs, the assign expression result is its input
+                    result = toValue(tree.rhs, target);
+
+                    Symbol sym = assign.sym;
+                    FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
+                    if (sym.isStatic()) {
+                        append(CoreOp.fieldStore(fr, result));
+                    } else {
+                        append(CoreOp.fieldStore(fr, receiver, result));
+                    }
+                    break;
+                }
+                case INDEXED: {
+                    JCArrayAccess assign = (JCArrayAccess) lhs;
+
+                    Value array = toValue(assign.indexed);
+                    Value index = toValue(assign.index);
+
+                    // Scan the rhs, the assign expression result is its input
+                    result = toValue(tree.rhs, target);
+
+                    append(CoreOp.arrayStoreOp(array, index, result));
+                    break;
+                }
+                default:
+                    throw unsupported(tree);
+            }
+        }
+
+        @Override
+        public void visitAssignop(JCTree.JCAssignOp tree) {
+            // Capture applying rhs and operation
+            Function<Value, Value> scanRhs = (lhs) -> {
+                Type unboxedType = types.unboxedTypeOrType(tree.type);
+                Value rhs = toValue(tree.rhs, unboxedType);
+                lhs = unboxIfNeeded(lhs);
+
+                Value assignOpResult = switch (tree.getTag()) {
+
+                    // Arithmetic operations
+                    case PLUS_ASG -> append(CoreOp.add(lhs, rhs));
+                    case MINUS_ASG -> append(CoreOp.sub(lhs, rhs));
+                    case MUL_ASG -> append(CoreOp.mul(lhs, rhs));
+                    case DIV_ASG -> append(CoreOp.div(lhs, rhs));
+                    case MOD_ASG -> append(CoreOp.mod(lhs, rhs));
+
+                    // Bitwise operations (including their boolean variants)
+                    case BITOR_ASG -> append(CoreOp.or(lhs, rhs));
+                    case BITAND_ASG -> append(CoreOp.and(lhs, rhs));
+                    case BITXOR_ASG -> append(CoreOp.xor(lhs, rhs));
+
+                    // Shift operations
+                    case SL_ASG -> append(CoreOp.lshl(lhs, rhs));
+                    case SR_ASG -> append(CoreOp.ashr(lhs, rhs));
+                    case USR_ASG -> append(CoreOp.lshr(lhs, rhs));
+
+
+                    default -> throw unsupported(tree);
+                };
+                return result = convert(assignOpResult, tree.type);
+            };
+
+            applyCompoundAssign(tree.lhs, scanRhs);
+        }
+
+        void applyCompoundAssign(JCTree.JCExpression lhs, Function<Value, Value> scanRhs) {
+            // Consume top node that applies to access
+            lhs = TreeInfo.skipParens(lhs);
+            switch (lhs.getTag()) {
+                case IDENT -> {
+                    JCIdent assign = (JCIdent) lhs;
+
+                    Symbol sym = assign.sym;
+                    switch (sym.getKind()) {
+                        case LOCAL_VARIABLE, PARAMETER -> {
+                            Value varOp = varOpValue(sym);
+
+                            Op.Result lhsOpValue = append(CoreOp.varLoad(varOp));
+                            // Scan the rhs
+                            Value r = scanRhs.apply(lhsOpValue);
+
+                            append(CoreOp.varStore(varOp, r));
+                        }
+                        case FIELD -> {
+                            FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
+
+                            Op.Result lhsOpValue;
+                            TypeElement resultType = typeToTypeElement(sym.type);
+                            if (sym.isStatic()) {
+                                lhsOpValue = append(CoreOp.fieldLoad(resultType, fr));
+                            } else {
+                                lhsOpValue = append(CoreOp.fieldLoad(resultType, fr, thisValue()));
+                            }
+                            // Scan the rhs
+                            Value r = scanRhs.apply(lhsOpValue);
+
+                            if (sym.isStatic()) {
+                                append(CoreOp.fieldStore(fr, r));
+                            } else {
+                                append(CoreOp.fieldStore(fr, thisValue(), r));
+                            }
+                        }
+                        default -> {
+                            // @@@ Cannot reach here?
+                            throw unsupported(lhs);
+                        }
+                    }
+                }
+                case SELECT -> {
+                    JCFieldAccess assign = (JCFieldAccess) lhs;
+
+                    Value receiver = toValue(assign.selected);
+
+                    Symbol sym = assign.sym;
+                    FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
+
+                    Op.Result lhsOpValue;
+                    TypeElement resultType = typeToTypeElement(sym.type);
+                    if (sym.isStatic()) {
+                        lhsOpValue = append(CoreOp.fieldLoad(resultType, fr));
+                    } else {
+                        lhsOpValue = append(CoreOp.fieldLoad(resultType, fr, receiver));
+                    }
+                    // Scan the rhs
+                    Value r = scanRhs.apply(lhsOpValue);
+
+                    if (sym.isStatic()) {
+                        append(CoreOp.fieldStore(fr, r));
+                    } else {
+                        append(CoreOp.fieldStore(fr, receiver, r));
+                    }
+                }
+                case INDEXED -> {
+                    JCArrayAccess assign = (JCArrayAccess) lhs;
+
+                    Value array = toValue(assign.indexed);
+                    Value index = toValue(assign.index);
+
+                    Op.Result lhsOpValue = append(CoreOp.arrayLoadOp(array, index));
+                    // Scan the rhs
+                    Value r = scanRhs.apply(lhsOpValue);
+
+                    append(CoreOp.arrayStoreOp(array, index, r));
+                }
+                default -> throw unsupported(lhs);
+            }
+        }
+
+        @Override
+        public void visitIdent(JCIdent tree) {
+            // Visited only for read access
+
+            Symbol sym = tree.sym;
+            switch (sym.getKind()) {
+                case LOCAL_VARIABLE, RESOURCE_VARIABLE, BINDING_VARIABLE, PARAMETER, EXCEPTION_PARAMETER -> {
+                    Value varOp = varOpValue(sym);
+                    if (varOp.type() instanceof VarType) {
+                        // regular var
+                        result = append(CoreOp.varLoad(varOp));
+                    } else {
+                        // captured value
+                        result = varOp;
+                    }
+                }
+                case FIELD, ENUM_CONSTANT -> {
+                    if (sym.name.equals(names._this)) {
+                        result = thisValue();
+                    } else {
+                        FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
+                        TypeElement resultType = typeToTypeElement(sym.type);
+                        if (sym.isStatic()) {
+                            result = append(CoreOp.fieldLoad(resultType, fr));
+                        } else {
+                            result = append(CoreOp.fieldLoad(resultType, fr, thisValue()));
+                        }
+                    }
+                }
+                case INTERFACE, CLASS, ENUM -> {
+                    result = null;
+                }
+                default -> {
+                    // @@@ Cannot reach here?
+                    throw unsupported(tree);
+                }
+            }
+        }
+
+        @Override
+        public void visitTypeIdent(JCTree.JCPrimitiveTypeTree tree) {
+            result = null;
+        }
+
+        @Override
+        public void visitTypeArray(JCTree.JCArrayTypeTree tree) {
+            result = null; // MyType[].class is handled in visitSelect just as MyType.class
+        }
+
+        @Override
+        public void visitSelect(JCFieldAccess tree) {
+            // Visited only for read access
+
+            Type qualifierTarget = qualifierTarget(tree);
+            // @@@: might cause redundant load if accessed symbol is static but the qualifier is not a type
+            Value receiver = toValue(tree.selected);
+
+            if (tree.name.equals(names._class)) {
+                result = append(CoreOp.constant(JavaType.J_L_CLASS, typeToTypeElement(tree.selected.type)));
+            } else if (types.isArray(tree.selected.type)) {
+                if (tree.sym.equals(syms.lengthVar)) {
+                    result = append(CoreOp.arrayLength(receiver));
+                } else {
+                    // Should not reach here
+                    throw unsupported(tree);
+                }
+            } else {
+                Symbol sym = tree.sym;
+                switch (sym.getKind()) {
+                    case FIELD, ENUM_CONSTANT -> {
+                        FieldRef fr = symbolToFieldRef(sym, qualifierTarget.hasTag(NONE) ?
+                                tree.selected.type : qualifierTarget);
+                        TypeElement resultType = typeToTypeElement(types.memberType(tree.selected.type, sym));
+                        if (sym.isStatic()) {
+                            result = append(CoreOp.fieldLoad(resultType, fr));
+                        } else {
+                            result = append(CoreOp.fieldLoad(resultType, fr, receiver));
+                        }
+                    }
+                    case INTERFACE, CLASS, ENUM -> {
+                        result = null;
+                    }
+                    default -> {
+                        // @@@ Cannot reach here?
+                        throw unsupported(tree);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void visitIndexed(JCArrayAccess tree) {
+            // Visited only for read access
+
+            Value array = toValue(tree.indexed);
+
+            Value index = toValue(tree.index);
+
+            result = append(CoreOp.arrayLoadOp(array, index));
+        }
+
+        @Override
+        public void visitApply(JCTree.JCMethodInvocation tree) {
+            // @@@ Symbol.externalType, for use with inner classes
+
+            // @@@ this.xyz(...) calls in a constructor
+
+            // @@@ super.xyz(...) calls
+            // Modeling with a call operation would result in the receiver type differing from that
+            // in the method reference, perhaps that is sufficient?
+
+            JCTree meth = TreeInfo.skipParens(tree.meth);
+            switch (meth.getTag()) {
+                case IDENT: {
+                    JCIdent access = (JCIdent) meth;
+
+                    Symbol sym = access.sym;
+                    List<Value> args = new ArrayList<>();
+                    if (!sym.isStatic()) {
+                        args.add(thisValue());
+                    }
+
+                    args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
+
+                    MethodRef mr = symbolToErasedMethodRef(sym, symbolSiteType(sym));
+                    Value res = append(CoreOp.invoke(typeToTypeElement(meth.type.getReturnType()), mr, args));
+                    if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
+                        result = res;
+                    }
+                    break;
+                }
+                case SELECT: {
+                    JCFieldAccess access = (JCFieldAccess) meth;
+
+                    Type qualifierTarget = qualifierTarget(access);
+                    Value receiver = toValue(access.selected, qualifierTarget);
+
+                    Symbol sym = access.sym;
+                    List<Value> args = new ArrayList<>();
+                    if (!sym.isStatic()) {
+                        args.add(receiver);
+                    }
+
+                    args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
+
+                    MethodRef mr = symbolToErasedMethodRef(sym, qualifierTarget.hasTag(NONE) ?
+                            access.selected.type : qualifierTarget);
+                    Value res = append(CoreOp.invoke(typeToTypeElement(meth.type.getReturnType()), mr, args));
+                    if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
+                        result = res;
+                    }
+                    break;
+                }
+                default:
+                    unsupported(meth);
+            }
+        }
+
+        List<Value> scanMethodArguments(List<JCExpression> args, Type methodType, Type varargsElement) {
+            ListBuffer<Value> argValues = new ListBuffer<>();
+            com.sun.tools.javac.util.List<Type> targetTypes = methodType.getParameterTypes();
+            if (varargsElement != null) {
+                targetTypes = targetTypes.reverse().tail;
+                for (int i = 0 ; i < args.size() - (methodType.getParameterTypes().size() - 1) ; i++) {
+                    targetTypes = targetTypes.prepend(varargsElement);
+                }
+                targetTypes = targetTypes.reverse();
+            }
+
+            for (JCTree.JCExpression arg : args) {
+                argValues.add(toValue(arg, targetTypes.head));
+                targetTypes = targetTypes.tail;
+            }
+            return argValues.toList();
+        }
+
+        @Override
+        public void visitReference(JCTree.JCMemberReference tree) {
+            MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
+            JCVariableDecl recv = memberReferenceToLambda.receiverVar();
+            if (recv != null) {
+                scan(recv);
+            }
+            scan(memberReferenceToLambda.lambda());
+        }
+
+        Type qualifierTarget(JCFieldAccess tree) {
+            Type selectedType = types.skipTypeVars(tree.selected.type, true);
+            return selectedType.isCompound() ?
+                    tree.sym.owner.type :
+                    Type.noType;
+        }
+
+        @Override
+        public void visitTypeCast(JCTree.JCTypeCast tree) {
+            Value v = toValue(tree.expr);
+
+            Type expressionType = tree.expr.type;
+            Type type = tree.type;
+            if (expressionType.isPrimitive() && type.isPrimitive()) {
+                if (expressionType.equals(type)) {
+                    // Redundant cast
+                    result = v;
+                } else {
+                    result = append(CoreOp.conv(typeToTypeElement(type), v));
+                }
+            } else if (expressionType.isPrimitive() || type.isPrimitive()) {
+                result = convert(v, tree.type);
+            } else if (!expressionType.hasTag(BOT) &&
+                    types.isAssignable(expressionType, type)) {
+                // Redundant cast
+                result = v;
+            } else {
+                // Reference cast
+                JavaType jt = typeToTypeElement(types.erasure(type));
+                result = append(CoreOp.cast(typeToTypeElement(type), jt, v));
+            }
+        }
+
+        @Override
+        public void visitTypeTest(JCTree.JCInstanceOf tree) {
+            Value target = toValue(tree.expr);
+
+            if (tree.pattern.getTag() != Tag.IDENT) {
+                result = scanPattern(tree.getPattern(), target);
+            } else {
+                result = append(CoreOp.instanceOf(typeToTypeElement(tree.pattern.type), target));
+            }
+        }
+
+        Value scanPattern(JCTree.JCPattern pattern, Value target) {
+            // Type of pattern
+            JavaType patternType;
+            if (pattern instanceof JCTree.JCBindingPattern p) {
+                patternType = ExtendedOp.Pattern.bindingType(typeToTypeElement(p.type));
+            } else if (pattern instanceof JCTree.JCRecordPattern p) {
+                patternType = ExtendedOp.Pattern.recordType(typeToTypeElement(p.record.type));
+            } else {
+                throw unsupported(pattern);
+            }
+
+            // Push pattern body
+            pushBody(pattern, FunctionType.functionType(patternType));
+
+            // @@@ Assumes just pattern nodes, likely will change when method patterns are supported
+            //     that have expressions for any arguments (which perhaps in turn may have pattern expressions)
+            List<JCVariableDecl> variables = new ArrayList<>();
+            class PatternScanner extends FilterScanner {
+
+                private Value result;
+
+                public PatternScanner() {
+                    super(Set.of(Tag.BINDINGPATTERN, Tag.RECORDPATTERN));
+                }
+
+                @Override
+                public void visitBindingPattern(JCTree.JCBindingPattern binding) {
+                    JCVariableDecl var = binding.var;
+                    variables.add(var);
+
+                    result = append(ExtendedOp.bindingPattern(typeToTypeElement(var.type), var.name.toString()));
+                }
+
+                @Override
+                public void visitRecordPattern(JCTree.JCRecordPattern record) {
+                    // @@@ Is always Identifier to record?
+                    // scan(record.deconstructor);
+
+                    List<Value> nestedValues = new ArrayList<>();
+                    for (JCTree.JCPattern jcPattern : record.nested) {
+                        nestedValues.add(toValue(jcPattern));
+                    }
+
+                    result = append(ExtendedOp.recordPattern(symbolToRecordTypeRef(record.record), nestedValues));
+                }
+
+                Value toValue(JCTree tree) {
+                    result = null;
+                    scan(tree);
+                    return result;
+                }
+            }
+            // Scan pattern
+            Value patternValue = new PatternScanner().toValue(pattern);
+            append(CoreOp._yield(patternValue));
+            Body.Builder patternBody = stack.body;
+
+            // Pop body
+            popBody();
+
+            // Find nearest ancestor body stack element associated with a statement tree
+            // @@@ Strengthen check of tree?
+            BodyStack _variablesStack = stack;
+            while (!(_variablesStack.tree instanceof JCTree.JCStatement)) {
+                _variablesStack = _variablesStack.parent;
+            }
+            BodyStack variablesStack = _variablesStack;
+
+            // Create pattern var ops for pattern variables using the
+            // builder associated with the nearest statement tree
+            for (JCVariableDecl jcVar : variables) {
+                Value init = variablesStack.block.op(defaultValue(jcVar.type));
+                Op.Result op = variablesStack.block.op(CoreOp.var(jcVar.name.toString(), typeToTypeElement(jcVar.type), init));
+                variablesStack.localToOp.put(jcVar.sym, op);
+            }
+
+            // Create pattern descriptor
+            List<JavaType> patternDescParams = variables.stream().map(var -> typeToTypeElement(var.type)).toList();
+            FunctionType matchFuncType = FunctionType.functionType(JavaType.VOID, patternDescParams);
+
+            // Create the match body, assigning pattern values to pattern variables
+            Body.Builder matchBody = Body.Builder.of(patternBody.ancestorBody(), matchFuncType);
+            Block.Builder matchBuilder = matchBody.entryBlock();
+            for (int i = 0; i < variables.size(); i++) {
+                Value v = matchBuilder.parameters().get(i);
+                Value var = variablesStack.localToOp.get(variables.get(i).sym);
+                matchBuilder.op(CoreOp.varStore(var, v));
+            }
+            matchBuilder.op(CoreOp._yield());
+
+            // Create the match operation
+            return append(ExtendedOp.match(target, patternBody, matchBody));
+        }
+
+        @Override
+        public void visitNewClass(JCTree.JCNewClass tree) {
+            // @@@ Support anonymous classes
+            if (tree.def != null) {
+                throw unsupported(tree);
+            }
+
+            Type type = tree.type;
+            Type outer = type.getEnclosingType();
+            List<Value> args = new ArrayList<>();
+            if (!outer.hasTag(TypeTag.NONE)) {
+                // Obtain outer value for inner class, and add as first argument
+                JCTree.JCExpression encl = tree.encl;
+                Value outerInstance;
+                if (encl == null) {
+                    outerInstance = thisValue();
+                } else {
+                    outerInstance = toValue(tree.encl);
+                }
+                args.add(outerInstance);
+            }
+
+            // Create erased method type reference for constructor, where
+            // the return type declares the class to instantiate
+            // @@@ require symbol site type?
+            MethodRef methodRef = symbolToErasedMethodRef(tree.constructor);
+            FunctionType constructorType = FunctionType.functionType(
+                    symbolToErasedDesc(tree.constructor.owner),
+                    methodRef.type().parameterTypes());
+
+            args.addAll(scanMethodArguments(tree.args, tree.constructorType, tree.varargsElement));
+
+            result = append(CoreOp._new(typeToTypeElement(type), constructorType, args));
+        }
+
+        @Override
+        public void visitNewArray(JCTree.JCNewArray tree) {
+            if (tree.elems != null) {
+                int length = tree.elems.size();
+                Op.Result a = append(CoreOp.newArray(
+                        typeToTypeElement(tree.type),
+                        append(CoreOp.constant(JavaType.INT, length))));
+                int i = 0;
+                for (JCExpression elem : tree.elems) {
+                    Value element = toValue(elem, types.elemtype(tree.type));
+                    append(CoreOp.arrayStoreOp(
+                            a,
+                            append(CoreOp.constant(JavaType.INT, i)),
+                            element));
+                    i++;
+                }
+
+                result = a;
+            } else {
+                List<Value> indexes = new ArrayList<>();
+                for (JCTree.JCExpression dim : tree.dims) {
+                    indexes.add(toValue(dim));
+                }
+
+                JavaType arrayType = typeToTypeElement(tree.type);
+                FunctionType constructorType = FunctionType.functionType(arrayType,
+                        indexes.stream().map(Value::type).toList());
+                result = append(CoreOp._new(arrayType, constructorType, indexes));
+            }
+        }
+
+        @Override
+        public void visitLambda(JCTree.JCLambda tree) {
+            FunctionalExpressionKind kind = functionalKind(tree);
+            final FunctionType lambdaType = switch (kind) {
+                case QUOTED_STRUCTURAL -> typeToFunctionType(tree.target);
+                default -> typeToFunctionType(types.findDescriptorType(tree.target));
+            };
+
+            // Push quoted body
+            // We can either be explicitly quoted or a structural quoted expression
+            // within some larger reflected code
+            if (isQuoted || kind == FunctionalExpressionKind.QUOTED_STRUCTURAL) {
+                pushBody(tree.body, FunctionType.VOID);
+            }
+
+            // Push lambda body
+            pushBody(tree.body, lambdaType);
+
+            // Map lambda parameters to varOp values
+            for (int i = 0; i < tree.params.size(); i++) {
+                JCVariableDecl p = tree.params.get(i);
+                Op.Result paramOp = append(CoreOp.var(
+                        p.name.toString(),
+                        stack.block.parameters().get(i)));
+                stack.localToOp.put(p.sym, paramOp);
+            }
+
+            // Scan the lambda body
+            if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
+                Value exprVal = toValue(tree.body, tree.getDescriptorType(types).getReturnType());
+                if (!tree.body.type.hasTag(TypeTag.VOID)) {
+                    append(CoreOp._return(exprVal));
+                } else {
+                    appendTerminating(CoreOp::_return);
+                }
+            } else {
+                Type prevBodyTarget = bodyTarget;
+                try {
+                    bodyTarget = tree.getDescriptorType(types).getReturnType();
+                    toValue(tree.body);
+                    // @@@ Check if unreachable
+                    appendTerminating(CoreOp::_return);
+                } finally {
+                    bodyTarget = prevBodyTarget;
+                }
+            }
+
+            Op lambdaOp = switch (kind) {
+                case QUOTED_STRUCTURAL -> {
+                    yield CoreOp.closure(stack.body);
+                }
+                case QUOTABLE, NOT_QUOTED -> {
+                    // Get the functional interface type
+                    JavaType fiType = typeToTypeElement(tree.target);
+                    // build functional lambda
+                    yield CoreOp.lambda(fiType, stack.body);
+                }
+            };
+
+            // Pop lambda body
+            popBody();
+
+            Value lambdaResult;
+            if (isQuoted) {
+                lambdaResult = append(lambdaOp, generateLocation(tree, true));
+            } else {
+                lambdaResult = append(lambdaOp);
+            }
+
+            if (isQuoted || kind == FunctionalExpressionKind.QUOTED_STRUCTURAL) {
+                append(CoreOp._yield(lambdaResult));
+                CoreOp.QuotedOp quotedOp = CoreOp.quoted(stack.body);
+
+                // Pop quoted body
+                popBody();
+
+                lambdaResult = append(quotedOp);
+            }
+
+            result = lambdaResult;
+        }
+
+        @Override
+        public void visitIf(JCTree.JCIf tree) {
+            List<Body.Builder> bodies = new ArrayList<>();
+
+            boolean first = true;
+            while (tree != null) {
+                // @@@ cond.type can be boolean or Boolean
+                JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
+
+                // Push if condition
+                pushBody(cond,
+                        FunctionType.functionType(JavaType.BOOLEAN));
+                Value last = toValue(cond);
+                // Yield the boolean result of the condition
+                append(CoreOp._yield(last));
+                bodies.add(stack.body);
+
+                // Pop if condition
+                popBody();
+
+                // Push if body
+                pushBody(tree.thenpart, FunctionType.VOID);
+
+                scan(tree.thenpart);
+                appendTerminating(CoreOp::_yield);
+                bodies.add(stack.body);
+
+                // Pop if body
+                popBody();
+
+                JCTree.JCStatement elsepart = tree.elsepart;
+                if (elsepart == null) {
+                    tree = null;
+                }
+                else if (elsepart.getTag() == Tag.BLOCK) {
+                    // Push else body
+                    pushBody(elsepart, FunctionType.VOID);
+
+                    scan(elsepart);
+                    appendTerminating(CoreOp::_yield);
+                    bodies.add(stack.body);
+
+                    // Pop else body
+                    popBody();
+
+                    tree = null;
+                } else if (elsepart.getTag() == Tag.IF) {
+                    tree = (JCTree.JCIf) elsepart;
+                }
+                first = false;
+            }
+
+            append(ExtendedOp._if(bodies));
+            result = null;
+        }
+
+        @Override
+        public void visitSwitchExpression(JCTree.JCSwitchExpression tree) {
+            Value target = toValue(tree.selector);
+
+            FunctionType caseLabelType = FunctionType.functionType(JavaType.BOOLEAN, target.type());
+            Type switchType = adaptBottom(tree.type);
+            FunctionType actionType = FunctionType.functionType(typeToTypeElement(switchType));
+            List<Body.Builder> bodies = new ArrayList<>();
+            for (JCTree.JCCase c : tree.cases) {
+                // Labels body
+                JCTree.JCCaseLabel headCl = c.labels.head;
+                if (headCl instanceof JCTree.JCPatternCaseLabel pcl) {
+                    if (c.labels.size() > 1) {
+                        throw unsupported(c);
+                    }
+
+                    pushBody(pcl, caseLabelType);
+
+                    Value localTarget = stack.block.parameters().get(0);
+                    final Value localResult;
+                    if (c.guard != null) {
+                        List<Body.Builder> clBodies = new ArrayList<>();
+
+                        pushBody(pcl.pat, FunctionType.functionType(JavaType.BOOLEAN));
+                        Value patVal = scanPattern(pcl.pat, localTarget);
+                        append(CoreOp._yield(patVal));
+                        clBodies.add(stack.body);
+                        popBody();
+
+                        pushBody(c.guard, FunctionType.functionType(JavaType.BOOLEAN));
+                        append(CoreOp._yield(toValue(c.guard)));
+                        clBodies.add(stack.body);
+                        popBody();
+
+                        localResult = append(ExtendedOp.conditionalAnd(clBodies));
+                    } else {
+                        localResult = scanPattern(pcl.pat, localTarget);
+                    }
+                    // Yield the boolean result of the condition
+                    append(CoreOp._yield(localResult));
+                    bodies.add(stack.body);
+
+                    // Pop label
+                    popBody();
+                } else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) {
+                    pushBody(headCl, caseLabelType);
+
+                    Value localTarget = stack.block.parameters().get(0);
+                    final Value localResult;
+                    if (c.labels.size() == 1) {
+                        Value expr = toValue(ccl.expr);
+                        // @@@ Conversion of localTarget
+                        if (ccl.expr.type.isPrimitive()) {
+                            localResult = append(CoreOp.eq(localTarget, expr));
+                        } else {
+                            localResult = append(CoreOp.invoke(
+                                    MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
+                                    localTarget, expr));
+                        }
+                    } else {
+                        List<Body.Builder> clBodies = new ArrayList<>();
+                        for (JCTree.JCCaseLabel cl : c.labels) {
+                            ccl = (JCTree.JCConstantCaseLabel) cl;
+                            pushBody(ccl, FunctionType.functionType(JavaType.BOOLEAN));
+
+                            Value expr = toValue(ccl.expr);
+                            // @@@ Conversion of localTarget
+                            final Value labelResult;
+                            if (ccl.expr.type.isPrimitive()) {
+                                labelResult = append(CoreOp.eq(localTarget, expr));
+                            } else {
+                                labelResult = append(CoreOp.invoke(
+                                        MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
+                                        localTarget, expr));
+                            }
+
+                            append(CoreOp._yield(labelResult));
+                            clBodies.add(stack.body);
+
+                            // Pop label
+                            popBody();
+                        }
+
+                        localResult = append(ExtendedOp.conditionalOr(clBodies));
+                    }
+
+                    append(CoreOp._yield(localResult));
+                    bodies.add(stack.body);
+
+                    // Pop labels
+                    popBody();
+                } else if (headCl instanceof JCTree.JCDefaultCaseLabel) {
+                    // @@@ Do we need to model the default label body?
+                    pushBody(headCl, FunctionType.VOID);
+
+                    append(CoreOp._yield());
+                    bodies.add(stack.body);
+
+                    // Pop label
+                    popBody();
+                } else {
+                    throw unsupported(tree);
+                }
+
+                // Statements body
+                switch (c.caseKind) {
+                    case RULE -> {
+                        pushBody(c.body, actionType);
+                        Type yieldType = adaptBottom(tree.type);
+                        if (c.body instanceof JCExpression) {
+                            // Yield the boolean result of the condition
+                            Value bodyVal = toValue(c.body, yieldType);
+                            append(CoreOp._yield(bodyVal));
+                        } else {
+                            // Otherwise there is a yield statement
+                            Type prevBodyTarget = bodyTarget;
+                            try {
+                                bodyTarget = yieldType;
+                                Value bodyVal = toValue(c.body);
+                            } finally {
+                                bodyTarget = prevBodyTarget;
+                            }
+                        }
+                        bodies.add(stack.body);
+
+                        // Pop block
+                        popBody();
+                    }
+                    case STATEMENT -> {
+                        // @@@ Avoid nesting for a single block? Goes against "say what you see"
+                        // boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock;
+                        pushBody(c, actionType);
+
+                        scan(c.stats);
+
+                        appendTerminating(c.completesNormally
+                                ? ExtendedOp::switchFallthroughOp
+                                : CoreOp::unreachable);
+
+                        bodies.add(stack.body);
+
+                        // Pop block
+                        popBody();
+                    }
+                };
+            }
+
+            result = append(ExtendedOp.switchExpression(actionType.returnType(), target, bodies));
+        }
+
+        @Override
+        public void visitYield(JCTree.JCYield tree) {
+            Value retVal = toValue(tree.value, bodyTarget);
+            if (retVal == null) {
+                result = append(ExtendedOp.java_yield());
+            } else {
+                result = append(ExtendedOp.java_yield(retVal));
+            }
+        }
+
+        @Override
+        public void visitWhileLoop(JCTree.JCWhileLoop tree) {
+            // @@@ Patterns
+            // @@@ cond.type can be boolean or Boolean
+            JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
+
+            // Push while condition
+            pushBody(cond, FunctionType.functionType(JavaType.BOOLEAN));
+            Value last = toValue(cond);
+            // Yield the boolean result of the condition
+            append(CoreOp._yield(last));
+            Body.Builder condition = stack.body;
+
+            // Pop while condition
+            popBody();
+
+            // Push while body
+            pushBody(tree.body, FunctionType.VOID);
+            scan(tree.body);
+            appendTerminating(ExtendedOp::_continue);
+            Body.Builder body = stack.body;
+
+            // Pop while body
+            popBody();
+
+            append(ExtendedOp._while(condition, body));
+            result = null;
+        }
+
+        @Override
+        public void visitDoLoop(JCTree.JCDoWhileLoop tree) {
+            // @@@ Patterns
+            // @@@ cond.type can be boolean or Boolean
+            JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
+
+            // Push while body
+            pushBody(tree.body, FunctionType.VOID);
+            scan(tree.body);
+            appendTerminating(ExtendedOp::_continue);
+            Body.Builder body = stack.body;
+
+            // Pop while body
+            popBody();
+
+            // Push while condition
+            pushBody(cond, FunctionType.functionType(JavaType.BOOLEAN));
+            Value last = toValue(cond);
+            // Yield the boolean result of the condition
+            append(CoreOp._yield(last));
+            Body.Builder condition = stack.body;
+
+            // Pop while condition
+            popBody();
+
+            append(ExtendedOp.doWhile(body, condition));
+            result = null;
+        }
+
+        @Override
+        public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
+            // Push expression
+            pushBody(tree.expr, FunctionType.functionType(typeToTypeElement(tree.expr.type)));
+            Value last = toValue(tree.expr);
+            // Yield the Iterable result of the expression
+            append(CoreOp._yield(last));
+            Body.Builder expression = stack.body;
+
+            // Pop expression
+            popBody();
+
+            JCVariableDecl var = tree.getVariable();
+            JavaType eType = typeToTypeElement(var.type);
+            VarType varEType = VarType.varType(typeToTypeElement(var.type));
+
+            // Push init
+            // @@@ When lhs assignment is a pattern we embed the pattern match into the init body and
+            // return the bound variables
+            pushBody(var, FunctionType.functionType(varEType, eType));
+            Op.Result varEResult = append(CoreOp.var(var.name.toString(), stack.block.parameters().get(0)));
+            append(CoreOp._yield(varEResult));
+            Body.Builder init = stack.body;
+            // Pop init
+            popBody();
+
+            // Push body
+            pushBody(tree.body, FunctionType.functionType(JavaType.VOID, varEType));
+            stack.localToOp.put(var.sym, stack.block.parameters().get(0));
+
+            scan(tree.body);
+            appendTerminating(ExtendedOp::_continue);
+            Body.Builder body = stack.body;
+            // Pop body
+            popBody();
+
+            append(ExtendedOp.enhancedFor(expression, init, body));
+            result = null;
+        }
+
+        @Override
+        public void visitForLoop(JCTree.JCForLoop tree) {
+            class VarDefScanner extends FilterScanner {
+                final List<JCVariableDecl> decls;
+
+                public VarDefScanner() {
+                    super(Set.of(Tag.VARDEF));
+                    this.decls = new ArrayList<>();
+                }
+
+                @Override
+                public void visitVarDef(JCVariableDecl tree) {
+                    decls.add(tree);
+                }
+
+                void mapVarsToBlockArguments() {
+                    for (int i = 0; i < decls.size(); i++) {
+                        stack.localToOp.put(decls.get(i).sym, stack.block.parameters().get(i));
+                    }
+                }
+
+                List<VarType> varTypes() {
+                    return decls.stream()
+                            .map(t -> VarType.varType(typeToTypeElement(t.type)))
+                            .toList();
+                }
+
+                List<Value> varValues() {
+                    return decls.stream()
+                            .map(t -> stack.localToOp.get(t.sym))
+                            .toList();
+                }
+            }
+
+            // Scan local variable declarations
+            VarDefScanner vds = new VarDefScanner();
+            vds.scan(tree.init);
+            List<VarType> varTypes = vds.varTypes();
+
+            // Push init
+            if (varTypes.size() > 1) {
+                pushBody(null, FunctionType.functionType(TupleType.tupleType(varTypes)));
+                scan(tree.init);
+
+                // Capture all local variable declarations in tuple
+                append(CoreOp._yield(append(CoreOp.tuple(vds.varValues()))));
+            } else if (varTypes.size() == 1) {
+                pushBody(null, FunctionType.functionType(varTypes.get(0)));
+                scan(tree.init);
+
+                append(CoreOp._yield(vds.varValues().get(0)));
+            } else {
+                pushBody(null, FunctionType.VOID);
+                scan(tree.init);
+
+                append(CoreOp._yield());
+            }
+            Body.Builder init = stack.body;
+
+            // Pop init
+            popBody();
+
+            // Push cond
+            pushBody(tree.cond, FunctionType.functionType(JavaType.BOOLEAN, varTypes));
+            if (tree.cond != null) {
+                vds.mapVarsToBlockArguments();
+
+                Value last = toValue(tree.cond);
+                // Yield the boolean result of the condition
+                append(CoreOp._yield(last));
+            } else {
+                append(CoreOp._yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
+            }
+            Body.Builder cond = stack.body;
+
+            // Pop cond
+            popBody();
+
+            // Push update
+            // @@@ tree.step is a List<JCStatement>
+            pushBody(null, FunctionType.functionType(JavaType.VOID, varTypes));
+            if (!tree.step.isEmpty()) {
+                vds.mapVarsToBlockArguments();
+
+                scan(tree.step);
+            }
+            append(CoreOp._yield());
+            Body.Builder update = stack.body;
+
+            // Pop update
+            popBody();
+
+            // Push body
+            pushBody(tree.body, FunctionType.functionType(JavaType.VOID, varTypes));
+            if (tree.body != null) {
+                vds.mapVarsToBlockArguments();
+
+                scan(tree.body);
+            }
+            appendTerminating(ExtendedOp::_continue);
+            Body.Builder body = stack.body;
+
+            // Pop update
+            popBody();
+
+            append(ExtendedOp._for(init, cond, update, body));
+            result = null;
+        }
+
+        @Override
+        public void visitConditional(JCTree.JCConditional tree) {
+            List<Body.Builder> bodies = new ArrayList<>();
+
+            JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
+
+            // Push condition
+            pushBody(cond,
+                    FunctionType.functionType(JavaType.BOOLEAN));
+            Value condVal = toValue(cond);
+            // Yield the boolean result of the condition
+            append(CoreOp._yield(condVal));
+            bodies.add(stack.body);
+
+            // Pop condition
+            popBody();
+
+            JCTree.JCExpression truepart = TreeInfo.skipParens(tree.truepart);
+
+            Type condType = adaptBottom(tree.type);
+
+            // Push true body
+            pushBody(truepart,
+                    FunctionType.functionType(typeToTypeElement(condType)));
+
+            Value trueVal = toValue(truepart, condType);
+            // Yield the result
+            append(CoreOp._yield(trueVal));
+            bodies.add(stack.body);
+
+            // Pop true body
+            popBody();
+
+            JCTree.JCExpression falsepart = TreeInfo.skipParens(tree.falsepart);
+
+            // Push false body
+            pushBody(falsepart,
+                    FunctionType.functionType(typeToTypeElement(condType)));
+
+            Value falseVal = toValue(falsepart, condType);
+            // Yield the result
+            append(CoreOp._yield(falseVal));
+            bodies.add(stack.body);
+
+            // Pop false body
+            popBody();
+
+            result = append(ExtendedOp.conditionalExpression(typeToTypeElement(condType), bodies));
+        }
+
+        private Type condType(JCExpression tree, Type type) {
+            if (type.hasTag(BOT)) {
+                return adaptBottom(tree.type);
+            } else {
+                return type;
+            }
+        }
+
+        private Type adaptBottom(Type type) {
+            return type.hasTag(BOT) ?
+                    (pt.hasTag(NONE) ? syms.objectType : pt) :
+                    type;
+        }
+
+        @Override
+        public void visitAssert(JCAssert tree) {
+            // assert <cond:body1> [detail:body2]
+
+            List<Body.Builder> bodies = new ArrayList<>();
+            JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
+
+            // Push condition
+            pushBody(cond,
+                    FunctionType.functionType(JavaType.BOOLEAN));
+            Value condVal = toValue(cond);
+
+            // Yield the boolean result of the condition
+            append(CoreOp._yield(condVal));
+            bodies.add(stack.body);
+
+            // Pop condition
+            popBody();
+
+            if (tree.detail != null) {
+                JCTree.JCExpression detail = TreeInfo.skipParens(tree.detail);
+
+                pushBody(detail,
+                         FunctionType.functionType(typeToTypeElement(tree.detail.type)));
+                Value detailVal = toValue(detail);
+
+                append(CoreOp._yield(detailVal));
+                bodies.add(stack.body);
+
+                //Pop detail
+                popBody();
+            }
+
+            result = append(CoreOp._assert(bodies));
+
+        }
+
+        @Override
+        public void visitBlock(JCTree.JCBlock tree) {
+            if (stack.tree == tree) {
+                // Block is associated with the visit of a parent structure
+                scan(tree.stats);
+            } else {
+                // Otherwise, independent block structure
+                // @@@ Support synchronized blocks
+                // Push block
+                pushBody(tree, FunctionType.VOID);
+                scan(tree.stats);
+                appendTerminating(CoreOp::_yield);
+                Body.Builder body = stack.body;
+
+                // Pop block
+                popBody();
+
+                append(ExtendedOp.block(body));
+            }
+            result = null;
+        }
+
+        @Override
+        public void visitLabelled(JCTree.JCLabeledStatement tree) {
+            // Push block
+            pushBody(tree, FunctionType.VOID);
+            // Create constant for label
+            String labelName = tree.label.toString();
+            Op.Result label = append(CoreOp.constant(JavaType.J_L_STRING, labelName));
+            // Set label on body stack
+            stack.setLabel(labelName, label);
+            scan(tree.body);
+            appendTerminating(CoreOp::_yield);
+            Body.Builder body = stack.body;
+
+            // Pop block
+            popBody();
+
+            result = append(ExtendedOp.labeled(body));
+        }
+
+        @Override
+        public void visitTry(JCTree.JCTry tree) {
+            List<JCVariableDecl> rVariableDecls = new ArrayList<>();
+            List<TypeElement> rTypes = new ArrayList<>();
+            Body.Builder resources;
+            if (!tree.resources.isEmpty()) {
+                // Resources body returns a tuple that contains the resource variables/values
+                // in order of declaration
+                for (JCTree resource : tree.resources) {
+                    if (resource instanceof JCVariableDecl vdecl) {
+                        rVariableDecls.add(vdecl);
+                        rTypes.add(VarType.varType(typeToTypeElement(vdecl.type)));
+                    } else {
+                        rTypes.add(typeToTypeElement(resource.type));
+                    }
+                }
+
+                // Push resources body
+                pushBody(null, FunctionType.functionType(TupleType.tupleType(rTypes)));
+
+                List<Value> rValues = new ArrayList<>();
+                for (JCTree resource : tree.resources) {
+                    rValues.add(toValue(resource));
+                }
+
+                append(CoreOp._yield(append(CoreOp.tuple(rValues))));
+                resources = stack.body;
+
+                // Pop resources body
+                popBody();
+            } else {
+                resources = null;
+            }
+
+            // Push body
+            // Try body accepts the resource variables (in order of declaration).
+            List<VarType> rVarTypes = rTypes.stream().<VarType>mapMulti((t, c) -> {
+                if (t instanceof VarType vt) {
+                    c.accept(vt);
+                }
+            }).toList();
+            pushBody(tree.body, FunctionType.functionType(JavaType.VOID, rVarTypes));
+            for (int i = 0; i < rVariableDecls.size(); i++) {
+                stack.localToOp.put(rVariableDecls.get(i).sym, stack.block.parameters().get(i));
+            }
+            scan(tree.body);
+            appendTerminating(CoreOp::_yield);
+            Body.Builder body = stack.body;
+
+            // Pop block
+            popBody();
+
+            List<Body.Builder> catchers = new ArrayList<>();
+            for (JCTree.JCCatch catcher : tree.catchers) {
+                // Push body
+                pushBody(catcher.body, FunctionType.functionType(JavaType.VOID, typeToTypeElement(catcher.param.type)));
+                Op.Result exVariable = append(CoreOp.var(
+                        catcher.param.name.toString(),
+                        stack.block.parameters().get(0)));
+                stack.localToOp.put(catcher.param.sym, exVariable);
+                scan(catcher.body);
+                appendTerminating(CoreOp::_yield);
+                catchers.add(stack.body);
+
+                // Pop block
+                popBody();
+            }
+
+            Body.Builder finalizer;
+            if (tree.finalizer != null) {
+                // Push body
+                pushBody(tree.finalizer, FunctionType.VOID);
+                scan(tree.finalizer);
+                appendTerminating(CoreOp::_yield);
+                finalizer = stack.body;
+
+                // Pop block
+                popBody();
+            }
+            else {
+                finalizer = null;
+            }
+
+            result = append(ExtendedOp._try(resources, body, catchers, finalizer));
+        }
+
+        @Override
+        public void visitUnary(JCTree.JCUnary tree) {
+            Tag tag = tree.getTag();
+            switch (tag) {
+                case POSTINC, POSTDEC, PREINC, PREDEC -> {
+                    // Capture applying rhs and operation
+                    Function<Value, Value> scanRhs = (lhs) -> {
+                        Value one = append(numericOneValue(tree.type));
+                        Value unboxedLhs = unboxIfNeeded(lhs);
+
+                        Value unboxedLhsPlusOne = switch (tree.getTag()) {
+                            // Arithmetic operations
+                            case POSTINC, PREINC -> append(CoreOp.add(unboxedLhs, one));
+                            case POSTDEC, PREDEC -> append(CoreOp.sub(unboxedLhs, one));
+
+                            default -> throw unsupported(tree);
+                        };
+                        Value lhsPlusOne = convert(unboxedLhsPlusOne, tree.type);
+
+                        // Assign expression result
+                        result =  switch (tree.getTag()) {
+                            case POSTINC, POSTDEC -> lhs;
+                            case PREINC, PREDEC -> lhsPlusOne;
+
+                            default -> throw unsupported(tree);
+                        };
+                        return lhsPlusOne;
+                    };
+
+                    applyCompoundAssign(tree.arg, scanRhs);
+                }
+                case NEG -> {
+                    Value rhs = toValue(tree.arg, tree.type);
+                    result = append(CoreOp.neg(rhs));
+                }
+                case NOT -> {
+                    Value rhs = toValue(tree.arg, tree.type);
+                    result = append(CoreOp.not(rhs));
+                }
+                default -> throw unsupported(tree);
+            }
+        }
+
+        @Override
+        public void visitBinary(JCBinary tree) {
+            Tag tag = tree.getTag();
+            if (tag == Tag.AND || tag == Tag.OR) {
+                // Logical operations
+                // @@@ Flatten nested sequences
+
+                // Push lhs
+                pushBody(tree.lhs, FunctionType.functionType(JavaType.BOOLEAN));
+                Value lhs = toValue(tree.lhs);
+                // Yield the boolean result of the condition
+                append(CoreOp._yield(lhs));
+                Body.Builder bodyLhs = stack.body;
+
+                // Pop lhs
+                popBody();
+
+                // Push rhs
+                pushBody(tree.rhs, FunctionType.functionType(JavaType.BOOLEAN));
+                Value rhs = toValue(tree.rhs);
+                // Yield the boolean result of the condition
+                append(CoreOp._yield(rhs));
+                Body.Builder bodyRhs = stack.body;
+
+                // Pop lhs
+                popBody();
+
+                List<Body.Builder> bodies = List.of(bodyLhs, bodyRhs);
+                result = append(tag == Tag.AND
+                        ? ExtendedOp.conditionalAnd(bodies)
+                        : ExtendedOp.conditionalOr(bodies));
+            } else if (tag == Tag.PLUS && tree.operator.opcode == ByteCodes.string_add) {
+                //Ignore the operator and query both subexpressions for their type with concats
+                Type lhsType = tree.lhs.type;
+                Type rhsType = tree.rhs.type;
+
+                Value lhs = toValue(tree.lhs, lhsType);
+                Value rhs = toValue(tree.rhs, rhsType);
+
+                result = append(CoreOp.concat(lhs, rhs));
+            }
+            else {
+                Type opType = tree.operator.type.getParameterTypes().getFirst();
+                // @@@ potentially handle shift input conversion like other binary ops
+                boolean isShift = tag == Tag.SL || tag == Tag.SR || tag == Tag.USR;
+                Value lhs = toValue(tree.lhs, opType);
+                Value rhs = toValue(tree.rhs, isShift ? tree.operator.type.getParameterTypes().getLast() : opType);
+
+                result = switch (tag) {
+                    // Arithmetic operations
+                    case PLUS -> append(CoreOp.add(lhs, rhs));
+                    case MINUS -> append(CoreOp.sub(lhs, rhs));
+                    case MUL -> append(CoreOp.mul(lhs, rhs));
+                    case DIV -> append(CoreOp.div(lhs, rhs));
+                    case MOD -> append(CoreOp.mod(lhs, rhs));
+
+                    // Test operations
+                    case EQ -> append(CoreOp.eq(lhs, rhs));
+                    case NE -> append(CoreOp.neq(lhs, rhs));
+                    //
+                    case LT -> append(CoreOp.lt(lhs, rhs));
+                    case LE -> append(CoreOp.le(lhs, rhs));
+                    case GT -> append(CoreOp.gt(lhs, rhs));
+                    case GE -> append(CoreOp.ge(lhs, rhs));
+
+                    // Bitwise operations (including their boolean variants)
+                    case BITOR -> append(CoreOp.or(lhs, rhs));
+                    case BITAND -> append(CoreOp.and(lhs, rhs));
+                    case BITXOR -> append(CoreOp.xor(lhs, rhs));
+
+                    // Shift operations
+                    case SL -> append(CoreOp.lshl(lhs, rhs));
+                    case SR -> append(CoreOp.ashr(lhs, rhs));
+                    case USR -> append(CoreOp.lshr(lhs, rhs));
+
+                    default -> throw unsupported(tree);
+                };
+            }
+        }
+
+        @Override
+        public void visitLiteral(JCLiteral tree) {
+            Object value = switch (tree.type.getTag()) {
+                case BOOLEAN -> tree.value instanceof Integer i && i == 1;
+                case CHAR -> (char) (int) tree.value;
+                default -> tree.value;
+            };
+            Type constantType = adaptBottom(tree.type);
+            result = append(CoreOp.constant(typeToTypeElement(constantType), value));
+        }
+
+        @Override
+        public void visitReturn(JCReturn tree) {
+            Value retVal = toValue(tree.expr, bodyTarget);
+            if (retVal == null) {
+                result = append(CoreOp._return());
+            } else {
+                result = append(CoreOp._return(retVal));
+            }
+        }
+
+        @Override
+        public void visitThrow(JCTree.JCThrow tree) {
+            Value throwVal = toValue(tree.expr);
+            result = append(CoreOp._throw(throwVal));
+        }
+
+        @Override
+        public void visitBreak(JCTree.JCBreak tree) {
+            Value label = tree.label != null
+                    ? getLabel(tree.label.toString())
+                    : null;
+            result = append(ExtendedOp._break(label));
+        }
+
+        @Override
+        public void visitContinue(JCTree.JCContinue tree) {
+            Value label = tree.label != null
+                    ? getLabel(tree.label.toString())
+                    : null;
+            result = append(ExtendedOp._continue(label));
+        }
+
+        @Override
+        public void visitClassDef(JCClassDecl tree) {
+            // do nothing
+        }
+
+
+        UnsupportedASTException unsupported(JCTree tree) {
+            return new UnsupportedASTException(tree);
+        }
+
+        CoreOp.FuncOp scanMethod() {
+            scan(body);
+            // @@@ Check if unreachable
+            appendTerminating(CoreOp::_return);
+            CoreOp.FuncOp func = CoreOp.func(name.toString(), stack.body);
+            func.setLocation(generateLocation(currentNode, true));
+            return func;
+        }
+
+        CoreOp.FuncOp scanLambda() {
+            scan(body);
+            append(CoreOp._return(result));
+            return CoreOp.func(name.toString(), stack.body);
+        }
+
+        JavaType symbolToErasedDesc(Symbol s) {
+            return typeToTypeElement(s.erasure(types));
+        }
+
+        JavaType typeToTypeElement(Type t) {
+            t = normalizeType(t);
+            return switch (t.getTag()) {
+                case VOID -> JavaType.VOID;
+                case CHAR -> JavaType.CHAR;
+                case BOOLEAN -> JavaType.BOOLEAN;
+                case BYTE -> JavaType.BYTE;
+                case SHORT -> JavaType.SHORT;
+                case INT -> JavaType.INT;
+                case FLOAT -> JavaType.FLOAT;
+                case LONG -> JavaType.LONG;
+                case DOUBLE -> JavaType.DOUBLE;
+                case ARRAY -> {
+                    Type et = ((ArrayType)t).elemtype;
+                    yield JavaType.array(typeToTypeElement(et));
+                }
+                case WILDCARD -> {
+                    Type.WildcardType wt = (Type.WildcardType)t;
+                    yield wt.isUnbound() ?
+                            JavaType.wildcard() :
+                            JavaType.wildcard(wt.isExtendsBound() ? BoundKind.EXTENDS : BoundKind.SUPER, typeToTypeElement(wt.type));
+                }
+                case TYPEVAR -> t.tsym.owner.kind == Kind.MTH ?
+                        JavaType.typeVarRef(t.tsym.name.toString(), symbolToErasedMethodRef(t.tsym.owner),
+                                typeToTypeElement(t.getUpperBound())) :
+                        JavaType.typeVarRef(t.tsym.name.toString(),
+                                (jdk.internal.java.lang.reflect.code.type.ClassType)symbolToErasedDesc(t.tsym.owner),
+                                typeToTypeElement(t.getUpperBound()));
+                case CLASS -> {
+                    Assert.check(!t.isIntersection() && !t.isUnion());
+                    // @@@ Need to clean this up, probably does not work inner generic classes
+                    // whose enclosing class is also generic
+                    List<JavaType> typeArguments;
+                    if (t.getTypeArguments().nonEmpty()) {
+                        typeArguments = new ArrayList<>();
+                        for (Type ta : t.getTypeArguments()) {
+                            typeArguments.add(typeToTypeElement(ta));
+                        }
+                    } else {
+                        typeArguments = List.of();
+                    }
+
+                    // Use flat name to ensure demarcation of nested classes
+                    yield JavaType.parameterized(JavaType.type(ClassDesc.of(t.tsym.flatName().toString())), typeArguments);
+                }
+                default -> {
+                    throw new UnsupportedOperationException("Unsupported type: kind=" + t.getKind() + " type=" + t);
+                }
+            };
+        }
+
+        Type symbolSiteType(Symbol s) {
+            boolean isMember = s.owner == syms.predefClass ||
+                    s.isMemberOf(currentClassSym, types);
+            return isMember ? currentClassSym.type : s.owner.type;
+        }
+
+        FieldRef symbolToFieldRef(Symbol s, Type site) {
+            // @@@ Made Gen::binaryQualifier public, duplicate logic?
+            // Ensure correct qualifying class is used in the reference, see JLS 13.1
+            // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
+            return symbolToErasedFieldRef(gen.binaryQualifier(s, types.erasure(site)));
+        }
+
+        FieldRef symbolToErasedFieldRef(Symbol s) {
+            Type erasedType = s.erasure(types);
+            return FieldRef.field(
+                    typeToTypeElement(s.owner.erasure(types)),
+                    s.name.toString(),
+                    typeToTypeElement(erasedType));
+        }
+
+        MethodRef symbolToErasedMethodRef(Symbol s, Type site) {
+            // @@@ Made Gen::binaryQualifier public, duplicate logic?
+            // Ensure correct qualifying class is used in the reference, see JLS 13.1
+            // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
+            return symbolToErasedMethodRef(gen.binaryQualifier(s, types.erasure(site)));
+        }
+
+        MethodRef symbolToErasedMethodRef(Symbol s) {
+            Type erasedType = s.erasure(types);
+            return MethodRef.method(
+                    typeToTypeElement(s.owner.erasure(types)),
+                    s.name.toString(),
+                    typeToTypeElement(erasedType.getReturnType()),
+                    erasedType.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
+        }
+
+        FunctionType symbolToFunctionType(Symbol s) {
+            return typeToFunctionType(s.type);
+        }
+
+        FunctionType typeToFunctionType(Type t) {
+            return FunctionType.functionType(
+                    typeToTypeElement(t.getReturnType()),
+                    t.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
+        }
+
+        RecordTypeRef symbolToRecordTypeRef(Symbol.ClassSymbol s) {
+            TypeElement recordType = typeToTypeElement(s.type);
+            List<RecordTypeRef.ComponentRef> components = s.getRecordComponents().stream()
+                    .map(rc -> new RecordTypeRef.ComponentRef(typeToTypeElement(rc.type), rc.name.toString()))
+                    .toList();
+            return RecordTypeRef.recordType(recordType, components);
+        }
+
+        Op defaultValue(Type t) {
+            return switch (t.getTag()) {
+                case BYTE -> CoreOp.constant(typeToTypeElement(t), (byte)0);
+                case CHAR -> CoreOp.constant(typeToTypeElement(t), (char)0);
+                case BOOLEAN -> CoreOp.constant(typeToTypeElement(t), false);
+                case SHORT -> CoreOp.constant(typeToTypeElement(t), (short)0);
+                case INT -> CoreOp.constant(typeToTypeElement(t), 0);
+                case FLOAT -> CoreOp.constant(typeToTypeElement(t), 0f);
+                case LONG -> CoreOp.constant(typeToTypeElement(t), 0L);
+                case DOUBLE -> CoreOp.constant(typeToTypeElement(t), 0d);
+                default -> CoreOp.constant(typeToTypeElement(t), null);
+            };
+        }
+
+        Op numericOneValue(Type t) {
+            return switch (t.getTag()) {
+                case BYTE -> CoreOp.constant(typeToTypeElement(t), (byte)1);
+                case CHAR -> CoreOp.constant(typeToTypeElement(t), (char)1);
+                case SHORT -> CoreOp.constant(typeToTypeElement(t), (short)1);
+                case INT -> CoreOp.constant(typeToTypeElement(t), 1);
+                case FLOAT -> CoreOp.constant(typeToTypeElement(t), 1f);
+                case LONG -> CoreOp.constant(typeToTypeElement(t), 1L);
+                case DOUBLE -> CoreOp.constant(typeToTypeElement(t), 1d);
+                case CLASS -> numericOneValue(types.unboxedType(t));
+                default -> throw new UnsupportedOperationException(t.toString());
+            };
+        }
+
+        Type normalizeType(Type t) {
+            Assert.check(!t.hasTag(METHOD));
+            return types.upward(t, false, types.captures(t));
+        }
+
+        Type typeElementToType(TypeElement desc) {
+            return primitiveAndBoxTypeMap().getOrDefault(desc, Type.noType);
+        }
+
+        public boolean checkDenotableInTypeDesc(Type t) {
+            return denotableChecker.visit(t, null);
+        }
+        // where
+
+        /**
+         * A type visitor that descends into the given type looking for types that are non-denotable
+         * in code model types. Examples of such types are: type-variables (regular or captured),
+         * wildcard type argument, intersection types, union types. The visit methods return false
+         * as soon as a non-denotable type is encountered and true otherwise. (see {@link Check#checkDenotable(Type)}.
+         */
+        private static final Types.SimpleVisitor<Boolean, Void> denotableChecker = new Types.SimpleVisitor<>() {
+            @Override
+            public Boolean visitType(Type t, Void s) {
+                return true;
+            }
+            @Override
+            public Boolean visitClassType(ClassType t, Void s) {
+                if (t.isUnion() || t.isIntersection()) {
+                    // union and intersections cannot be denoted in code model types
+                    return false;
+                }
+                // @@@ What about enclosing types?
+                for (Type targ : t.getTypeArguments()) {
+                    // propagate into type arguments
+                    if (!visit(targ, s)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            @Override
+            public Boolean visitTypeVar(TypeVar t, Void s) {
+                // type variables cannot be denoted in code model types
+                return false;
+            }
+
+            @Override
+            public Boolean visitWildcardType(WildcardType t, Void s) {
+                // wildcards cannot de denoted in code model types
+                return false;
+            }
+
+            @Override
+            public Boolean visitArrayType(ArrayType t, Void s) {
+                // propagate into element type
+                return visit(t.elemtype, s);
+            }
+        };
+
+    }
+
+    /**
+     * An exception thrown when an unsupported AST node is found when building a method IR.
+     */
+    static class UnsupportedASTException extends RuntimeException {
+
+        private static final long serialVersionUID = 0;
+        transient final JCTree tree;
+
+        public UnsupportedASTException(JCTree tree) {
+            this.tree = tree;
+        }
+    }
+
+    enum FunctionalExpressionKind {
+        QUOTED_STRUCTURAL(true), // this is transitional
+        QUOTABLE(true),
+        NOT_QUOTED(false);
+
+        final boolean isQuoted;
+
+        FunctionalExpressionKind(boolean isQuoted) {
+            this.isQuoted = isQuoted;
+        }
+    }
+
+    FunctionalExpressionKind functionalKind(JCFunctionalExpression functionalExpression) {
+        if (functionalExpression.target.hasTag(TypeTag.METHOD)) {
+            return FunctionalExpressionKind.QUOTED_STRUCTURAL;
+        } else if (types.asSuper(functionalExpression.target, syms.quotableType.tsym) != null) {
+            return FunctionalExpressionKind.QUOTABLE;
+        } else {
+            return FunctionalExpressionKind.NOT_QUOTED;
+        }
+    }
+
+    /*
+     * Converts a method reference which cannot be used directly into a lambda.
+     * This code has been derived from LambdaToMethod::MemberReferenceToLambda. The main
+     * difference is that, while that code concerns with translation strategy, boxing
+     * conversion and type erasure, this version does not and, as such, can remain
+     * at a higher level. Note that this code needs to create a synthetic variable
+     * declaration in case of a bounded method reference whose receiver expression
+     * is other than 'this'/'super' (this is done to prevent the receiver expression
+     * from being computed twice).
+     */
+    private class MemberReferenceToLambda {
+
+        private final JCMemberReference tree;
+        private final Symbol owner;
+        private final ListBuffer<JCExpression> args = new ListBuffer<>();
+        private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
+        private JCVariableDecl receiverVar = null;
+
+        MemberReferenceToLambda(JCMemberReference tree, Symbol currentClass) {
+            this.tree = tree;
+            this.owner = new MethodSymbol(0, names.lambda, tree.target, currentClass);
+            if (tree.kind == ReferenceKind.BOUND && !isThisOrSuper(tree.getQualifierExpression())) {
+                // true bound method reference, hoist receiver expression out
+                Type recvType = types.asSuper(tree.getQualifierExpression().type, tree.sym.owner);
+                VarSymbol vsym = makeSyntheticVar("rec$", recvType);
+                receiverVar = make.VarDef(vsym, tree.getQualifierExpression());
+            }
+        }
+
+        JCVariableDecl receiverVar() {
+            return receiverVar;
+        }
+
+        JCLambda lambda() {
+            int prevPos = make.pos;
+            try {
+                make.at(tree);
+
+                //body generation - this can be either a method call or a
+                //new instance creation expression, depending on the member reference kind
+                VarSymbol rcvr = addParametersReturnReceiver();
+                JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
+                        ? expressionInvoke(rcvr)
+                        : expressionNew();
+
+                JCLambda slam = make.Lambda(params.toList(), expr);
+                slam.target = tree.target;
+                slam.type = tree.type;
+                slam.pos = tree.pos;
+                return slam;
+            } finally {
+                make.at(prevPos);
+            }
+        }
+
+        /**
+         * Generate the parameter list for the converted member reference.
+         *
+         * @return The receiver variable symbol, if any
+         */
+        VarSymbol addParametersReturnReceiver() {
+            com.sun.tools.javac.util.List<Type> descPTypes = tree.getDescriptorType(types).getParameterTypes();
+            VarSymbol receiverParam = null;
+            switch (tree.kind) {
+                case BOUND:
+                    if (receiverVar != null) {
+                        receiverParam = receiverVar.sym;
+                    }
+                    break;
+                case UNBOUND:
+                    // The receiver is the first parameter, extract it and
+                    // adjust the SAM and unerased type lists accordingly
+                    receiverParam = addParameter("rec$", descPTypes.head, false);
+                    descPTypes = descPTypes.tail;
+                    break;
+            }
+            for (int i = 0; descPTypes.nonEmpty(); ++i) {
+                // By default use the implementation method parameter type
+                Type parmType = descPTypes.head;
+                addParameter("x$" + i, parmType, true);
+
+                // Advance to the next parameter
+                descPTypes = descPTypes.tail;
+            }
+
+            return receiverParam;
+        }
+
+        /**
+         * determine the receiver of the method call - the receiver can
+         * be a type qualifier, the synthetic receiver parameter or 'super'.
+         */
+        private JCExpression expressionInvoke(VarSymbol receiverParam) {
+            JCExpression qualifier = receiverParam != null ?
+                    make.at(tree.pos).Ident(receiverParam) :
+                    tree.getQualifierExpression();
+
+            //create the qualifier expression
+            JCFieldAccess select = make.Select(qualifier, tree.sym.name);
+            select.sym = tree.sym;
+            select.type = tree.referentType;
+
+            //create the method call expression
+            JCMethodInvocation apply = make.Apply(com.sun.tools.javac.util.List.nil(), select, args.toList()).
+                    setType(tree.referentType.getReturnType());
+
+            apply.varargsElement = tree.varargsElement;
+            return apply;
+        }
+
+        /**
+         * Lambda body to use for a 'new'.
+         */
+        private JCExpression expressionNew() {
+            Type expectedType = tree.referentType.getReturnType().hasTag(TypeTag.VOID) ?
+                    tree.expr.type : tree.referentType.getReturnType();
+            if (tree.kind == ReferenceKind.ARRAY_CTOR) {
+                //create the array creation expression
+                JCNewArray newArr = make.NewArray(
+                        make.Type(types.elemtype(expectedType)),
+                        com.sun.tools.javac.util.List.of(make.Ident(params.first())),
+                        null);
+                newArr.type = tree.getQualifierExpression().type;
+                return newArr;
+            } else {
+                //create the instance creation expression
+                //note that method reference syntax does not allow an explicit
+                //enclosing class (so the enclosing class is null)
+                // but this may need to be patched up later with the proxy for the outer this
+                JCExpression newType = make.Type(types.erasure(expectedType));
+                if (expectedType.tsym.type.getTypeArguments().nonEmpty()) {
+                    newType = make.TypeApply(newType, com.sun.tools.javac.util.List.nil());
+                }
+                JCNewClass newClass = make.NewClass(null,
+                        com.sun.tools.javac.util.List.nil(),
+                        newType,
+                        args.toList(),
+                        null);
+                newClass.constructor = tree.sym;
+                newClass.constructorType = tree.referentType;
+                newClass.type = expectedType;
+                newClass.varargsElement = tree.varargsElement;
+                return newClass;
+            }
+        }
+
+        private VarSymbol makeSyntheticVar(String name, Type type) {
+            VarSymbol vsym = new VarSymbol(PARAMETER | SYNTHETIC, names.fromString(name), type, owner);
+            vsym.pos = tree.pos;
+            return vsym;
+        }
+
+        private VarSymbol addParameter(String name, Type type, boolean genArg) {
+            VarSymbol vsym = makeSyntheticVar(name, type);
+            params.append(make.VarDef(vsym, null));
+            if (genArg) {
+                args.append(make.Ident(vsym));
+            }
+            return vsym;
+        }
+
+        boolean isThisOrSuper(JCExpression expression) {
+            return TreeInfo.isThisQualifier(expression) || TreeInfo.isSuperQualifier(tree);
+        }
+    }
+}
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -234,11 +234,11 @@
      *  non-accessible class, clone it with the qualifier class as owner.
      *
      *  @param sym    The accessed symbol
      *  @param site   The qualifier's type.
      */
-    Symbol binaryQualifier(Symbol sym, Type site) {
+    public Symbol binaryQualifier(Symbol sym, Type site) {
 
         if (site.hasTag(ARRAY)) {
             if (sym == syms.lengthVar ||
                 sym.owner != syms.arrayClass)
                 return sym;
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -1607,10 +1607,14 @@
             }
 
             if (shouldStop(CompileState.TRANSTYPES))
                 return;
 
+            if (Feature.REFLECT_METHODS.allowedInSource(source)) {
+                env.tree = ReflectMethods.instance(context).translateTopLevelClass(env.tree, localMake);
+            }
+
             env.tree = transTypes.translateTopLevelClass(env.tree, localMake);
             compileStates.put(env, CompileState.TRANSTYPES);
 
             if (shouldStop(CompileState.TRANSPATTERNS))
                 return;
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -23,10 +23,11 @@
  * questions.
  */
 
 package com.sun.tools.javac.model;
 
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -41,12 +42,15 @@
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.util.Elements;
 import javax.tools.JavaFileObject;
 import static javax.lang.model.util.ElementFilter.methodsIn;
 
+import com.sun.source.tree.Tree;
 import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.api.JavacScope;
 import com.sun.tools.javac.api.JavacTaskImpl;
+import com.sun.tools.javac.api.JavacTrees;
 import com.sun.tools.javac.code.*;
 import com.sun.tools.javac.code.Attribute.Compound;
 import com.sun.tools.javac.code.Directive.ExportsDirective;
 import com.sun.tools.javac.code.Directive.ExportsFlag;
 import com.sun.tools.javac.code.Directive.OpensDirective;
@@ -54,30 +58,34 @@
 import com.sun.tools.javac.code.Directive.RequiresDirective;
 import com.sun.tools.javac.code.Directive.RequiresFlag;
 import com.sun.tools.javac.code.Scope.WriteableScope;
 import com.sun.tools.javac.code.Source.Feature;
 import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.comp.Attr;
 import com.sun.tools.javac.comp.AttrContext;
 import com.sun.tools.javac.comp.Enter;
 import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.comp.ReflectMethods;
 import com.sun.tools.javac.main.JavaCompiler;
 import com.sun.tools.javac.processing.PrintingProcessor;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.*;
 import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
 import com.sun.tools.javac.tree.TreeScanner;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.DefinedBy.Api;
 import com.sun.tools.javac.util.Name;
 import static com.sun.tools.javac.code.Kinds.Kind.*;
 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
 import static com.sun.tools.javac.code.TypeTag.CLASS;
-import com.sun.tools.javac.comp.Attr;
+
 import com.sun.tools.javac.comp.Modules;
 import com.sun.tools.javac.comp.Resolve;
-import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
 import com.sun.tools.javac.resources.CompilerProperties.Notes;
+import jdk.internal.java.lang.reflect.code.op.CoreOp;
+
 import static com.sun.tools.javac.tree.JCTree.Tag.*;
 
 /**
  * Utility methods for operating on program elements.
  *
@@ -92,14 +100,17 @@
     private final Symtab syms;
     private final Modules modules;
     private final Names names;
     private final Types types;
     private final Enter enter;
+    private final JavacTrees javacTrees;
     private final Attr attr;
     private final Resolve resolve;
+    private final ReflectMethods reflectMethods;
     private final JavacTaskImpl javacTaskImpl;
     private final Log log;
+    private final TreeMaker make;
     private final boolean allowModules;
 
     public static JavacElements instance(Context context) {
         JavacElements instance = context.get(JavacElements.class);
         if (instance == null)
@@ -116,13 +127,16 @@
         names = Names.instance(context);
         types = Types.instance(context);
         enter = Enter.instance(context);
         attr = Attr.instance(context);
         resolve = Resolve.instance(context);
+        javacTrees = JavacTrees.instance(context);
+        reflectMethods = ReflectMethods.instance(context);
         JavacTask t = context.get(JavacTask.class);
         javacTaskImpl = t instanceof JavacTaskImpl taskImpl ? taskImpl : null;
         log = Log.instance(context);
+        make = TreeMaker.instance(context);
         Source source = Source.instance(context);
         allowModules = Feature.MODULES.allowedInSource(source);
     }
 
     @Override @DefinedBy(Api.LANGUAGE_MODEL)
@@ -791,10 +805,42 @@
             case TYP -> ((ClassSymbol) sym).classfile;
             default -> sym.enclClass().classfile;
         };
     }
 
+    @Override @DefinedBy(Api.LANGUAGE_MODEL)
+    public Optional<Object> getBody(ExecutableElement e) {
+        if (e.getModifiers().contains(Modifier.ABSTRACT) ||
+                e.getModifiers().contains(Modifier.NATIVE)) {
+            return Optional.empty();
+        }
+
+        CoreOp.FuncOp funcOp;
+        try {
+            JCMethodDecl methodTree = (JCMethodDecl)getTree(e);
+            JavacScope scope = javacTrees.getScope(javacTrees.getPath(e));
+            ClassSymbol enclosingClass = (ClassSymbol) scope.getEnclosingClass();
+            funcOp = attr.runWithAttributedMethod(scope.getEnv(), methodTree,
+                    attribBlock -> reflectMethods.getMethodBody(enclosingClass, methodTree, attribBlock, make));
+        } catch (RuntimeException ex) {  // ReflectMethods.UnsupportedASTException
+            // some other error occurred when attempting to attribute the method
+            // @@@ better report of error
+            ex.printStackTrace();
+            return Optional.empty();
+        }
+
+        // Reparse using API in java.base
+        try {
+            String opString = funcOp.toText();
+            Class<?> opParserClass = Class.forName("java.lang.reflect.code.parser.OpParser");
+            Method fromStringMethod = opParserClass.getDeclaredMethod("fromStringOfFuncOp", String.class);
+            return Optional.of(fromStringMethod.invoke(null, opString));
+        } catch (ReflectiveOperationException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     /**
      * Returns the tree node and compilation unit corresponding to this
      * element, or null if they can't be found.
      */
     private Pair<JCTree, JCCompilationUnit> getTreeAndTopLevel(Element e) {
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
@@ -1396,10 +1396,18 @@
 # 0: name (variable), 1: message segment
 compiler.err.cant.infer.local.var.type=\
     cannot infer type for local variable {0}\n\
     ({1})
 
+# 0: list of type
+compiler.err.cant.infer.quoted.lambda.return.type=\
+    cannot infer return type for quoted lambda expression\n\
+    (found: {0})
+
+compiler.err.quoted.lambda.must.be.explicit=\
+    Quoted lambda cannot have implicit parameter types
+
 # 0: name
 compiler.err.restricted.type.not.allowed.here=\
     ''{0}'' is not allowed here
 
 # 0: name
@@ -4082,5 +4090,30 @@
 
 # 0: type
 compiler.err.enclosing.class.type.non.denotable=\
     enclosing class type: {0}\n\
     is non-denotable, try casting to a denotable type
+
+########################################
+# Diagnostics for code reflection
+########################################
+
+compiler.misc.feature.reflect.methods=\
+    code reflection
+
+# 0: symbol, 1: symbol, 2: string
+compiler.note.method.ir.dump=\
+    code reflection enabled for method {0}.{1}\n\
+    {2}
+
+# 0: symbol, 1: symbol, 2: string
+compiler.note.method.ir.skip=\
+    unsupported code reflection node {2} found in method {0}.{1}
+
+# 0: string
+compiler.note.quoted.ir.dump=\
+    code reflection enabled for method quoted lambda\n\
+    {0}
+
+# 0: string
+compiler.note.quoted.ir.skip=\
+    unsupported code reflection node {0} found in quoted lambda
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
@@ -36,10 +36,11 @@
 import com.sun.source.tree.*;
 import com.sun.tools.javac.code.*;
 import com.sun.tools.javac.code.Directive.RequiresDirective;
 import com.sun.tools.javac.code.Scope.*;
 import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.code.Type.MethodType;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.DefinedBy.Api;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
 import com.sun.tools.javac.util.List;
 
@@ -751,23 +752,35 @@
     }
 
     /**
      * Common supertype for all functional expression trees (lambda and method references)
      */
-    public abstract static class JCFunctionalExpression extends JCPolyExpression {
+    public abstract static sealed class JCFunctionalExpression extends JCPolyExpression
+                                                               permits JCLambda, JCMemberReference {
 
         public JCFunctionalExpression() {
             //a functional expression is always a 'true' poly
             polyKind = PolyKind.POLY;
         }
 
         /** list of target types inferred for this functional expression. */
         public Type target;
+        /** code reflection specific metadata. */
+        public CodeReflectionInfo codeReflectionInfo;
 
         public Type getDescriptorType(Types types) {
-            return target != null ? types.findDescriptorType(target) : types.createErrorType(null);
+            if (target == null) {
+                return types.createErrorType(null);
+            } else if (target.hasTag(TypeTag.METHOD)) {
+                // this is a quoted expression
+                return target;
+            } else {
+                return types.findDescriptorType(target);
+            }
         }
+
+        public record CodeReflectionInfo(Symbol quotedField, List<JCExpression> capturedArgs) { }
     }
 
     /**
      * A class definition.
      */
@@ -1955,11 +1968,11 @@
     }
 
     /**
      * A lambda expression.
      */
-    public static class JCLambda extends JCFunctionalExpression implements LambdaExpressionTree {
+    public static final class JCLambda extends JCFunctionalExpression implements LambdaExpressionTree {
 
         public enum ParameterKind {
             IMPLICIT,
             EXPLICIT
         }
@@ -2541,11 +2554,11 @@
     }
 
     /**
      * Selects a member expression.
      */
-    public static class JCMemberReference extends JCFunctionalExpression implements MemberReferenceTree {
+    public static non-sealed class JCMemberReference extends JCFunctionalExpression implements MemberReferenceTree {
 
         public ReferenceMode mode;
         public ReferenceKind kind;
         public Name name;
         public JCExpression expr;
diff a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
@@ -165,10 +165,23 @@
             default:
                 return false;
         }
     }
 
+    public static boolean isSuperQualifier(JCTree tree) {
+        switch (tree.getTag()) {
+            case PARENS:
+                return isThisQualifier(skipParens(tree));
+            case IDENT: {
+                JCIdent id = (JCIdent)tree;
+                return id.name == id.name.table.names._super;
+            }
+            default:
+                return false;
+        }
+    }
+
     /** Is this tree an identifier, possibly qualified by 'this'?
      */
     public static boolean isIdentOrThisDotIdent(JCTree tree) {
         switch (tree.getTag()) {
             case PARENS:
diff a/test/jdk/TEST.groups b/test/jdk/TEST.groups
--- a/test/jdk/TEST.groups
+++ b/test/jdk/TEST.groups
@@ -659,6 +659,11 @@
     :jdk_nio \
     :jdk_svc
 
 jdk_core_no_security = \
    :jdk_core \
-   -:jdk_security
+   -:jdk_security
+
+
+jdk_lang_reflect_code = \
+   java/lang/reflect/code
+
diff a/test/jdk/java/lang/reflect/Method/cr/TreeAccessTest.java b/test/jdk/java/lang/reflect/Method/cr/TreeAccessTest.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/Method/cr/TreeAccessTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+
+/**
+ * @test
+ * @summary Test symbolic access of method
+ * @run testng TreeAccessTest
+ */
+
+public class TreeAccessTest {
+
+    @CodeReflection
+    public int m(String s) {
+        return s.length();
+    }
+
+    @Test
+    void testTreeAccess() throws Exception {
+        Method m = TreeAccessTest.class.getDeclaredMethod("m", String.class);
+
+        Optional<CoreOp.FuncOp> tree = m.getCodeModel();
+        Assert.assertTrue(tree.isPresent());
+
+        CoreOp.FuncOp methodTree = tree.get();
+
+        String expectedTree = """
+                func @"m" (%0 : TreeAccessTest, %1 : java.lang.String)int -> {
+                      %2 : Var<java.lang.String> = var %1 @"s";
+                      %3 : java.lang.String = var.load %2;
+                      %4 : int = invoke %3 @"java.lang.String::length()int";
+                      return %4;
+                };
+                """;
+
+        Assert.assertEquals(canonicalizeModel(methodTree), canonicalizeModel(expectedTree));
+    }
+
+    @Test
+    public int n(String s) {
+        return s.length();
+    }
+
+    @Test
+    void testNoTree() throws Exception {
+        Method m = TreeAccessTest.class.getDeclaredMethod("n", String.class);
+
+        Optional<CoreOp.FuncOp> tree = m.getCodeModel();
+        Assert.assertTrue(tree.isEmpty());
+    }
+
+
+    // serializes dropping location information, parses, and then serializes, dropping location information
+    static String canonicalizeModel(Op o) {
+        return canonicalizeModel(serialize(o));
+    }
+
+    // parses, and then serializes, dropping location information
+    static String canonicalizeModel(String d) {
+        Op o;
+        try {
+            o = OpParser.fromString(ExtendedOp.FACTORY, d).get(0);
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+        return serialize(o);
+    }
+
+    // serializes, dropping location information
+    static String serialize(Op o) {
+        StringWriter w = new StringWriter();
+        OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
+        return w.toString();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/CoreBinaryOpsTest.java b/test/jdk/java/lang/reflect/code/CoreBinaryOpsTest.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/CoreBinaryOpsTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run junit CoreBinaryOpsTest
+ */
+
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.function.ThrowingSupplier;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.AccessFlag;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.TypeElement;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.util.*;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CoreBinaryOpsTest {
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_BOOLEAN)
+    static int and(int left, int right) {
+        return left & right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_FLOATING_POINT)
+    static int add(int left, int right) {
+        return left + right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_FLOATING_POINT)
+    static int div(int left, int right) {
+        return left / right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INT_LONG)
+    static int leftShift(int left, int right) {
+        return left << right;
+    }
+
+    @CodeReflection
+    @Direct
+    static int leftShiftIL(int left, long right) {
+        return left << right;
+    }
+
+    @CodeReflection
+    @Direct
+    static long leftShiftLI(long left, int right) {
+        return left << right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_FLOATING_POINT)
+    static int mod(int left, int right) {
+        return left % right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_FLOATING_POINT)
+    static int mul(int left, int right) {
+        return left * right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_BOOLEAN)
+    static int or(int left, int right) {
+        return left | right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INT_LONG)
+    static int signedRightShift(int left, int right) {
+        return left >> right;
+    }
+
+    @CodeReflection
+    @Direct
+    static int signedRightShiftIL(int left, long right) {
+        return left >> right;
+    }
+
+    @CodeReflection
+    @Direct
+    static long signedRightShiftLI(long left, int right) {
+        return left >> right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_FLOATING_POINT)
+    static int sub(int left, int right) {
+        return left - right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INT_LONG)
+    static int unsignedRightShift(int left, int right) {
+        return left >>> right;
+    }
+
+    @CodeReflection
+    @Direct
+    static int unsignedRightShiftIL(int left, long right) {
+        return left >>> right;
+    }
+
+    @CodeReflection
+    @Direct
+    static long unsignedRightShiftLI(long left, int right) {
+        return left >>> right;
+    }
+
+    @CodeReflection
+    @SupportedTypes(TypeList.INTEGRAL_BOOLEAN)
+    static int xor(int left, int right) {
+        return left ^ right;
+    }
+
+    @ParameterizedTest
+    @CodeReflectionExecutionSource
+    void test(CoreOp.FuncOp funcOp, Object left, Object right) {
+        Result interpret = runCatching(() -> interpret(left, right, funcOp));
+        Result bytecode = runCatching(() -> bytecode(left, right, funcOp));
+        assertResults(interpret, bytecode);
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    @interface SupportedTypes {
+        TypeList value();
+    }
+
+    enum TypeList {
+        INT_LONG(int.class, long.class),
+        INTEGRAL_BOOLEAN(int.class, long.class, byte.class, short.class, char.class, boolean.class),
+        INTEGRAL_FLOATING_POINT(int.class, long.class, byte.class, short.class, char.class, float.class, double.class);
+
+        private final Class<?>[] types;
+
+        TypeList(Class<?>... types) {
+            this.types = types;
+        }
+
+        public Class<?>[] types() {
+            return types;
+        }
+    }
+
+    // mark as "do not transform"
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    @interface Direct {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    @ArgumentsSource(CodeReflectionSourceProvider.class)
+    @interface CodeReflectionExecutionSource {
+    }
+
+    static class CodeReflectionSourceProvider implements ArgumentsProvider {
+        private static final Map<JavaType, List<?>> INTERESTING_INPUTS = Map.of(
+                // explicit type parameters to ensure boxing results in the expected type
+                JavaType.INT, List.<Integer>of(Integer.MIN_VALUE, Integer.MAX_VALUE, 1, 0, -1),
+                JavaType.LONG, List.<Long>of(Long.MIN_VALUE, Long.MAX_VALUE, 1L, 0L, -1L),
+                JavaType.BYTE, List.<Byte>of(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 1, (byte) 0, (byte) -1),
+                JavaType.SHORT, List.<Short>of(Short.MIN_VALUE, Short.MAX_VALUE, (short) 1, (short) 0, (short) -1),
+                JavaType.CHAR, List.<Character>of(Character.MIN_VALUE, Character.MAX_VALUE, (char) 1, (char) 0, (char) -1),
+                JavaType.DOUBLE, List.<Double>of(Double.MIN_VALUE, Double.MAX_VALUE, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.MIN_NORMAL, 1d, 0d, -1d),
+                JavaType.FLOAT, List.<Float>of(Float.MIN_VALUE, Float.MAX_VALUE, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.MIN_NORMAL, 1f, 0f, -1f),
+                JavaType.BOOLEAN, List.<Boolean>of(true, false)
+        );
+
+        @Override
+        public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
+            Method testMethod = extensionContext.getRequiredTestMethod();
+            return codeReflectionMethods(extensionContext.getRequiredTestClass())
+                    .flatMap(method -> {
+                        CoreOp.FuncOp funcOp = method.getCodeModel().orElseThrow(
+                                () -> new IllegalStateException("Expected code model to be present for method " + method)
+                        );
+                        SupportedTypes supportedTypes = method.getAnnotation(SupportedTypes.class);
+                        if (method.isAnnotationPresent(Direct.class)) {
+                            if (supportedTypes != null) {
+                                throw new IllegalArgumentException("Direct should not be combined with SupportedTypes");
+                            }
+                            return Stream.of(funcOp);
+                        }
+                        if (supportedTypes == null || supportedTypes.value().types().length == 0) {
+                            throw new IllegalArgumentException("Missing supported types");
+                        }
+                        return Arrays.stream(supportedTypes.value().types())
+                                .map(type -> retype(funcOp, type));
+                    })
+                    .flatMap(transformedFunc -> argumentsForMethod(transformedFunc, testMethod));
+        }
+
+        private static <T> Stream<List<T>> cartesianProduct(List<List<? extends T>> source) {
+            if (source.isEmpty()) {
+                return Stream.of(new ArrayList<>());
+            }
+            return source.getFirst().stream()
+                    .flatMap(e -> cartesianProduct(source.subList(1, source.size())).map(l -> {
+                        ArrayList<T> newList = new ArrayList<>(l);
+                        newList.add(e);
+                        return newList;
+                    }));
+        }
+
+        private static CoreOp.FuncOp retype(CoreOp.FuncOp original, Class<?> newType) {
+            JavaType type = JavaType.type(newType);
+            FunctionType functionType = original.invokableType();
+            if (functionType.parameterTypes().stream().allMatch(t -> t.equals(type))) {
+                return original; // already expected type
+            }
+            if (functionType.parameterTypes().stream().distinct().count() != 1) {
+                original.writeTo(System.err);
+                throw new IllegalArgumentException("Only FuncOps with exactly one distinct parameter type are supported");
+            }
+            // if the return type does not match the input types, we keep it
+            TypeElement retType = functionType.returnType().equals(functionType.parameterTypes().getFirst())
+                    ? type
+                    : functionType.returnType();
+            return CoreOp.func(original.funcName(), FunctionType.functionType(retType, type, type))
+                    .body(builder -> builder.transformBody(original.body(), builder.parameters(), (block, op) -> {
+                                block.context().mapValue(op.result(), block.op(retype(block.context(), op)));
+                                return block;
+                            })
+                    );
+        }
+
+        private static Op retype(CopyContext context, Op op) {
+            return switch (op) {
+                case CoreOp.VarOp varOp ->
+                        CoreOp.var(varOp.varName(), context.getValueOrDefault(varOp.operands().getFirst(), varOp.operands().getFirst()));
+                default -> op;
+            };
+        }
+
+        private static Stream<Arguments> argumentsForMethod(CoreOp.FuncOp funcOp, Method testMethod) {
+            Parameter[] testMethodParameters = testMethod.getParameters();
+            List<TypeElement> funcParameters = funcOp.invokableType().parameterTypes();
+            if (testMethodParameters.length - 1 != funcParameters.size()) {
+                throw new IllegalArgumentException("method " + testMethod + " does not take the correct number of parameters");
+            }
+            if (testMethodParameters[0].getType() != CoreOp.FuncOp.class) {
+                throw new IllegalArgumentException("method " + testMethod + " does not take a leading FuncOp argument");
+            }
+            Named<CoreOp.FuncOp> opNamed = Named.of(funcOp.funcName() + "{" + funcOp.invokableType() + "}", funcOp);
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+            for (int i = 1; i < testMethodParameters.length; i++) {
+                Class<?> resolved = resolveParameter(funcParameters.get(i - 1), lookup);
+                if (!isCompatible(resolved, testMethodParameters[i].getType())) {
+                    System.out.println(testMethod + " does not accept inputs of type " + resolved + " at index " + i);
+                    return Stream.empty();
+                }
+            }
+            List<List<?>> allInputs = new ArrayList<>();
+            for (TypeElement parameterType : funcParameters) {
+                allInputs.add(INTERESTING_INPUTS.get((JavaType) parameterType));
+            }
+            return cartesianProduct(allInputs)
+                    .map(objects -> {
+                        objects.add(opNamed);
+                        return objects.reversed().toArray(); // reverse so FuncOp is at the beginning
+                    })
+                    .map(Arguments::of);
+        }
+
+        private static Class<?> resolveParameter(TypeElement typeElement, MethodHandles.Lookup lookup) {
+            try {
+                return (Class<?>)((JavaType) typeElement).erasure().resolve(lookup);
+            } catch (ReflectiveOperationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        // check whether elements of type sourceType can be passed to a parameter of parameterType
+        private static boolean isCompatible(Class<?> sourceType, Class<?> parameterType) {
+            return wrapped(parameterType).isAssignableFrom(wrapped(sourceType));
+        }
+
+        private static Class<?> wrapped(Class<?> target) {
+            return MethodType.methodType(target).wrap().returnType();
+        }
+
+        private static Stream<Method> codeReflectionMethods(Class<?> testClass) {
+            return Arrays.stream(testClass.getDeclaredMethods())
+                    .filter(method -> method.accessFlags().contains(AccessFlag.STATIC))
+                    .filter(method -> method.isAnnotationPresent(CodeReflection.class));
+        }
+
+    }
+
+    private static Object interpret(Object left, Object right, CoreOp.FuncOp op) {
+        return Interpreter.invoke(MethodHandles.lookup(), op, left, right);
+    }
+
+    private static Object bytecode(Object left, Object right, CoreOp.FuncOp op) throws Throwable {
+        CoreOp.FuncOp func = SSA.transform(op.transform(OpTransformer.LOWERING_TRANSFORMER));
+        MethodHandle handle = BytecodeGenerator.generate(MethodHandles.lookup(), func);
+        return handle.invoke(left, right);
+    }
+
+    private static void assertResults(Result first, Result second) {
+        System.out.println("first: " + first);
+        System.out.println("second: " + second);
+        // either the same error occurred on both or no error occurred
+        if (first.throwable != null || second.throwable != null) {
+            assertNotNull(first.throwable, () -> "only second threw an exception: " + second.throwable);
+            assertNotNull(second.throwable, () -> "only first threw an exception: " + first.throwable);
+            if (first.throwable.getClass() != second.throwable.getClass()) {
+                first.throwable.printStackTrace();
+                second.throwable.printStackTrace();
+                fail("Different exceptions were thrown");
+            }
+            return;
+        }
+        // otherwise, both results should be non-null and equals
+        assertNotNull(first.onSuccess);
+        assertEquals(first.onSuccess, second.onSuccess);
+    }
+
+    private static <T> Result runCatching(ThrowingSupplier<T> supplier) {
+        Object value = null;
+        Throwable interpretThrowable = null;
+        try {
+            value = supplier.get();
+        } catch (Throwable t) {
+            interpretThrowable = t;
+        }
+        return new Result(value, interpretThrowable);
+    }
+
+    record Result(Object onSuccess, Throwable throwable) {
+    }
+
+}
diff a/test/jdk/java/lang/reflect/code/TestArrayCreation.java b/test/jdk/java/lang/reflect/code/TestArrayCreation.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestArrayCreation.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestArrayCreation
+ */
+
+public class TestArrayCreation {
+    @CodeReflection
+    public static String[] f() {
+        return new String[10];
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f());
+    }
+
+    @CodeReflection
+    public static String[][] f2() {
+        return new String[10][];
+    }
+
+    @Test
+    public void testf2() {
+        CoreOp.FuncOp f = getFuncOp("f2");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f2());
+    }
+
+    @CodeReflection
+    public static String[][] f3() {
+        return new String[10][10];
+    }
+
+    @Test
+    public void testf3() {
+        CoreOp.FuncOp f = getFuncOp("f3");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f3());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestArrayCreation.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestArrayTypes.java b/test/jdk/java/lang/reflect/code/TestArrayTypes.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestArrayTypes.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestArrayTypes
+ */
+
+public class TestArrayTypes {
+    @CodeReflection
+    public static Class<?> f() {
+        return String[].class;
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f());
+    }
+
+    @CodeReflection
+    public static Class<?> f2() {
+        return int[][].class;
+    }
+
+    @Test
+    public void testf2() {
+        CoreOp.FuncOp f = getFuncOp("f2");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f2());
+    }
+
+    @CodeReflection
+    public static Class<?> f3() {
+        return CoreOp.ArrayLengthOp[][][][][][][].class;
+    }
+
+    @Test
+    public void testf3() {
+        CoreOp.FuncOp f = getFuncOp("f3");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f), f3());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestArrayTypes.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBinops.java b/test/jdk/java/lang/reflect/code/TestBinops.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBinops.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestBinops
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestBinops {
+
+    @CodeReflection
+    public static boolean not(boolean b) {
+        return !b;
+    }
+
+    @Test
+    public void testNot() {
+        CoreOp.FuncOp f = getFuncOp("not");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, true), not(true));
+        Assert.assertEquals(Interpreter.invoke(f, false), not(false));
+    }
+
+    @CodeReflection
+    public static int mod(int a, int b) {
+        return a % b;
+    }
+
+    @Test
+    public void testMod() {
+        CoreOp.FuncOp f = getFuncOp("mod");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 10, 3), mod(10, 3));
+    }
+
+    @CodeReflection
+    public static int bitand(int a, int b) {
+        return a & b;
+    }
+
+    @Test
+    public void testBitand() {
+        CoreOp.FuncOp f = getFuncOp("bitand");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 10, 3), bitand(10, 3));
+    }
+
+    @CodeReflection
+    public static int bitor(int a, int b) {
+        return a | b;
+    }
+
+    @Test
+    public void testBitor() {
+        CoreOp.FuncOp f = getFuncOp("bitor");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 10, 3), bitor(10, 3));
+    }
+
+    @CodeReflection
+    public static int bitxor(int a, int b) {
+        return a ^ b;
+    }
+
+    @Test
+    public void testBitxor() {
+        CoreOp.FuncOp f = getFuncOp("bitxor");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 10, 3), bitxor(10, 3));
+    }
+
+    @CodeReflection
+    public static boolean booland(boolean a, boolean b) {
+        return a & b;
+    }
+
+    @Test
+    public void testBooland() {
+        CoreOp.FuncOp f = getFuncOp("booland");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, true, false), booland(true, false));
+    }
+
+    @CodeReflection
+    public static boolean boolor(boolean a, boolean b) {
+        return a | b;
+    }
+
+    @Test
+    public void testBoolor() {
+        CoreOp.FuncOp f = getFuncOp("boolor");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, false, true), boolor(false, true));
+    }
+
+    @CodeReflection
+    public static boolean boolxor(boolean a, boolean b) {
+        return a ^ b;
+    }
+
+    @Test
+    public void testBoolxor() {
+        CoreOp.FuncOp f = getFuncOp("boolxor");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, true, true), boolxor(true, true));
+    }
+
+    @CodeReflection
+    public static double doublemod(double a, double b) {
+        return a % b;
+    }
+
+    @Test
+    public void testDoublemod() {
+        CoreOp.FuncOp f = getFuncOp("doublemod");
+
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 15.6, 2.1), doublemod(15.6, 2.1));
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestBinops.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBlockIndexes.java b/test/jdk/java/lang/reflect/code/TestBlockIndexes.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBlockIndexes.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestBlockIndexes
+ */
+
+public class TestBlockIndexes {
+
+    @CodeReflection
+    static int f(int[] a) {
+        int sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            sum += a[i];
+        }
+        return sum;
+    }
+
+    @Test
+    public void testBlockIndexes() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        assertBlockIndexes(f);
+
+        AtomicBoolean first = new AtomicBoolean(true);
+        f = f.transform((block, op) -> {
+            if (first.getAndSet(false)) {
+                // Create some blocks without predecessors
+                for (int i = 0; i < 5; i++) {
+                    Block.Builder redundant = block.block();
+                    redundant.op(CoreOp._return());
+                }
+            }
+            block.op(op);
+            return block;
+        });
+        assertBlockIndexes(f);
+    }
+
+    static void assertBlockIndexes(CoreOp.FuncOp f) {
+        for (Block b : f.body().blocks()) {
+            Assert.assertEquals(b.index(), b.parentBody().blocks().indexOf(b));
+        }
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestBlockIndexes.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBlockOp.java b/test/jdk/java/lang/reflect/code/TestBlockOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBlockOp.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestBlockOp
+ */
+
+public class TestBlockOp {
+
+    @CodeReflection
+    public static int f() {
+        int i = 0;
+
+        {
+            i++;
+        }
+
+        {
+            i += 2;
+        }
+
+        return i;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestBlockOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), f());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBlockParameters.java b/test/jdk/java/lang/reflect/code/TestBlockParameters.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBlockParameters.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestBlockParameters
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.CodeElement;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.INT;
+
+public class TestBlockParameters {
+    static FuncOp m() {
+        return func("f", functionType(INT, INT, INT))
+                .body(fe -> {
+                    LambdaOp lop = lambda(fe.parentBody(), functionType(INT, INT), JavaType.type(FunctionType.class))
+                            .body(le -> {
+                                le.op(_return(le.parameters().get(0)));
+                            });
+                    fe.op(lop);
+                    Block.Builder b = fe.block(INT, INT);
+                    fe.op(branch(b.successor(fe.parameters())));
+
+                    b.op(_return(b.parameters().get(0)));
+                });
+    }
+
+    @Test
+    public void t() {
+        FuncOp m = m();
+        m.traverse(null, CodeElement.blockVisitor((_, b) -> {
+            for (Block.Parameter p : b.parameters()) {
+                testBlockParameter(p);
+            }
+
+            return null;
+        }));
+    }
+
+    void testBlockParameter(Block.Parameter p) {
+        Assert.assertEquals(p.index(), p.declaringBlock().parameters().indexOf(p));
+
+        if (p.invokableOperation() instanceof Op.Invokable iop) {
+            Assert.assertTrue(p.declaringBlock().isEntryBlock());
+            Assert.assertEquals(p.index(), iop.parameters().indexOf(p));
+        } else {
+            // There are no non-invokable operations with bodies in the model
+            Assert.assertFalse(p.declaringBlock().isEntryBlock());
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBreakContinue.java b/test/jdk/java/lang/reflect/code/TestBreakContinue.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBreakContinue.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestBreakContinue
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.BitSet;
+import java.util.Optional;
+import java.util.function.IntUnaryOperator;
+import java.util.stream.Stream;
+
+public class TestBreakContinue {
+
+    @CodeReflection
+    public static BitSet forLoopBreakContinue(IntUnaryOperator f) {
+        BitSet b = new BitSet();
+        for (int i = 0; i < 8; i++) {
+            b.set(i);
+            int r = f.applyAsInt(i);
+            if (r == 0) {
+                continue;
+            } else if (r == 1) {
+                break;
+            }
+            b.set(i * 2);
+        }
+        return b;
+    }
+
+    @Test
+    public void testForLoopBreakContinue() {
+        CoreOp.FuncOp f = getFuncOp("forLoopBreakContinue");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        IntUnaryOperator o = i -> {
+            if (i <= 3) return -1;
+            if (i <= 5) return 0;
+            return 1;
+        };
+        Assert.assertEquals(Interpreter.invoke(lf, o), forLoopBreakContinue(o));
+    }
+
+    @CodeReflection
+    public static BitSet nestedForLoopBreakContinue(IntUnaryOperator f) {
+        BitSet b = new BitSet();
+        for (int j = 0; j < 8; j++) {
+            b.set(j);
+            int r = f.applyAsInt(j);
+            if (r == 0) {
+                continue;
+            } else if (r == 1) {
+                break;
+            }
+            for (int i = 8; i < 16; i++) {
+                b.set(i);
+                r = f.applyAsInt(i);
+                if (r == 2) {
+                    continue;
+                } else if (r == 3) {
+                    break;
+                }
+                b.set(i * 2);
+            }
+            b.set(j * 2);
+        }
+        return b;
+    }
+
+    @Test
+    public void testNestedForLoopBreakContinue() {
+        CoreOp.FuncOp f = getFuncOp("nestedForLoopBreakContinue");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int r = -1; r < 4; r++) {
+            int fr = r;
+            IntUnaryOperator o = i -> fr;
+            Assert.assertEquals(Interpreter.invoke(lf, o), nestedForLoopBreakContinue(o));
+        }
+    }
+
+
+    @CodeReflection
+    public static BitSet forLoopLabeledBreakContinue(IntUnaryOperator f) {
+        BitSet b = new BitSet();
+        outer: for (int j = 0; j < 8; j++) {
+            b.set(j);
+            int r = f.applyAsInt(j);
+            if (r == 0) {
+                continue outer;
+            } else if (r == 1) {
+                break outer;
+            }
+            inner: for (int i = 8; i < 16; i++) {
+                b.set(i);
+                r = f.applyAsInt(i);
+                if (r == 2) {
+                    continue inner;
+                } else if (r == 3) {
+                    break inner;
+                } else if (r == 4) {
+                    continue outer;
+                } else if (r == 5) {
+                    break outer;
+                }
+                b.set(i * 2);
+            }
+            b.set(j * 2);
+        }
+        return b;
+    }
+
+    @Test
+    public void testForLoopLabeledBreakContinue() {
+        CoreOp.FuncOp f = getFuncOp("forLoopLabeledBreakContinue");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int r = -1; r < 6; r++) {
+            int fr = r;
+            IntUnaryOperator o = i -> fr;
+            Assert.assertEquals(Interpreter.invoke(lf, o), forLoopLabeledBreakContinue(o));
+        }
+    }
+
+    @CodeReflection
+    public static BitSet blockBreak(IntUnaryOperator f) {
+        BitSet b = new BitSet();
+        a: b: {
+            b.set(1);
+            if (f.applyAsInt(1) != 0) {
+                break a;
+            }
+            b.set(2);
+            if (f.applyAsInt(2) != 0) {
+                break b;
+            }
+            b.set(3);
+            c: {
+                b.set(4);
+                if (f.applyAsInt(4) != 0) {
+                    break a;
+                }
+                b.set(5);
+                if (f.applyAsInt(5) != 0) {
+                    break b;
+                }
+                b.set(6);
+                if (f.applyAsInt(6) != 0) {
+                    break c;
+                }
+                b.set(7);
+            }
+            b.set(8);
+        }
+        return b;
+    }
+
+    @Test
+    public void testBlockBreak() {
+        CoreOp.FuncOp f = getFuncOp("blockBreak");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int i = 0; i < 7; i++) {
+            int fi = i;
+            IntUnaryOperator o = v -> v == fi ? 1 : 0;
+            Assert.assertEquals(Interpreter.invoke(lf, o), blockBreak(o));
+        }
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestBreakContinue.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestBuild.java b/test/jdk/java/lang/reflect/code/TestBuild.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestBuild.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.analysis.SSA;
+import java.util.function.IntBinaryOperator;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.FunctionType.VOID;
+import static java.lang.reflect.code.type.JavaType.INT;
+import static java.lang.reflect.code.type.JavaType.type;
+
+/*
+ * @test
+ * @run testng TestBuild
+ */
+
+public class TestBuild {
+
+    public LambdaOp f() {
+        IntBinaryOperator ibo = (IntBinaryOperator & Quotable) (a, b) -> a + b;
+        Quotable iboq = (Quotable) ibo;
+        return SSA.transform((LambdaOp) iboq.quoted().op());
+    }
+
+    @Test
+    public void testBoundValueAsOperand() {
+        LambdaOp f = f();
+
+        var body = Body.Builder.of(null, f.invokableType());
+        var block = body.entryBlock();
+
+        var a = f.body().entryBlock().parameters().get(0);
+        var b = f.body().entryBlock().parameters().get(1);
+        // Passing bound values as operands to a new unbound operation
+        var addop = add(a, b);
+
+        Assert.assertThrows(IllegalStateException.class, () -> block.op(addop));
+    }
+
+    @Test
+    public void testBoundValueAsHeaderArgument() {
+        LambdaOp f = f();
+
+        var body = Body.Builder.of(null, f.invokableType());
+        var block = body.entryBlock();
+        var anotherBlock = block.block(INT, INT);
+
+        var a = f.body().entryBlock().parameters().get(0);
+        var b = f.body().entryBlock().parameters().get(1);
+        // Passing bound values as header arguments of a header
+        // that is the successor of a terminal operation
+        var brop = branch(anotherBlock.successor(a, b));
+
+        Assert.assertThrows(IllegalStateException.class, () -> block.op(brop));
+    }
+
+    @Test
+    public void testUnmappedBoundValue() {
+        LambdaOp f = f();
+
+        var body = Body.Builder.of(null, f.invokableType());
+        var block = body.entryBlock();
+
+        var freturnOp = f.body().entryBlock().terminatingOp();
+        // Unmapped bound value that is operand of the bound return op
+        Assert.assertThrows(IllegalArgumentException.class, () -> block.op(freturnOp));
+    }
+
+    @Test
+    public void testMappingToBoundValue() {
+        LambdaOp f = f();
+
+        var body = Body.Builder.of(null, f.invokableType());
+        var block = body.entryBlock();
+
+        var result = f.body().entryBlock().firstOp().result();
+        // Mapping to a bound value
+        Assert.assertThrows(IllegalArgumentException.class, () -> block.context().mapValue(result, result));
+    }
+
+    @Test
+    public void testMappedBoundValue() {
+        LambdaOp f = f();
+
+        var body = Body.Builder.of(null, f.invokableType());
+        var block = body.entryBlock();
+
+        var a = block.parameters().get(0);
+        var b = block.parameters().get(1);
+        var result = block.op(add(a, b));
+        // Map the bound value used as the operand to the bound return op to
+        // the above value
+        block.context().mapValue(f.body().entryBlock().firstOp().result(), result);
+
+        var freturnOp = f.body().entryBlock().terminatingOp();
+        // No error since values (operands) are mapped
+        block.op(freturnOp);
+    }
+
+    @Test
+    public void testPartiallyConstructedValueAccess() {
+        var body = Body.Builder.of(null, functionType(INT, INT, INT));
+        var block = body.entryBlock();
+
+        Block.Parameter a = block.parameters().get(0);
+        Block.Parameter b = block.parameters().get(1);
+        Op.Result result = block.op(add(a, b));
+
+        // Access the declaring block of values before the block and its body are
+        // constructed
+        Assert.assertThrows(IllegalStateException.class, a::declaringBlock);
+        Assert.assertThrows(IllegalStateException.class, result::declaringBlock);
+        // Access to parent block/body of operation result before they are constructed
+        Assert.assertThrows(IllegalStateException.class, result.op()::parentBlock);
+        Assert.assertThrows(IllegalStateException.class, result.op()::ancestorBody);
+        // Access to set of users before constructed
+        Assert.assertThrows(IllegalStateException.class, a::uses);
+
+        block.op(_return(result));
+
+        var f = func("f", body);
+
+        Assert.assertNotNull(a.declaringBlock());
+        Assert.assertNotNull(result.declaringBlock());
+        Assert.assertNotNull(result.op().parentBlock());
+        Assert.assertNotNull(result.op().ancestorBody());
+        Assert.assertNotNull(a.uses());
+    }
+
+    @Test
+    public void testPartiallyConstructedHeaderAccess() {
+        var body = Body.Builder.of(null, functionType(INT, INT, INT));
+        var block = body.entryBlock();
+        var anotherBlock = block.block(INT, INT);
+
+        var a = block.parameters().get(0);
+        var b = block.parameters().get(1);
+        Block.Reference successor = anotherBlock.successor(a, b);
+        // Access to target block before constructed
+        Assert.assertThrows(IllegalStateException.class, successor::targetBlock);
+        block.op(branch(anotherBlock.successor(a, b)));
+
+        a = anotherBlock.parameters().get(0);
+        b = anotherBlock.parameters().get(1);
+        var result = anotherBlock.op(add(a, b));
+        anotherBlock.op(_return(result));
+
+        var f = func("f", body);
+
+        Assert.assertNotNull(successor.targetBlock());
+    }
+
+    @Test
+    public void testValueUseFromOtherModel() {
+        var abody = Body.Builder.of(null, functionType(INT, INT, INT));
+        var ablock = abody.entryBlock();
+        var aa = ablock.parameters().get(0);
+        var ab = ablock.parameters().get(1);
+
+        var bbody = Body.Builder.of(null, abody.bodyType());
+        var bblock = bbody.entryBlock();
+
+        // Operation uses values from another model
+        var addOp = add(aa, ab);
+        Assert.assertThrows(IllegalStateException.class, () -> bblock.op(addOp));
+    }
+
+    @Test
+    public void testHeaderFromOtherBody() {
+        var abody = Body.Builder.of(null, VOID);
+        var ablock = abody.entryBlock().block();
+
+        var bbody = Body.Builder.of(null, VOID);
+        var bblock = bbody.entryBlock();
+
+        // Operation uses header with target block from another model
+        var brOp = branch(ablock.successor());
+        Assert.assertThrows(IllegalStateException.class, () -> bblock.op(brOp));
+    }
+
+    @Test
+    public void testHeaderFromEntryBlock() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        Assert.assertThrows(IllegalStateException.class, block::successor);
+    }
+
+    @Test
+    public void testBuiltBodyBuilder() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(_return());
+        func("f", body);
+
+        // Body is built
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testBodyBuilderWithBuiltAncestor() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(_return());
+        func("f", body);
+
+        // ancestor body is built
+        Assert.assertThrows(IllegalStateException.class, () -> Body.Builder.of(body, VOID));
+    }
+
+    @Test
+    public void testBodyBuilderWithUnbuiltChildren() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(_return());
+
+        Body.Builder.of(body, VOID);
+
+        // Great-grandchild body is not built
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testMistmatchedBody() {
+        var body1 = Body.Builder.of(null, VOID);
+        var block1 = body1.entryBlock();
+
+        var anotherBody = Body.Builder.of(null, VOID);
+
+        var body2 = Body.Builder.of(anotherBody, VOID);
+        var block2 = body2.entryBlock();
+        block2.op(_return());
+        var lambdaOp = lambda(type(Runnable.class), body2);
+
+        // Op's grandparent body is not parent body of block1
+        Assert.assertThrows(IllegalStateException.class, () -> block1.op(lambdaOp));
+    }
+
+    @Test
+    public void testAppendAfterTerminatingOperation() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(_return());
+
+        // Append operation after terminating operation
+        Assert.assertThrows(IllegalStateException.class, () -> block.op(_return()));
+    }
+
+    @Test
+    public void testNoTerminatingOperation() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(constant(INT, 0));
+
+        // No terminating operation
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testUnreferencedBlocksRemoved() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        block.op(_return());
+
+        // Create empty blocks
+        block.block();
+        block.block();
+        block.block();
+
+        FuncOp f = func("f", body);
+        Assert.assertEquals(f.body().blocks().size(), 1);
+    }
+
+    @Test
+    public void testEmptyEntryBlock() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testNonEmptyEntryBlockNoTerminatingOp() {
+        var body = Body.Builder.of(null, VOID);
+        var block = body.entryBlock();
+        // No terminating op
+        block.op(constant(INT, 0));
+
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testEmptyBlockWithPredecessor() {
+        var body = Body.Builder.of(null, VOID);
+        var entryBlock = body.entryBlock();
+        // Create empty block
+        var block = entryBlock.block();
+        // Branch to empty block
+        entryBlock.op(branch(block.successor()));
+
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+
+    @Test
+    public void testNonEmptyBlockNoTerminatingOp() {
+        var body = Body.Builder.of(null, VOID);
+        var entryBlock = body.entryBlock();
+        // Create empty block
+        var block = entryBlock.block();
+        // Branch to empty block
+        entryBlock.op(branch(block.successor()));
+        // No terminating op
+        block.op(constant(INT, 0));
+
+        Assert.assertThrows(IllegalStateException.class, () -> func("f", body));
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestClosureOps.java b/test/jdk/java/lang/reflect/code/TestClosureOps.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestClosureOps.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestClosureOps
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.type.JavaType;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.add;
+import static java.lang.reflect.code.op.CoreOp.closure;
+import static java.lang.reflect.code.op.CoreOp.closureCall;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.op.CoreOp.func;
+import static java.lang.reflect.code.op.CoreOp.quoted;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.INT;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public class TestClosureOps {
+
+    static class Builder {
+        static final MethodRef ACCEPT_METHOD = MethodRef.method(type(TestClosureOps.Builder.class), "accept",
+                INT, CoreOp.QuotedOp.QUOTED_TYPE);
+
+        static int accept(Quoted c) {
+            Assert.assertEquals(1, c.capturedValues().size());
+            Assert.assertEquals(1, c.capturedValues().values().iterator().next());
+
+            int r = (int) Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) c.op(),
+                    c.capturedValues(), 42);
+            return r;
+        }
+    }
+
+    @Test
+    public void testQuotedWithCapture() {
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(block -> {
+                    Block.Parameter i = block.parameters().get(0);
+
+                    // functional type = (int)int
+                    // op descriptor = ()Quoted<ClosureOp>
+                    CoreOp.QuotedOp qop = quoted(block.parentBody(), qblock -> {
+                        return closure(qblock.parentBody(), functionType(INT, INT))
+                                .body(cblock -> {
+                                    Block.Parameter ci = cblock.parameters().get(0);
+
+                                    cblock.op(_return(
+                                            // capture i from function's body
+                                            cblock.op(add(i, ci))
+                                    ));
+                                });
+                    });
+                    Op.Result cquoted = block.op(qop);
+
+                    Op.Result or = block.op(CoreOp.invoke(TestClosureOps.Builder.ACCEPT_METHOD, cquoted));
+                    block.op(_return(or));
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testWithCapture() {
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(block -> {
+                    Block.Parameter i = block.parameters().get(0);
+
+                    // functional type = (int)int
+                    //   captures i
+                    CoreOp.ClosureOp closure = CoreOp.closure(block.parentBody(),
+                                    functionType(INT, INT))
+                            .body(cblock -> {
+                                Block.Parameter ci = cblock.parameters().get(0);
+
+                                cblock.op(_return(
+                                        cblock.op(add(i, ci))));
+                            });
+                    Op.Result c = block.op(closure);
+
+                    Op.Result fortyTwo = block.op(constant(INT, 42));
+                    Op.Result or = block.op(closureCall(c, fortyTwo));
+                    block.op(_return(or));
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testQuotableModel() {
+        Quoted quoted = () -> {};
+        Op qop = quoted.op();
+        Op top = qop.ancestorBody().parentOp().ancestorBody().parentOp();
+        Assert.assertTrue(top instanceof CoreOp.FuncOp);
+
+        CoreOp.FuncOp fop = (CoreOp.FuncOp) top;
+        Assert.assertEquals(JavaType.type(Quoted.class), fop.invokableType().returnType());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestConcat.java b/test/jdk/java/lang/reflect/code/TestConcat.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestConcat.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.runtime.CodeReflection;
+import java.util.*;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestConcat
+ */
+
+public class TestConcat {
+
+    static final String TESTSTR = "TESTING STRING";
+
+    @CodeReflection
+    static String f() {
+       int test = 1;
+       String res = "HI " + TESTSTR + test;
+       return res;
+    }
+
+
+    @CodeReflection
+    public static String byteConcat1(byte b, String s) {
+       return b + s;
+    }
+
+    @CodeReflection
+    public static String byteConcat2(String s, byte b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String shortConcat1(short b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String shortConcat2(String s, short b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String intConcat1(int b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String intConcat2(String s, int b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String longConcat1(long b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String longConcat2(String s, long b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String floatConcat1(float b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String floatConcat2(String s, float b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String doubleConcat1(double b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String doubleConcat2(String s, double b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String booleanConcat1(boolean b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String booleanConcat2(String s, boolean b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String charConcat1(char b, String s) {
+        return b + s;
+    }
+    @CodeReflection
+    public static String charConcat2(String s, char b) {
+        return s + b;
+    }
+    @CodeReflection
+    public static String objectConcat1(Object b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String objectConcat2(String s, Object b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String objectConcat3(TestObject b, String s) {
+        return b + s;
+    }
+
+    @CodeReflection
+    public static String objectConcat4(String s, TestObject b) {
+        return s + b;
+    }
+
+    @CodeReflection
+    public static String stringConcat(String b, String s) {
+        return b + s;
+    }
+
+
+    record TestMethodData(Class<?> first, Class<?> second, String third) {
+    }
+
+    static final Map<Class<?>, Object> valMap;
+    static {
+        valMap = new HashMap<>();
+        valMap.put(byte.class, (byte) 42);
+        valMap.put(short.class, (short) 42);
+        valMap.put(int.class, 42);
+        valMap.put(long.class, (long) 42);
+        valMap.put(float.class, 42f);
+        valMap.put(double.class, 42d);
+        valMap.put(char.class, 'z');
+        valMap.put(boolean.class, false);
+        valMap.put(Object.class, new Object() {
+                    @Override
+                    public String toString() {
+                        return "I'm a test string.";
+                    }
+                });
+        valMap.put(TestObject.class, new TestObject());
+        valMap.put(String.class, TESTSTR);
+    }
+    private static String testName(Class<?> n, Integer i){
+        return n.getSimpleName().toLowerCase() + "Concat" + i;
+    }
+    @DataProvider(name = "testData")
+    public static Object[][]  testData() {
+        Set<Class<?>> types = Set.of(byte.class, short.class, int.class, long.class, float.class,
+                double.class, char.class, boolean.class, Object.class);
+
+        //Types from types concatenated to strings left-to-right and right-to-left
+        Stream<TestMethodData> s1 = types.stream().map(t -> new TestMethodData(t, String.class, testName(t, 1)));
+        Stream<TestMethodData> s2 = types.stream().map(t -> new TestMethodData(String.class, t, testName(t, 2)));
+
+        //Custom Object and basic string concat tests
+        Stream<TestMethodData> s3 = Stream.of(new TestMethodData(TestObject.class, String.class, testName(Object.class, 3)),
+                                      new TestMethodData(String.class, TestObject.class, testName(Object.class, 4)),
+                                      new TestMethodData(String.class, String.class, "stringConcat"));
+
+        Object[] t = Stream.concat(Stream.concat(s1,s2),s3).toArray();
+
+        Object[][] args = new Object[t.length][];
+
+        for(int i = 0; i < args.length; i++) {
+            args[i] = new Object[]{ t[i] };
+        }
+
+        return args;
+    }
+
+    @Test(dataProvider = "testData")
+    public static void testRun(TestMethodData t) {
+        try {
+            Object[] args = new Object[] {valMap.get(t.first), valMap.get(t.second)};
+            Class<TestConcat> clazz = TestConcat.class;
+            Method method = clazz.getDeclaredMethod(t.third, t.first, t.second);
+            CoreOp.FuncOp f = method.getCodeModel().orElseThrow();
+            var res1 = Interpreter.invoke(MethodHandles.lookup(), f, args);
+            var res2 = method.invoke(null, args);
+
+            Assert.assertEquals(res1, res2);
+
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static final class TestObject {
+        TestObject(){}
+
+        @Override
+        public String toString() {
+            return "TestObject String";
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestConditionalExpression.java b/test/jdk/java/lang/reflect/code/TestConditionalExpression.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestConditionalExpression.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestConditionalExpression
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestConditionalExpression {
+    @CodeReflection
+    public static int simpleExpression(boolean b, int x, int y) {
+        return b ? x : y;
+    }
+
+    @Test
+    public void testSimpleExpression() {
+        CoreOp.FuncOp f = getFuncOp("simpleExpression");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf, true, 1, 2), simpleExpression(true, 1, 2));
+        Assert.assertEquals(Interpreter.invoke(lf, false, 1, 2), simpleExpression(false, 1, 2));
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestConditionalExpression.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestConditionalOp.java b/test/jdk/java/lang/reflect/code/TestConditionalOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestConditionalOp.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestConditionalOp
+ */
+
+public class TestConditionalOp {
+
+    @CodeReflection
+    static boolean f(boolean a, boolean b, boolean c, List<String> l) {
+        return F.a(a, l) || (F.b(b, l) && F.c(c, l));
+    }
+
+    static class F {
+        static boolean a(boolean a, List<String> l) {
+            l.add("a");
+            return a;
+        }
+
+        static boolean b(boolean b, List<String> l) {
+            l.add("b");
+            return b;
+        }
+
+        static boolean c(boolean c, List<String> l) {
+            l.add("c");
+            return c;
+        }
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestConditionalOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int i = 0; i < 8; i++) {
+            boolean a = (i & 1) != 0;
+            boolean b = (i & 2) != 0;
+            boolean c = (i & 4) != 0;
+            List<String> la = new ArrayList<>();
+            boolean ra = (boolean) Interpreter.invoke(MethodHandles.lookup(), lf, a, b, c, la);
+
+            List<String> le = new ArrayList<>();
+            boolean re = f(a, b, c, le);
+
+            Assert.assertEquals(ra, re);
+            Assert.assertEquals(la, le);
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestConstants.java b/test/jdk/java/lang/reflect/code/TestConstants.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestConstants.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestConstants
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestConstants {
+
+    @CodeReflection
+    public static boolean c_boolean() {
+        return true;
+    }
+
+    @CodeReflection
+    public static boolean c_boolean_f() {
+        return false;
+    }
+
+    @CodeReflection
+    public static byte c_byte() {
+        return 42;
+    }
+
+    @CodeReflection
+    public static byte c_byte_neg() {
+        return -42;
+    }
+
+    @CodeReflection
+    public static short c_short() {
+        return 42;
+    }
+
+    @CodeReflection
+    public static short c_short_neg() {
+        return -42;
+    }
+
+    @CodeReflection
+    public static char c_char() {
+        return 'A';
+    }
+
+    @CodeReflection
+    public static int c_int() {
+        return 42;
+    }
+
+    @CodeReflection
+    public static int c_int_neg() {
+        return -42;
+    }
+
+    @CodeReflection
+    public static long c_long() {
+        return 42L;
+    }
+
+    @CodeReflection
+    public static long c_long_neg() {
+        return -42L;
+    }
+
+    @CodeReflection
+    public static float c_float() {
+        return 1.0f;
+    }
+
+    @CodeReflection
+    public static float c_float_neg() {
+        return -1.0f;
+    }
+
+    @CodeReflection
+    public static double c_double() {
+        return 1.0;
+    }
+
+    @CodeReflection
+    public static double c_double_neg() {
+        return -1.0;
+    }
+
+    @CodeReflection
+    public static String c_String() {
+        String s = "42";
+        s = null;
+        return s;
+    }
+
+    @CodeReflection
+    public static Class<?> c_Class() {
+        return String.class;
+    }
+
+    @CodeReflection
+    public static Class<?> c_Class_primitive() {
+        return float.class;
+    }
+
+    @DataProvider
+    static Object[][] provider() {
+        return new Object[][] {
+                { boolean.class },
+                { byte.class },
+                { short.class },
+                { char.class },
+                { int.class },
+                { long.class },
+                { float.class },
+                { double.class },
+                { String.class },
+                { Class.class }
+        };
+    }
+
+    @Test(dataProvider = "provider")
+    public void testString(Class<?> c) throws Exception {
+        String name = "c_" + c.getSimpleName();
+        List<Method> ms = Stream.of(TestConstants.class.getDeclaredMethods())
+                .filter(m -> m.getName().startsWith(name))
+                .toList();
+
+        for (Method m : ms) {
+            CoreOp.FuncOp f = m.getCodeModel().get();
+
+            f.writeTo(System.out);
+
+            Assert.assertEquals(Interpreter.invoke(f), m.invoke(null));
+        }
+    }
+
+    @CodeReflection
+    public static String compareNull(String s) {
+        if (s == null) {
+            return "NULL";
+        } else {
+            return s;
+        }
+    }
+
+    @Test
+    public void testCompareNull() {
+        CoreOp.FuncOp f = getFuncOp("compareNull");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf, (Object) null), compareNull(null));
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestConstants.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestCopy.java b/test/jdk/java/lang/reflect/code/TestCopy.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestCopy.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestCopy
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExternalizableOp;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.function.IntUnaryOperator;
+import java.util.stream.Stream;
+
+public class TestCopy {
+
+    @CodeReflection
+    static int f(int i) {
+        IntUnaryOperator f = j -> i + j;
+        return f.applyAsInt(42);
+    }
+
+    @Test
+    public void testCopy() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        Op copy = f.copy();
+
+        Assert.assertEquals(f.toText(), copy.toText());
+    }
+
+    @Test
+    public void testCopyWithDefinition() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        ExternalizableOp.ExternalizedOp odef = ExternalizableOp.ExternalizedOp.externalizeOp(CopyContext.create(), f);
+        Op copy = CoreOp.FACTORY.constructOp(odef);
+
+        Assert.assertEquals(f.toText(), copy.toText());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestCopy.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestDominate.java b/test/jdk/java/lang/reflect/code/TestDominate.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestDominate.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.branch;
+import static java.lang.reflect.code.op.CoreOp.conditionalBranch;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.op.CoreOp.func;
+
+/*
+ * @test
+ * @run testng TestDominate
+ */
+
+public class TestDominate {
+
+    @Test
+    public void testIfElse() {
+        CoreOp.FuncOp f = func("f", FunctionType.VOID).body(entry -> {
+            Block.Builder ifBlock = entry.block();
+            Block.Builder elseBlock = entry.block();
+            Block.Builder end = entry.block();
+
+            Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
+            entry.op(conditionalBranch(p, ifBlock.successor(), elseBlock.successor()));
+
+            ifBlock.op(branch(end.successor()));
+
+            elseBlock.op(branch(end.successor()));
+
+            end.op(_return());
+        });
+
+        boolean[][] bvs = new boolean[][]{
+                {true, false, false, false},
+                {true, true, false, false},
+                {true, false, true, false},
+                {true, false, false, true}
+        };
+
+        test(f, bvs);
+    }
+
+    @Test
+    public void testForwardSuccessors() {
+        CoreOp.FuncOp f = func("f", FunctionType.VOID).body(entry -> {
+            Block.Builder b1 = entry.block();
+            Block.Builder b2 = entry.block();
+            Block.Builder b3 = entry.block();
+            Block.Builder b4 = entry.block();
+            Block.Builder b5 = entry.block();
+
+            Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
+            entry.op(conditionalBranch(p, b4.successor(), b2.successor()));
+
+            b4.op(conditionalBranch(p, b5.successor(), b3.successor()));
+
+            b2.op(conditionalBranch(p, b5.successor(), b1.successor()));
+
+            b5.op(_return());
+
+            b3.op(branch(b1.successor()));
+
+            b1.op(_return());
+        });
+
+        f.writeTo(System.out);
+        boolean[][] bvs = new boolean[][]{
+                {true, false, false, false, false, false},
+                {true, true, false, false, false, false},
+                {true, true, true, false, false, false},
+                {true, false, false, true, false, false},
+                {true, false, false, false, true, false},
+                {true, false, false, false, false, true},
+        };
+
+        test(f, bvs);
+    }
+
+    @Test
+    public void testBackbranch() {
+        CoreOp.FuncOp f = func("f", FunctionType.VOID).body(entry -> {
+            Block.Builder cond = entry.block();
+            Block.Builder body = entry.block();
+            Block.Builder update = entry.block();
+            Block.Builder end = entry.block();
+
+            Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
+            entry.op(branch(cond.successor()));
+
+            cond.op(conditionalBranch(p, body.successor(), end.successor()));
+
+            body.op(branch(update.successor()));
+
+            update.op(branch(cond.successor()));
+
+            end.op(_return());
+
+        });
+
+        boolean[][] bvs = new boolean[][]{
+                {true, false, false, false, false},
+                {true, true, false, false, false},
+                {true, true, true, false, false},
+                {true, true, true, true, false},
+                {true, true, false, false, true},
+        };
+        test(f, bvs);
+    }
+
+    static void test(CoreOp.FuncOp f, boolean[][] bvs) {
+        Block[] bs = f.body().blocks().toArray(Block[]::new);
+        for (int i = 0; i < bs.length; i++) {
+            for (int j = 0; j < bs.length; j++) {
+                Block x = bs[i];
+                Block y = bs[j];
+                Assert.assertEquals(y.isDominatedBy(x), bvs[j][i]);
+            }
+        }
+    }
+
+
+    @Test
+    public void testImmediateDominators() {
+        CoreOp.FuncOp f = func("f", FunctionType.VOID).body(entry -> {
+            Block.Builder b6 = entry.block();
+            Block.Builder b5 = entry.block();
+            Block.Builder b4 = entry.block();
+            Block.Builder b3 = entry.block();
+            Block.Builder b2 = entry.block();
+            Block.Builder b1 = entry.block();
+
+            Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
+            entry.op(branch(b6.successor()));
+
+            b6.op(conditionalBranch(p, b5.successor(), b4.successor()));
+
+            b5.op(branch(b1.successor()));
+
+            b4.op(conditionalBranch(p, b2.successor(), b3.successor()));
+
+            b1.op(branch(b2.successor()));
+
+            b2.op(conditionalBranch(p, b1.successor(), b3.successor()));
+
+            b3.op(branch(b2.successor()));
+        });
+        f.writeTo(System.out);
+        Map<Block, Block> idoms = f.body().immediateDominators();
+        System.out.println(idoms);
+
+        Block entry = f.body().entryBlock();
+        Block b6 = entry.successors().get(0).targetBlock();
+
+        for (Block b : f.body().blocks()) {
+            if (b == entry || b == b6) {
+                Assert.assertEquals(idoms.get(b), entry);
+            } else {
+                Assert.assertEquals(idoms.get(b), b6);
+            }
+        }
+    }
+
+    @Test
+    public void testCytronExample() {
+        CoreOp.FuncOp f = func("f", FunctionType.VOID).body(entry -> {
+            Block.Builder exit = entry.block();
+            Block.Builder b12 = entry.block();
+            Block.Builder b11 = entry.block();
+            Block.Builder b10 = entry.block();
+            Block.Builder b9 = entry.block();
+            Block.Builder b8 = entry.block();
+            Block.Builder b7 = entry.block();
+            Block.Builder b6 = entry.block();
+            Block.Builder b5 = entry.block();
+            Block.Builder b4 = entry.block();
+            Block.Builder b3 = entry.block();
+            Block.Builder b2 = entry.block();
+            Block.Builder b1 = entry.block();
+
+            Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
+
+            entry.op(conditionalBranch(p, exit.successor(), b1.successor()));
+
+            b1.op(branch(b2.successor()));
+
+            b2.op(conditionalBranch(p, b3.successor(), b7.successor()));
+
+            b3.op(conditionalBranch(p, b4.successor(), b5.successor()));
+
+            b4.op(branch(b6.successor()));
+
+            b5.op(branch(b6.successor()));
+
+            b6.op(branch(b8.successor()));
+
+            b7.op(branch(b8.successor()));
+
+            b8.op(branch(b9.successor()));
+
+            b9.op(conditionalBranch(p, b10.successor(), b11.successor()));
+
+            b10.op(branch(b11.successor()));
+
+            b11.op(conditionalBranch(p, b12.successor(), b9.successor()));
+
+            b12.op(conditionalBranch(p, exit.successor(), b2.successor()));
+
+            exit.op(_return());
+        });
+
+        f.writeTo(System.out);
+
+        Map<Block, Block> idoms = f.body().immediateDominators();
+        Node<String> domTree = buildDomTree(f.body().entryBlock(), idoms).transform(b -> Integer.toString(b.index()));
+        Node<String> domTreeExpected =
+                node("0",
+                        node("1",
+                                node("2",
+                                        node("7"),
+                                        node("8",
+                                                node("9",
+                                                        node("10"),
+                                                        node("11",
+                                                                node("12")))),
+                                        node("3",
+                                                node("4"), node("5"), node("6")))),
+                        node("13"));
+        Assert.assertEquals(domTree, domTreeExpected);
+
+
+        Map<String, Set<String>> df = f.body().dominanceFrontier().entrySet().stream()
+                .map(e -> Map.entry(Integer.toString(e.getKey().index()),
+                        e.getValue().stream().map(b -> Integer.toString(b.index())).collect(Collectors.toSet())))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        Map<String, Set<String>> dfExpected = Map.ofEntries(
+                Map.entry("1", Set.of("13")),
+                Map.entry("2", Set.of("13", "2")),
+                Map.entry("3", Set.of("8")),
+                Map.entry("4", Set.of("6")),
+                Map.entry("5", Set.of("6")),
+                Map.entry("6", Set.of("8")),
+                Map.entry("7", Set.of("8")),
+                Map.entry("8", Set.of("13", "2")),
+                Map.entry("9", Set.of("13", "2", "9")),
+                Map.entry("10", Set.of("11")),
+                Map.entry("11", Set.of("13", "2", "9")),
+                Map.entry("12", Set.of("13", "2"))
+        );
+        Assert.assertEquals(df, dfExpected);
+    }
+
+    static Node<Block> buildDomTree(Block entryBlock, Map<Block, Block> idoms) {
+        Map<Block, Node<Block>> m = new HashMap<>();
+        for (Map.Entry<Block, Block> e : idoms.entrySet()) {
+            Block id = e.getValue();
+            Block b = e.getKey();
+            if (b == entryBlock) {
+                continue;
+            }
+
+            Node<Block> parent = m.computeIfAbsent(id, _k -> new Node<>(_k, new HashSet<>()));
+            Node<Block> child = m.computeIfAbsent(b, _k -> new Node<>(_k, new HashSet<>()));
+            parent.children.add(child);
+        }
+        return m.get(entryBlock);
+    }
+
+    @SafeVarargs
+    static <T> Node<T> node(T t, Node<T>... children) {
+        return new Node<>(t, Set.of(children));
+    }
+
+    static <T> Node<T> node(T t, Set<Node<T>> children) {
+        return new Node<>(t, children);
+    }
+
+    record Node<T>(T t, Set<Node<T>> children) {
+        <U> Node<U> transform(Function<T, U> f) {
+            Set<Node<U>> mchildren = new HashSet<>();
+            for (Node<T> nc : children) {
+                mchildren.add(nc.transform(f));
+            }
+            return node(f.apply(t), mchildren);
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestEnhancedForOp.java b/test/jdk/java/lang/reflect/code/TestEnhancedForOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestEnhancedForOp.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestEnhancedForOp
+ */
+
+public class TestEnhancedForOp {
+
+    @CodeReflection
+    public static int f() {
+        int j = 0;
+        for (int i : List.of(1, 2, 3, 4)) {
+            j += i;
+        }
+        return j;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestEnhancedForOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), f());
+    }
+
+
+    @CodeReflection
+    public static int array(int[] a) {
+        int j = 0;
+        for (int i : a) {
+            j += i;
+        }
+        return j;
+    }
+
+    @Test
+    public void testArray() {
+        CoreOp.FuncOp f = getFuncOp("array");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        int[] ia = new int[] {1, 2, 3, 4};
+        Assert.assertEquals(Interpreter.invoke(lf, ia), array(ia));
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestExceptionRegionOps.java b/test/jdk/java/lang/reflect/code/TestExceptionRegionOps.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestExceptionRegionOps.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestExceptionRegionOps
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp._throw;
+import static java.lang.reflect.code.op.CoreOp.branch;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.op.CoreOp.exceptionRegionEnter;
+import static java.lang.reflect.code.op.CoreOp.exceptionRegionExit;
+import static java.lang.reflect.code.op.CoreOp.func;
+import static java.lang.reflect.code.type.FunctionType.*;
+import static java.lang.reflect.code.type.JavaType.*;
+import static java.lang.reflect.code.type.JavaType.VOID;
+
+public class TestExceptionRegionOps {
+
+    public void testF(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void test() {
+        CoreOp.FuncOp f = func("f", functionType(VOID, type(IntConsumer.class)))
+                .body(fbody -> {
+                    var fblock = fbody.entryBlock();
+                    var catchER1ISE = fblock.block(type(IllegalStateException.class));
+                    var catchER1IAE = fblock.block(type(IllegalArgumentException.class));
+                    var enterER1 = fblock.block();
+                    var end = fblock.block();
+
+                    //
+                    var c = fblock.parameters().get(0);
+                    var er1 = fblock.op(exceptionRegionEnter(
+                            enterER1.successor(),
+                            catchER1ISE.successor(), catchER1IAE.successor()));
+
+                    // Start of exception region
+                    enterER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 0))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of exception region
+                        b.op(exceptionRegionExit(er1, end.successor()));
+                    });
+
+                    // First catch block for exception region
+                    catchER1ISE.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 1))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    // Second catch for exception region
+                    catchER1IAE.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    //
+                    end.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 3))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(_return());
+                    });
+                });
+
+        f.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
+                this::testF);
+
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new NullPointerException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+
+    public void testCatchThrowableF(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } catch (Throwable e) {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchThrowable() {
+        CoreOp.FuncOp f = func("f", functionType(VOID, type(IntConsumer.class)))
+                .body(fbody -> {
+                    var fblock = fbody.entryBlock();
+                    var catchER1ISE = fblock.block(type(IllegalStateException.class));
+                    var catchER1T = fblock.block(type(Throwable.class));
+                    var enterER1 = fblock.block();
+                    var end = fblock.block();
+
+                    //
+                    var c = fblock.parameters().get(0);
+                    var er1 = fblock.op(exceptionRegionEnter(
+                            enterER1.successor(),
+                            catchER1ISE.successor(), catchER1T.successor()));
+
+                    // Start of exception region
+                    enterER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 0))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of exception region
+                        b.op(exceptionRegionExit(er1, end.successor()));
+                    });
+
+                    // First catch block for exception region
+                    catchER1ISE.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 1))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    // Second catch for exception region
+                    catchER1T.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    //
+                    end.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 3))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(_return());
+                    });
+                });
+
+        f.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
+                this::testCatchThrowableF);
+
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+
+    public void testNestedF(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+            try {
+                c.accept(1);
+                c.accept(-1);
+            } catch (IllegalStateException e) {
+                c.accept(2);
+                c.accept(-1);
+            }
+            c.accept(3);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            c.accept(4);
+            c.accept(-1);
+        }
+        c.accept(5);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testNested() {
+        CoreOp.FuncOp f = func("f", functionType(VOID, type(IntConsumer.class)))
+                .body(fbody -> {
+                    var fblock = fbody.entryBlock();
+                    var catchER1 = fblock.block(type(IllegalArgumentException.class));
+                    var catchER2 = fblock.block(type(IllegalStateException.class));
+                    var enterER1 = fblock.block();
+                    var enterER2 = fblock.block();
+                    var b3 = fblock.block();
+                    var end = fblock.block();
+
+                    //
+                    var c = fblock.parameters().get(0);
+                    var er1 = fblock.op(exceptionRegionEnter(
+                            enterER1.successor(),
+                            catchER1.successor()));
+
+                    // Start of first exception region
+                    enterER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 0))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                    });
+                    var er2 = enterER1.op(exceptionRegionEnter(
+                            enterER2.successor(),
+                            catchER2.successor()));
+
+                    // Start of second exception region
+                    enterER2.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 1))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of second exception region
+                        b.op(exceptionRegionExit(er2, b3.successor()));
+                    });
+
+                    // Catch block for second exception region
+                    catchER2.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(b3.successor()));
+                    });
+
+                    b3.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 3))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of first exception region
+                        b.op(exceptionRegionExit(er1, end.successor()));
+                    });
+
+                    // Catch block for first exception region
+                    catchER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 4))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    //
+                    end.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 5))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(_return());
+                    });
+                });
+
+        f.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
+                this::testNestedF);
+
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+            if (i == 4) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 5) throw new RuntimeException();
+        });
+    }
+
+    public void testCatchFinallyF(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchFinally() {
+        CoreOp.FuncOp f = func("f", functionType(VOID, JavaType.type(IntConsumer.class)))
+                .body(fbody -> {
+                    var fblock = fbody.entryBlock();
+                    var catchRE = fblock.block(type(IllegalStateException.class));
+                    var catchAll = fblock.block(type(Throwable.class));
+                    var enterER1 = fblock.block();
+                    var exitER1 = fblock.block();
+                    var enterER2 = fblock.block();
+                    var exitER2 = fblock.block();
+                    var end = fblock.block();
+
+                    //
+                    var c = fblock.parameters().get(0);
+                    var er1 = fblock.op(exceptionRegionEnter(
+                            enterER1.successor(),
+                            catchRE.successor(), catchAll.successor()));
+
+                    // Start of exception region
+                    enterER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 0))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of exception region
+                        b.op(exceptionRegionExit(er1, exitER1.successor()));
+                    });
+                    // Inline finally
+                    exitER1.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    // Catch block for RuntimeException
+                    var er2 = catchRE.op(exceptionRegionEnter(
+                            enterER2.successor(),
+                            catchAll.successor()));
+                    // Start of exception region
+                    enterER2.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 1))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        // End of exception region
+                        b.op(exceptionRegionExit(er2, exitER2.successor()));
+                    });
+                    // Inline finally
+                    exitER2.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(branch(end.successor()));
+                    });
+
+                    // Catch all block for finally
+                    catchAll.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 2))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(_throw(catchAll.parameters().get(0)));
+                    });
+
+                    //
+                    end.ops(b -> {
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, 3))));
+                        b.op(CoreOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b.op(constant(INT, -1))));
+                        b.op(_return());
+                    });
+                });
+
+        f.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
+                this::testCatchFinallyF
+                );
+
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+    }
+
+    static final MethodRef INT_CONSUMER_ACCEPT_METHOD = MethodRef.method(type(IntConsumer.class), "accept",
+            VOID, INT);
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestForOp.java b/test/jdk/java/lang/reflect/code/TestForOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestForOp.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestForOp
+ */
+
+public class TestForOp {
+
+    @CodeReflection
+    public static int f() {
+        int j = 0;
+        for (int i = 0; i < 10; i++) {
+            j += i;
+        }
+        return j;
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), f());
+    }
+
+    @CodeReflection
+    public static int f2() {
+        int k = 0;
+        for (int i = 0, j = 0; i < 10; i++, j++) {
+            k += i;
+            k += j;
+        }
+        return k;
+    }
+
+    @Test
+    public void testf2() {
+        CoreOp.FuncOp f = getFuncOp("f2");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), f2());
+    }
+
+    @CodeReflection
+    public static int f3() {
+        int k = 0;
+        int i = 0;
+        int j = 0;
+        for (i = 0, j = 0; i < 10; i++, j++) {
+            k += i;
+            k += j;
+        }
+        return k;
+    }
+
+    @Test
+    public void testf3() {
+        CoreOp.FuncOp f = getFuncOp("f3");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), f3());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestForOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestIfOp.java b/test/jdk/java/lang/reflect/code/TestIfOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestIfOp.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestIfOp
+ */
+
+public class TestIfOp {
+
+    @CodeReflection
+    public static String f(int i) {
+        String s;
+        if (i <= 1) {
+            s = "<=ONE";
+        } else if (i == 2) {
+            s = "TWO";
+        } else if (i == 3) {
+            s = "THREE";
+        } else if (i == 4) {
+            s = "FOUR";
+        } else {
+            s = ">=FIVE";
+        }
+        return s;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestIfOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    @Test
+    public void testf() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int i = 0; i < 6; i++) {
+            Assert.assertEquals(Interpreter.invoke(lf, i), f(i));
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestInline.java b/test/jdk/java/lang/reflect/code/TestInline.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestInline.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.type.JavaType;
+import java.util.List;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.INT;
+
+/*
+ * @test
+ * @run testng TestInline
+ */
+
+public class TestInline {
+
+    @Test
+    public void testInline() {
+        Quoted q = (int a, int b) -> a + b;
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(fblock -> {
+                    Block.Parameter i = fblock.parameters().get(0);
+
+                    Op.Result fortyTwo = fblock.op(constant(INT, 42));
+
+                    var cb = fblock.inline(cop, List.of(i, fortyTwo), Block.Builder.INLINE_RETURN);
+                    Assert.assertEquals(fblock, cb);
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testInlineVar() {
+        Quoted q = (int a, int b) -> a + b;
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(fblock -> {
+                    Block.Parameter i = fblock.parameters().get(0);
+
+                    Op.Result fortyTwo = fblock.op(constant(INT, 42));
+
+                    Op.Result v = fblock.op(var(fblock.op(constant(INT, 0))));
+
+                    var cb = fblock.inline(cop, List.of(i, fortyTwo), (b, value) -> {
+                        b.op(varStore(v, value));
+                    });
+                    Assert.assertEquals(fblock, cb);
+
+                    fblock.op(_return(fblock.op(varLoad(v))));
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+
+    @Test
+    public void testInlineLowerMultipleReturn() {
+        Quoted q = (int a, int b) ->  {
+            if (a < 10) {
+                return a + b;
+            }
+            return a - b;
+        };
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+        cop.writeTo(System.out);
+        CoreOp.ClosureOp lcop = cop.transform(CopyContext.create(), OpTransformer.LOWERING_TRANSFORMER);
+        lcop.writeTo(System.out);
+
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(fblock -> {
+                    Block.Parameter i = fblock.parameters().get(0);
+
+                    Op.Result fortyTwo = fblock.op(constant(INT, 42));
+
+                    var cb = fblock.inline(lcop, List.of(i, fortyTwo), Block.Builder.INLINE_RETURN);
+                    Assert.assertNotEquals(fblock, cb);
+                });
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testInlineLowerMultipleReturnVar() {
+        Quoted q = (int a, int b) ->  {
+            if (a < 10) {
+                return a + b;
+            }
+            return a - b;
+        };
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+        cop.writeTo(System.out);
+        CoreOp.ClosureOp lcop = cop.transform(CopyContext.create(), OpTransformer.LOWERING_TRANSFORMER);
+        lcop.writeTo(System.out);
+
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(fblock -> {
+                    Block.Parameter i = fblock.parameters().get(0);
+
+                    Op.Result fortyTwo = fblock.op(constant(INT, 42));
+
+                    Op.Result v = fblock.op(var(fblock.op(constant(INT, 0))));
+
+                    var cb = fblock.inline(lcop, List.of(i, fortyTwo), (b, value) -> {
+                        b.op(varStore(v, value));
+                    });
+                    Assert.assertNotEquals(fblock, cb);
+
+                    cb.op(_return(cb.op(varLoad(v))));
+                });
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testInlineMultipleReturnLower() {
+        Quoted q = (int a, int b) ->  {
+            if (a < 10) {
+                return a + b;
+            }
+            return a - b;
+        };
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+        cop.writeTo(System.out);
+
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(fblock -> {
+                    Block.Parameter i = fblock.parameters().get(0);
+
+                    Op.Result fortyTwo = fblock.op(constant(INT, 42));
+
+                    var cb = fblock.inline(cop, List.of(i, fortyTwo), Block.Builder.INLINE_RETURN);
+                    Assert.assertEquals(fblock, cb);
+                });
+        f.writeTo(System.out);
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    @Test
+    public void testInlineVoid() {
+        Quoted q = (int[] a) -> {
+            a[0] = 42;
+            return;
+        };
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(JavaType.VOID, JavaType.type(int[].class)))
+                .body(fblock -> {
+                    Block.Parameter a = fblock.parameters().get(0);
+
+                    var cb = fblock.inline(cop, List.of(a), Block.Builder.INLINE_RETURN);
+                    Assert.assertEquals(fblock, cb);
+                });
+
+        f.writeTo(System.out);
+
+        int[] a = new int[1];
+        Interpreter.invoke(MethodHandles.lookup(), f, a);
+        Assert.assertEquals(a[0], 42);
+    }
+
+}
diff a/test/jdk/java/lang/reflect/code/TestLambdaOps.java b/test/jdk/java/lang/reflect/code/TestLambdaOps.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestLambdaOps.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestLambdaOps
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.CoreOp.FuncOp;
+import java.lang.reflect.code.op.CoreOp.LambdaOp;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntSupplier;
+import java.util.function.IntUnaryOperator;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.INT;
+import static java.lang.reflect.code.type.JavaType.type;
+
+@Test
+public class TestLambdaOps {
+    static class Builder {
+        static final MethodRef ACCEPT_METHOD = MethodRef.method(type(Builder.class), "accept",
+                INT, CoreOp.QuotedOp.QUOTED_TYPE);
+
+        static int accept(Quoted l) {
+            Assert.assertEquals(1, l.capturedValues().size());
+            Assert.assertEquals(1, l.capturedValues().values().iterator().next());
+
+            int r = (int) Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) l.op(),
+                    l.capturedValues(), 42);
+            return r;
+        }
+    }
+
+    @Test
+    public void testQuotedWithCapture() {
+        // functional type = (int)int
+        FuncOp f = func("f", functionType(INT, INT))
+                .body(block -> {
+                    Block.Parameter i = block.parameters().get(0);
+
+                    // functional type = (int)int
+                    // op type = ()Quoted<LambdaOp>
+                    QuotedOp qop = quoted(block.parentBody(), qblock -> {
+                        return lambda(qblock.parentBody(),
+                                functionType(INT, INT), type(IntUnaryOperator.class))
+                                .body(lblock -> {
+                                    Block.Parameter li = lblock.parameters().get(0);
+
+                                    lblock.op(_return(
+                                            // capture i from function's body
+                                            lblock.op(add(i, li))
+                                    ));
+                                });
+                    });
+                    Op.Result lquoted = block.op(qop);
+
+                    Op.Result or = block.op(invoke(Builder.ACCEPT_METHOD, lquoted));
+                    block.op(_return(or));
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    static final MethodRef INT_UNARY_OPERATOR_METHOD = MethodRef.method(
+            IntUnaryOperator.class, "applyAsInt",
+            int.class, int.class);
+
+    @Test
+    public void testWithCapture() {
+        // functional type = (int)int
+        FuncOp f = func("f", functionType(INT, INT))
+                .body(block -> {
+                    Block.Parameter i = block.parameters().get(0);
+
+                    // functional type = (int)int
+                    // op type = ()IntUnaryOperator
+                    //   captures i
+                    LambdaOp lambda = lambda(block.parentBody(),
+                            functionType(INT, INT), type(IntUnaryOperator.class))
+                            .body(lblock -> {
+                                Block.Parameter li = lblock.parameters().get(0);
+
+                                lblock.op(_return(
+                                        lblock.op(add(i, li))));
+                            });
+                    Op.Result fi = block.op(lambda);
+
+                    Op.Result fortyTwo = block.op(constant(INT, 42));
+                    Op.Result or = block.op(invoke(INT_UNARY_OPERATOR_METHOD, fi, fortyTwo));
+                    block.op(_return(or));
+                });
+
+        f.writeTo(System.out);
+
+        int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
+        Assert.assertEquals(ir, 43);
+    }
+
+    static int f(int i) {
+        IntUnaryOperator fi = li -> {
+            return i + li;
+        };
+
+        int fortyTwo = 42;
+        int or = fi.applyAsInt(fortyTwo);
+        return or;
+    }
+
+    @Test
+    public void testQuotableModel() {
+        Quotable quotable = (Runnable & Quotable) () -> {};
+        Op qop = quotable.quoted().op();
+        Op top = qop.ancestorBody().parentOp().ancestorBody().parentOp();
+        Assert.assertTrue(top instanceof CoreOp.FuncOp);
+
+        CoreOp.FuncOp fop = (CoreOp.FuncOp) top;
+        Assert.assertEquals(type(Quoted.class), fop.invokableType().returnType());
+    }
+
+    @FunctionalInterface
+    public interface QuotableIntSupplier extends IntSupplier, Quotable {
+    }
+
+    @CodeReflection
+    static QuotableIntSupplier quote(int i) {
+        QuotableIntSupplier s = () -> i;
+        return s;
+    }
+
+    @Test
+    public void testQuote() {
+        FuncOp g = getFuncOp("quote");
+        g.writeTo(System.out);
+
+        {
+            QuotableIntSupplier op = (QuotableIntSupplier) Interpreter.invoke(MethodHandles.lookup(), g, 42);
+            Assert.assertEquals(op.getAsInt(), 42);
+
+            Quoted q = op.quoted();
+            q.op().writeTo(System.out);
+            Assert.assertEquals(q.capturedValues().size(), 1);
+            Assert.assertEquals(((Var<?>)q.capturedValues().values().iterator().next()).value(), 42);
+
+            int r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), q.capturedValues(), List.of());
+            Assert.assertEquals(r, 42);
+
+            Map<Value, Object> cvs = Map.of(
+                    q.capturedValues().keySet().iterator().next(),
+                    CoreOp.Var.of(0)
+            );
+            r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), cvs, List.of());
+            Assert.assertEquals(r, 0);
+        }
+
+        {
+            QuotableIntSupplier op = quote(42);
+            Assert.assertEquals(op.getAsInt(), 42);
+
+            Quoted q = op.quoted();
+            q.op().writeTo(System.out);
+            System.out.print(q.capturedValues().values());
+            Assert.assertEquals(q.capturedValues().size(), 1);
+            Assert.assertEquals(((Var<?>)q.capturedValues().values().iterator().next()).value(), 42);
+
+            int r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), q.capturedValues(), List.of());
+            Assert.assertEquals(r, 42);
+
+            Map<Value, Object> cvs = Map.of(
+                    q.capturedValues().keySet().iterator().next(),
+                    CoreOp.Var.of(0)
+            );
+            r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), cvs, List.of());
+            Assert.assertEquals(r, 0);
+        }
+    }
+
+
+    interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable {}
+
+    interface QuotableFunction<T, R> extends Function<T, R>, Quotable {}
+
+    interface QuotableBiFunction<T, U, R> extends BiFunction<T, U, R>, Quotable {}
+
+    @DataProvider
+    Iterator<Quotable> methodRefLambdas() {
+        return List.of(
+                (QuotableIntUnaryOperator) TestLambdaOps::m1,
+                (QuotableIntUnaryOperator) TestLambdaOps::m2,
+                (QuotableFunction<Integer, Integer>) TestLambdaOps::m1,
+                (QuotableFunction<Integer, Integer>) TestLambdaOps::m2,
+                (QuotableIntUnaryOperator) this::m3,
+                (QuotableBiFunction<TestLambdaOps, Integer, Integer>) TestLambdaOps::m4
+        ).iterator();
+    }
+
+    @Test(dataProvider = "methodRefLambdas")
+    public void testIsMethodReference(Quotable q) {
+        Quoted quoted = q.quoted();
+        CoreOp.LambdaOp lop = (CoreOp.LambdaOp) quoted.op();
+        Assert.assertTrue(lop.methodReference().isPresent());
+    }
+
+    static int m1(int i) {
+        return i;
+    }
+
+    static Integer m2(Integer i) {
+        return i;
+    }
+
+    int m3(int i) {
+        return i;
+    }
+
+    static int m4(TestLambdaOps tl, int i) {
+        return i;
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestLambdaOps.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestLinqUsingQuoted.java b/test/jdk/java/lang/reflect/code/TestLinqUsingQuoted.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestLinqUsingQuoted.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.TypeElement;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.type.MethodRef.method;
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+
+/*
+ * @test
+ * @run testng TestLinqUsingQuoted
+ */
+
+public class TestLinqUsingQuoted {
+
+    // Query interfaces
+
+    public interface Queryable {
+        TypeElement elementType();
+
+        // Queryable<T> -> Queryable<U>
+        FuncOp expression();
+
+        QueryProvider provider();
+
+        // T -> boolean
+        // Predicate<T>
+        default Queryable where(Quoted f) {
+            // @@@@ validate
+            ClosureOp c = (ClosureOp) f.op();
+            return insertQuery(elementType(), "where", c);
+        }
+
+        // T -> R
+        // Function<T, R>
+        default Queryable select(Quoted f) {
+            // @@@@ validate
+            ClosureOp c = (ClosureOp) f.op();
+            return insertQuery(c.invokableType().returnType(), "select", c);
+        }
+
+        private Queryable insertQuery(TypeElement et, String name, ClosureOp c) {
+            QueryProvider qp = provider();
+
+            FuncOp currentQueryExpression = expression();
+            FuncOp nextQueryExpression = currentQueryExpression.transform((block, op) -> {
+                if (op instanceof ReturnOp rop && rop.ancestorBody() == currentQueryExpression.body()) {
+                    Value query = block.context().getValue(rop.returnValue());
+
+                    Op.Result quotedLambda = block.op(quoted(block.parentBody(), qblock -> c));
+
+                    MethodRef md = method(qp.queryableType(), name,
+                            functionType(qp.queryableType(), QuotedOp.QUOTED_TYPE));
+                    Op.Result queryable = block.op(invoke(md, query, quotedLambda));
+
+                    block.op(_return(queryable));
+                } else {
+                    block.op(op);
+                }
+                return block;
+            });
+
+            return qp.createQuery(et, nextQueryExpression);
+        }
+
+        // Iterate
+        // Queryable -> Stream
+        default QueryResult elements() {
+            TypeElement resultType = JavaType.parameterized(JavaType.type(Stream.class), (JavaType) elementType());
+            return insertQueryResult("elements", resultType);
+        }
+
+        // Count
+        // Queryable -> Long
+        default QueryResult count() {
+            return insertQueryResult("count", JavaType.LONG);
+        }
+
+        private QueryResult insertQueryResult(String name, TypeElement resultType) {
+            QueryProvider qp = provider();
+
+            // Copy function expression, replacing return type
+            FuncOp currentQueryExpression = expression();
+            FuncOp nextQueryExpression = func("queryresult",
+                    functionType(qp.queryResultType(), currentQueryExpression.invokableType().parameterTypes()))
+                    .body(b -> b.inline(currentQueryExpression, b.parameters(), (block, query) -> {
+                        MethodRef md = method(qp.queryableType(), name, functionType(qp.queryResultType()));
+                        Op.Result queryResult = block.op(invoke(md, query));
+
+                        block.op(_return(queryResult));
+                    }));
+            return qp.createQueryResult(resultType, nextQueryExpression);
+        }
+    }
+
+    public interface QueryResult {
+        TypeElement resultType();
+
+        // Queryable -> QueryResult
+        FuncOp expression();
+
+        Object execute();
+    }
+
+    public interface QueryProvider {
+        TypeElement queryableType();
+
+        TypeElement queryResultType();
+
+        Queryable createQuery(TypeElement elementType, FuncOp expression);
+
+        QueryResult createQueryResult(TypeElement resultType, FuncOp expression);
+
+        Queryable newQuery(TypeElement elementType);
+    }
+
+
+    // Query implementation
+
+    public static final class TestQueryable implements Queryable {
+        final TypeElement elementType;
+        final TestQueryProvider provider;
+        final FuncOp expression;
+
+        TestQueryable(TypeElement elementType, TestQueryProvider provider) {
+            this.elementType = elementType;
+            this.provider = provider;
+
+            // Initial expression is an identity function
+            var funType = functionType(provider().queryableType(), provider().queryableType());
+            this.expression = func("query", funType)
+                    .body(b -> b.op(_return(b.parameters().get(0))));
+        }
+
+        TestQueryable(TypeElement elementType, TestQueryProvider provider, FuncOp expression) {
+            this.elementType = elementType;
+            this.provider = provider;
+            this.expression = expression;
+        }
+
+        @Override
+        public TypeElement elementType() {
+            return elementType;
+        }
+
+        @Override
+        public FuncOp expression() {
+            return expression;
+        }
+
+        @Override
+        public QueryProvider provider() {
+            return provider;
+        }
+    }
+
+    public record TestQueryResult(TypeElement resultType, FuncOp expression) implements QueryResult {
+        @Override
+        public Object execute() {
+            // @@@ Compile/translate the expression and execute it
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static final class TestQueryProvider implements QueryProvider {
+        final TypeElement queryableType;
+        final TypeElement queryResultType;
+
+        TestQueryProvider() {
+            this.queryableType = JavaType.type(Queryable.class);
+            this.queryResultType = JavaType.type(QueryResult.class);
+        }
+
+        @Override
+        public TypeElement queryableType() {
+            return queryableType;
+        }
+
+        @Override
+        public TypeElement queryResultType() {
+            return queryResultType;
+        }
+
+        @Override
+        public TestQueryable createQuery(TypeElement elementType, FuncOp expression) {
+            return new TestQueryable(elementType, this, expression);
+        }
+
+        @Override
+        public QueryResult createQueryResult(TypeElement resultType, FuncOp expression) {
+            return new TestQueryResult(resultType, expression);
+        }
+
+        @Override
+        public Queryable newQuery(TypeElement elementType) {
+            return new TestQueryable(elementType, this);
+        }
+    }
+
+
+    static class Customer {
+        // 1st column
+        String contactName;
+        // 2nd column
+        String phone;
+        // 3rd column
+        String city;
+    }
+
+    @Test
+    public void testSimpleQuery() {
+        QueryProvider qp = new TestQueryProvider();
+
+        QueryResult qr = qp.newQuery(JavaType.type(Customer.class))
+                // c -> c.city.equals("London")
+                .where((Customer c) -> c.city.equals("London"))
+                // c -> c.contactName
+                .select((Customer c) -> c.contactName).elements();
+
+        qr.expression().writeTo(System.out);
+
+        QueryResult qr2 = (QueryResult) Interpreter.invoke(MethodHandles.lookup(),
+                qr.expression(), qp.newQuery(JavaType.type(Customer.class)));
+
+        qr2.expression().writeTo(System.out);
+
+        Assert.assertEquals(qr.expression().toText(), qr2.expression().toText());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestLiveness.java b/test/jdk/java/lang/reflect/code/TestLiveness.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestLiveness.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestLiveness
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.CodeElement;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.analysis.Liveness;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.parser.OpParser;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+public class TestLiveness {
+
+    static final String F = """
+            func @"f" (%0 : int, %1 : int)int -> {
+                %2 : int = add %0 %1;
+                return %2;
+            };
+            """;
+
+    @Test
+    public void testF() {
+        Op op = OpParser.fromString(CoreOp.FACTORY, F).getFirst();
+
+        var actual = liveness(op);
+        var expected = Map.of(
+                0, List.of(Set.of(), Set.of()));
+        Assert.assertEquals(actual, expected);
+    }
+
+    static final String IF_ELSE = """
+            func @"ifelse" (%0 : int, %1 : int, %2 : int)int -> {
+                %3 : int = constant @"10";
+                %4 : boolean = lt %2 %3;
+                cbranch %4 ^block_0 ^block_1;
+
+              ^block_0:
+                %5 : int = constant @"1";
+                %6 : int = add %0 %5;
+                branch ^block_2(%6, %1);
+
+              ^block_1:
+                %7 : int = constant @"2";
+                %8 : int = add %1 %7;
+                branch ^block_2(%0, %8);
+
+              ^block_2(%9 : int, %10 : int):
+                %11 : int = add %9 %10;
+                return %11;
+            };
+            """;
+
+    @Test
+    public void testIfElse() {
+        Op op = OpParser.fromString(CoreOp.FACTORY, IF_ELSE).getFirst();
+
+        var actual = liveness(op);
+        var expected = Map.of(
+                0, List.of(Set.of(), Set.of(0, 1)),
+                1, List.of(Set.of(0, 1), Set.of()),
+                2, List.of(Set.of(0, 1), Set.of()),
+                3, List.of(Set.of(), Set.of())
+        );
+        Assert.assertEquals(actual, expected);
+    }
+
+    static final String LOOP = """
+            func @"loop" (%0 : int)int -> {
+                %1 : int = constant @"0";
+                %2 : int = constant @"0";
+                branch ^block_0(%1, %2);
+
+              ^block_0(%3 : int, %4 : int):
+                %5 : boolean = lt %4 %0;
+                cbranch %5 ^block_1 ^block_2;
+
+              ^block_1:
+                %6 : int = add %3 %4;
+                branch ^block_3;
+
+              ^block_3:
+                %7 : int = constant @"1";
+                %8 : int = add %4 %7;
+                branch ^block_0(%6, %8);
+
+              ^block_2:
+                return %3;
+            };
+            """;
+
+    @Test
+    public void testLoop() {
+        Op op = OpParser.fromString(CoreOp.FACTORY, LOOP).getFirst();
+
+        var actual = liveness(op);
+        var expected = Map.of(
+                0, List.of(Set.of(), Set.of(0)),
+                1, List.of(Set.of(0), Set.of(0, 3, 4)),
+                2, List.of(Set.of(0, 3, 4), Set.of(0, 4, 6)),
+                3, List.of(Set.of(3), Set.of()),
+                4, List.of(Set.of(0, 4, 6), Set.of(0))
+        );
+        Assert.assertEquals(actual, expected);
+    }
+
+    static final String IF_ELSE_NESTED = """
+            func @"ifelseNested" (%0 : int, %1 : int, %2 : int, %3 : int, %4 : int)int -> {
+                %5 : int = constant @"20";
+                %6 : boolean = lt %4 %5;
+                cbranch %6 ^block_0 ^block_1;
+
+              ^block_0:
+                %7 : int = constant @"10";
+                %8 : boolean = lt %4 %7;
+                cbranch %8 ^block_2 ^block_3;
+
+              ^block_2:
+                %9 : int = constant @"1";
+                %10 : int = add %0 %9;
+                branch ^block_4(%10, %1);
+
+              ^block_3:
+                %11 : int = constant @"2";
+                %12 : int = add %1 %11;
+                branch ^block_4(%0, %12);
+
+              ^block_4(%13 : int, %14 : int):
+                %15 : int = constant @"3";
+                %16 : int = add %2 %15;
+                branch ^block_5(%13, %14, %16, %3);
+
+              ^block_1:
+                %17 : int = constant @"20";
+                %18 : boolean = gt %4 %17;
+                cbranch %18 ^block_6 ^block_7;
+
+              ^block_6:
+                %19 : int = constant @"4";
+                %20 : int = add %0 %19;
+                branch ^block_8(%20, %1);
+
+              ^block_7:
+                %21 : int = constant @"5";
+                %22 : int = add %1 %21;
+                branch ^block_8(%0, %22);
+
+              ^block_8(%23 : int, %24 : int):
+                %25 : int = constant @"6";
+                %26 : int = add %3 %25;
+                branch ^block_5(%23, %24, %2, %26);
+
+              ^block_5(%27 : int, %28 : int, %29 : int, %30 : int):
+                %31 : int = add %27 %28;
+                %32 : int = add %31 %29;
+                %33 : int = add %32 %30;
+                return %33;
+            };
+            """;
+
+    @Test
+    public void testIfElseNested() {
+        Op op = OpParser.fromString(CoreOp.FACTORY, IF_ELSE_NESTED).getFirst();
+
+        var actual = liveness(op);
+        var expected = Map.of(
+                0, List.of(Set.of(), Set.of(0, 1, 2, 3, 4)),
+                1, List.of(Set.of(0, 1, 2, 3, 4), Set.of(0, 1, 2, 3)),
+                2, List.of(Set.of(0, 1, 2, 3, 4), Set.of(0, 1, 2, 3)),
+                3, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
+                4, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
+                5, List.of(Set.of(2, 3), Set.of()),
+                6, List.of(Set.of(), Set.of()),
+                7, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
+                8, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
+                9, List.of(Set.of(2, 3), Set.of())
+        );
+        Assert.assertEquals(actual, expected);
+    }
+
+    static final String LOOP_NESTED = """
+            func @"loopNested" (%0 : int)int -> {
+                %1 : int = constant @"0";
+                %2 : int = constant @"0";
+                branch ^block_0(%1, %2);
+
+              ^block_0(%3 : int, %4 : int):
+                %5 : boolean = lt %4 %0;
+                cbranch %5 ^block_1 ^block_2;
+
+              ^block_1:
+                %6 : int = constant @"0";
+                branch ^block_3(%3, %6);
+
+              ^block_3(%7 : int, %8 : int):
+                %9 : boolean = lt %8 %0;
+                cbranch %9 ^block_4 ^block_5;
+
+              ^block_4:
+                %10 : int = add %7 %4;
+                %11 : int = add %10 %8;
+                branch ^block_6;
+
+              ^block_6:
+                %12 : int = constant @"1";
+                %13 : int = add %8 %12;
+                branch ^block_3(%11, %13);
+
+              ^block_5:
+                branch ^block_7;
+
+              ^block_7:
+                %14 : int = constant @"1";
+                %15 : int = add %4 %14;
+                branch ^block_0(%7, %15);
+
+              ^block_2:
+                return %3;
+            };
+            """;
+
+    @Test
+    public void testLoopNested() {
+        Op op = OpParser.fromString(CoreOp.FACTORY, LOOP_NESTED).getFirst();
+
+        var actual = liveness(op);
+        var expected = Map.of(
+                0, List.of(Set.of(), Set.of(0)),
+                1, List.of(Set.of(0), Set.of(0, 3, 4)),
+                2, List.of(Set.of(0, 3, 4), Set.of(0, 4)),
+                3, List.of(Set.of(3), Set.of()),
+                4, List.of(Set.of(0, 4), Set.of(0, 4, 7, 8)),
+                5, List.of(Set.of(0, 4, 7, 8), Set.of(0, 4, 8, 11)),
+                6, List.of(Set.of(0, 4, 7), Set.of(0, 4, 7)),
+                7, List.of(Set.of(0, 4, 8, 11), Set.of(0, 4)),
+                8, List.of(Set.of(0, 4, 7), Set.of(0))
+        );
+        Assert.assertEquals(actual, expected);
+    }
+
+    static Map<Integer, List<Set<Integer>>> liveness(Op op) {
+        Liveness l = new Liveness(op);
+        System.out.println(l);
+
+        Map<Value, Integer> valueMap = valueNameMapping(op);
+        Map<Block, Integer> blockMap = blockNameMapping(op);
+
+        return op.traverse(new HashMap<>(),
+                CodeElement.blockVisitor((m, b) -> {
+                    if (b.parentBody().parentOp() == op) {
+                        Liveness.BlockInfo lbi = l.getLiveness(b);
+                        m.put(blockMap.get(b),
+                                List.of(
+                                        lbi.liveIn().stream().map(valueMap::get).collect(Collectors.toSet()),
+                                        lbi.liveOut().stream().map(valueMap::get).collect(Collectors.toSet())
+                                ));
+                    }
+                    return m;
+                }));
+    }
+
+    static Map<Block, Integer> blockNameMapping(Op top) {
+        AtomicInteger i = new AtomicInteger();
+        return top.traverse(new HashMap<>(), CodeElement.blockVisitor((m, b) -> {
+            if (b.parentBody().parentOp() != top) {
+                return m;
+            }
+
+            m.computeIfAbsent(b, _ -> i.getAndIncrement());
+            for (Block.Reference s : b.successors()) {
+                m.computeIfAbsent(s.targetBlock(), _ -> i.getAndIncrement());
+            }
+
+            return m;
+        }));
+    }
+
+    static Map<Value, Integer> valueNameMapping(Op top) {
+        AtomicInteger i = new AtomicInteger();
+        return top.traverse(new HashMap<>(), (m, e) -> {
+            switch (e) {
+                case Block b -> {
+                    for (Block.Parameter p : b.parameters()) {
+                        m.put(p, i.getAndIncrement());
+                    }
+                }
+                case Op op -> {
+                    Op.Result r = op.result();
+                    if (r != null && !r.type().equals(JavaType.VOID)) {
+                        m.put(r, i.getAndIncrement());
+                    }
+                }
+                default -> {
+                }
+            }
+            return m;
+        });
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestLocalTransformationsAdaption.java b/test/jdk/java/lang/reflect/code/TestLocalTransformationsAdaption.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestLocalTransformationsAdaption.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.PrintStream;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.function.IntBinaryOperator;
+import java.util.function.IntUnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.op.CoreOp.arrayStoreOp;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.op.CoreOp.fieldLoad;
+import static java.lang.reflect.code.op.CoreOp.newArray;
+import static java.lang.reflect.code.type.MethodRef.method;
+import static java.lang.reflect.code.type.JavaType.*;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestLocalTransformationsAdaption
+ */
+
+public class TestLocalTransformationsAdaption {
+
+    @CodeReflection
+    static int f(int i) {
+        IntBinaryOperator add = (a, b) -> {
+            return add(a, b);
+        };
+
+        try {
+            IntUnaryOperator add42 = (a) -> {
+                return add.applyAsInt(a, 42);
+            };
+
+            int j = add42.applyAsInt(i);
+
+            IntBinaryOperator f = (a, b) -> {
+                if (i < 0) {
+                    throw new RuntimeException();
+                }
+
+                IntUnaryOperator g = (c) -> {
+                    return add(a, c);
+                };
+
+                return g.applyAsInt(b);
+            };
+
+            return f.applyAsInt(j, j);
+        } catch (RuntimeException e) {
+            throw new IndexOutOfBoundsException(i);
+        }
+    }
+
+    @Test
+    public void testInvocation() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f.writeTo(System.out);
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        f.writeTo(System.out);
+
+        int x = (int) Interpreter.invoke(MethodHandles.lookup(), f, 2);
+        Assert.assertEquals(x, f(2));
+
+        try {
+            Interpreter.invoke(MethodHandles.lookup(), f, -10);
+            Assert.fail();
+        } catch (Throwable e) {
+            Assert.assertEquals(IndexOutOfBoundsException.class, e.getClass());
+        }
+    }
+
+    @Test
+    public void testFuncEntryExit() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f.writeTo(System.out);
+
+        AtomicBoolean first = new AtomicBoolean(true);
+        CoreOp.FuncOp fc = f.transform((block, op) -> {
+            if (first.get()) {
+                printConstantString(block, "ENTRY");
+                first.set(false);
+            }
+
+            switch (op) {
+                case CoreOp.ReturnOp returnOp when getNearestInvokeableAncestorOp(returnOp) instanceof CoreOp.FuncOp: {
+                    printConstantString(block, "EXIT");
+                    break;
+                }
+                case CoreOp.ThrowOp throwOp: {
+                    printConstantString(block, "EXIT");
+                    break;
+                }
+                default:
+            }
+
+            block.apply(op);
+
+            return block;
+        });
+        fc.writeTo(System.out);
+
+        fc = fc.transform(OpTransformer.LOWERING_TRANSFORMER);
+        fc.writeTo(System.out);
+
+        int x = (int) Interpreter.invoke(MethodHandles.lookup(), fc, 2);
+        Assert.assertEquals(x, f(2));
+
+        try {
+            Interpreter.invoke(MethodHandles.lookup(), fc, -10);
+            Assert.fail();
+        } catch (Throwable e) {
+            Assert.assertEquals(IndexOutOfBoundsException.class, e.getClass());
+        }
+    }
+
+    static void printConstantString(Function<Op, Op.Result> opBuilder, String s) {
+        Op.Result c = opBuilder.apply(constant(J_L_STRING, s));
+        Value System_out = opBuilder.apply(fieldLoad(FieldRef.field(System.class, "out", PrintStream.class)));
+        opBuilder.apply(CoreOp.invoke(method(PrintStream.class, "println", void.class, String.class), System_out, c));
+    }
+
+    static Op getNearestInvokeableAncestorOp(Op op) {
+        do {
+            op = op.ancestorBody().parentOp();
+        } while (!(op instanceof Op.Invokable));
+        return op;
+    }
+
+
+    @Test
+    public void testReplaceCall() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp fc = f.transform((block, op) -> {
+            switch (op) {
+                case CoreOp.InvokeOp invokeOp when invokeOp.invokeDescriptor().equals(ADD_METHOD): {
+                    // Get the adapted operands, and pass those to the new call method
+                    List<Value> adaptedOperands = block.context().getValues(op.operands());
+                    Op.Result adaptedResult = block.apply(CoreOp.invoke(ADD_WITH_PRINT_METHOD, adaptedOperands));
+                    // Map the old call result to the new call result, so existing operations can be
+                    // adapted to use the new result
+                    block.context().mapValue(invokeOp.result(), adaptedResult);
+                    break;
+                }
+                default: {
+                    block.apply(op);
+                }
+            }
+            return block;
+        });
+        fc.writeTo(System.out);
+
+        fc = fc.transform(OpTransformer.LOWERING_TRANSFORMER);
+        fc.writeTo(System.out);
+
+        int x = (int) Interpreter.invoke(MethodHandles.lookup(), fc, 2);
+        Assert.assertEquals(x, f(2));
+    }
+
+
+    @Test
+    public void testCallEntryExit() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp fc = f.transform((block, op) -> {
+            switch (op) {
+                case CoreOp.InvokeOp invokeOp: {
+                    printCall(block.context(), invokeOp, block);
+                    break;
+                }
+                default: {
+                    block.apply(op);
+                }
+            }
+            return block;
+        });
+        fc.writeTo(System.out);
+
+        fc = fc.transform(OpTransformer.LOWERING_TRANSFORMER);
+        fc.writeTo(System.out);
+
+        int x = (int) Interpreter.invoke(MethodHandles.lookup(), fc, 2);
+        Assert.assertEquals(x, f(2));
+    }
+
+    static void printCall(CopyContext cc, CoreOp.InvokeOp invokeOp, Function<Op, Op.Result> opBuilder) {
+        List<Value> adaptedInvokeOperands = cc.getValues(invokeOp.operands());
+
+        String prefix = "ENTER";
+
+        Value arrayLength = opBuilder.apply(
+                constant(INT, adaptedInvokeOperands.size()));
+        Value formatArray = opBuilder.apply(
+                newArray(type(Object[].class), arrayLength));
+
+        Value indexZero = null;
+        for (int i = 0; i < adaptedInvokeOperands.size(); i++) {
+            Value operand = adaptedInvokeOperands.get(i);
+
+            Value index = opBuilder.apply(
+                    constant(INT, i));
+            if (i == 0) {
+                indexZero = index;
+            }
+
+            if (operand.type().equals(INT)) {
+                operand = opBuilder.apply(
+                        CoreOp.invoke(method(Integer.class, "valueOf", Integer.class, int.class), operand));
+                // @@@ Other primitive types
+            }
+            opBuilder.apply(
+                    arrayStoreOp(formatArray, index, operand));
+        }
+
+        Op.Result formatString = opBuilder.apply(
+                constant(J_L_STRING,
+                        prefix + ": " + invokeOp.invokeDescriptor() + "(" + formatString(adaptedInvokeOperands) + ")%n"));
+        Value System_out = opBuilder.apply(fieldLoad(FieldRef.field(System.class, "out", PrintStream.class)));
+        opBuilder.apply(
+                CoreOp.invoke(method(PrintStream.class, "printf", PrintStream.class, String.class, Object[].class),
+                        System_out, formatString, formatArray));
+
+        // Method call
+
+        Op.Result adaptedInvokeResult = opBuilder.apply(invokeOp);
+
+        // After method call
+
+        prefix = "EXIT";
+
+        if (adaptedInvokeResult.type().equals(INT)) {
+            adaptedInvokeResult = opBuilder.apply(
+                    CoreOp.invoke(method(Integer.class, "valueOf", Integer.class, int.class), adaptedInvokeResult));
+            // @@@ Other primitive types
+        }
+        opBuilder.apply(
+                arrayStoreOp(formatArray, indexZero, adaptedInvokeResult));
+
+        formatString = opBuilder.apply(
+                constant(J_L_STRING,
+                        prefix + ": " + invokeOp.invokeDescriptor() + " -> " + formatString(adaptedInvokeResult.type()) + "%n"));
+        opBuilder.apply(
+                CoreOp.invoke(method(PrintStream.class, "printf", PrintStream.class, String.class, Object[].class),
+                        System_out, formatString, formatArray));
+    }
+
+    static String formatString(List<Value> vs) {
+        return vs.stream().map(v -> formatString(v.type())).collect(Collectors.joining(","));
+    }
+
+    static String formatString(TypeElement t) {
+        if (t.equals(INT)) {
+            return "%d";
+        } else {
+            return "%s";
+        }
+    }
+
+
+    static final MethodRef ADD_METHOD = MethodRef.method(
+            TestLocalTransformationsAdaption.class, "add",
+            int.class, int.class, int.class);
+
+    static int add(int a, int b) {
+        return a + b;
+    }
+
+    static final MethodRef ADD_WITH_PRINT_METHOD = MethodRef.method(
+            TestLocalTransformationsAdaption.class, "addWithPrint",
+            int.class, int.class, int.class);
+
+    static int addWithPrint(int a, int b) {
+        System.out.printf("Adding %d + %d%n", a, b);
+        return a + b;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestLocalTransformationsAdaption.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestPatterns.java b/test/jdk/java/lang/reflect/code/TestPatterns.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestPatterns.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestPatterns
+ * @enablePreview
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestPatterns {
+
+    interface Point {
+    }
+
+    record ConcretePoint(int x, int y) implements Point {
+    }
+
+    enum Color {RED, GREEN, BLUE}
+
+    record ColoredPoint(ConcretePoint p, Color c) implements Point {
+    }
+
+    record Rectangle(Point upperLeft, Point lowerRight) {
+    }
+
+
+    @CodeReflection
+    public static String recordPatterns(Object r) {
+        if (r instanceof Rectangle(
+                ColoredPoint(ConcretePoint p, Color c),
+                ColoredPoint lr)) {
+            return p.toString();
+        } else {
+            return "";
+        }
+    }
+
+    @Test
+    public void testRecordPatterns() {
+        CoreOp.FuncOp f = getFuncOp("recordPatterns");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        {
+            Rectangle r = new Rectangle(
+                    new ColoredPoint(new ConcretePoint(1, 2), Color.RED),
+                    new ColoredPoint(new ConcretePoint(3, 4), Color.BLUE));
+            Assert.assertEquals(Interpreter.invoke(MethodHandles.lookup(), lf, r), recordPatterns(r));
+        }
+
+        {
+            Rectangle r = new Rectangle(
+                    new ColoredPoint(new ConcretePoint(1, 2), Color.RED),
+                    new ConcretePoint(3, 4));
+            Assert.assertEquals(Interpreter.invoke(MethodHandles.lookup(), lf, r), recordPatterns(r));
+        }
+
+        {
+            Rectangle r = new Rectangle(
+                    new ConcretePoint(1, 2),
+                    new ConcretePoint(3, 4));
+            Assert.assertEquals(Interpreter.invoke(MethodHandles.lookup(), lf, r), recordPatterns(r));
+        }
+
+        {
+            String r = "";;
+            Assert.assertEquals(Interpreter.invoke(MethodHandles.lookup(), lf, r), recordPatterns(r));
+        }
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestPatterns.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+}
diff a/test/jdk/java/lang/reflect/code/TestPrimitiveCast.java b/test/jdk/java/lang/reflect/code/TestPrimitiveCast.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestPrimitiveCast.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestPrimitiveCast
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.joining;
+
+public class TestPrimitiveCast {
+
+    static final Function<Object, String> FROM_DOUBLE = v -> fromDouble((double) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromDouble(double v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_FLOAT = v -> fromFloat((float) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromFloat(float v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_LONG = v -> fromLong((long) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromLong(long v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_INT = v -> fromInt((int) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromInt(int v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_SHORT = v -> fromShort((short) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromShort(short v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_CHAR = v -> fromChar((char) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromChar(char v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    static final Function<Object, String> FROM_BYTE = v -> fromByte((byte) v);
+
+    @CodeReflection
+    @SuppressWarnings("cast")
+    static String fromByte(byte v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+        return collect(d, f, l, i, s, c, b);
+    }
+
+    @DataProvider
+    static Object[][] fromMethods() {
+        return new Object[][] {
+                { "fromDouble", Math.PI, FROM_DOUBLE},
+                { "fromDouble", 65.1, FROM_DOUBLE},
+                { "fromFloat", (float) Math.PI, FROM_FLOAT},
+                { "fromFloat", 65.1f, FROM_FLOAT},
+                { "fromLong", Long.MAX_VALUE, FROM_LONG},
+                { "fromInt", Integer.MAX_VALUE, FROM_INT},
+                { "fromShort", Short.MAX_VALUE, FROM_SHORT},
+                { "fromChar", Character.MAX_VALUE, FROM_CHAR},
+                { "fromByte", Byte.MAX_VALUE, FROM_BYTE},
+        };
+    };
+
+    @Test(dataProvider = "fromMethods")
+    public void testFromDouble(String name, Object value, Function<Object, String> m) {
+        CoreOp.FuncOp f = getFuncOp(name);
+        Assert.assertEquals(Interpreter.invoke(MethodHandles.lookup(), f, value), m.apply(value));
+    }
+
+
+    static String collect(Object... values) {
+        return Stream.of(values).map(Object::toString).collect(joining(" "));
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestPrimitiveCast.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestRemoveFinalVars.java b/test/jdk/java/lang/reflect/code/TestRemoveFinalVars.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestRemoveFinalVars.java
@@ -0,0 +1,96 @@
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.op.CoreOp.FuncOp;
+import static java.lang.reflect.code.op.CoreOp.VarAccessOp.VarLoadOp;
+import static java.lang.reflect.code.op.CoreOp.VarAccessOp.VarStoreOp;
+import static java.lang.reflect.code.op.CoreOp.VarOp;
+
+/*
+ * @test
+ * @run testng TestRemoveFinalVars
+ */
+
+public class TestRemoveFinalVars {
+
+    @CodeReflection
+    static boolean f() {
+        final int x = 8; // final var
+        int y = x + 2; // final var
+        int z = y + 3; // non final var
+        z++;
+        return x == 8 && y == 10 && z == 14;
+    }
+
+    @Test
+    void test() {
+        FuncOp f = getFuncOp(this.getClass(),"f");
+        f.writeTo(System.out);
+        FuncOp lf = lower(f);
+        lf.writeTo(System.out);
+
+        FuncOp f2 = f.transform(TestRemoveFinalVars::rmFinalVars);
+        f2.writeTo(System.out);
+        FuncOp lf2 = lower(f2);
+        lf2.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), Interpreter.invoke(lf2));
+
+        SSA.transform(lower(f)).writeTo(System.out);
+    }
+
+    static FuncOp lower(FuncOp funcOp) {
+        return funcOp.transform(OpTransformer.LOWERING_TRANSFORMER);
+    }
+
+    static Block.Builder rmFinalVars(Block.Builder block, Op op) {
+        if (op instanceof VarOp varOp) {
+            // Is the variable stored to? If not we can remove it
+            // otherwise, it's not considered final and we copy it
+            if (isValueUsedWithOp(varOp.result(), o -> o instanceof VarStoreOp)) {
+                block.op(varOp);
+            }
+        } else if (op instanceof VarLoadOp varLoadOp) {
+            // If the variable is not stored to
+            if (!isValueUsedWithOp(varLoadOp.varOp().result(), o -> o instanceof VarStoreOp)) {
+                // Map result of load from variable to the value that initialized the variable
+                // Subsequently encountered input operations using the result will be copied
+                // to output operations using the mapped value
+                CopyContext cc = block.context();
+                cc.mapValue(varLoadOp.result(), cc.getValue(varLoadOp.varOp().operands().get(0)));
+            } else {
+                block.op(varLoadOp);
+            }
+        } else {
+            block.op(op);
+        }
+        return block;
+    }
+
+    private static boolean isValueUsedWithOp(Value value, Predicate<Op> opPredicate) {
+        for (Op.Result user : value.uses()) {
+            if (opPredicate.test(user.op())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static FuncOp getFuncOp(Class<?> c, String name) {
+        Optional<Method> om = Stream.of(c.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestSSA.java b/test/jdk/java/lang/reflect/code/TestSSA.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestSSA.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestSSA
+ */
+
+public class TestSSA {
+
+    @CodeReflection
+    static int ifelse(int a, int b, int n) {
+        if (n < 10) {
+            a += 1;
+        } else {
+            b += 2;
+        }
+        return a + b;
+    }
+
+    @Test
+    public void testIfelse() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("ifelse");
+
+        CoreOp.FuncOp lf = generate(f);
+
+        Assert.assertEquals((int) Interpreter.invoke(lf, 0, 0, 1), ifelse(0, 0, 1));
+        Assert.assertEquals((int) Interpreter.invoke(lf, 0, 0, 11), ifelse(0, 0, 11));
+    }
+
+    @CodeReflection
+    static int ifelseNested(int a, int b, int c, int d, int n) {
+        if (n < 20) {
+            if (n < 10) {
+                a += 1;
+            } else {
+                b += 2;
+            }
+            c += 3;
+        } else {
+            if (n > 20) {
+                a += 4;
+            } else {
+                b += 5;
+            }
+            d += 6;
+        }
+        return a + b + c + d;
+    }
+
+    @Test
+    public void testIfelseNested() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("ifelseNested");
+
+        CoreOp.FuncOp lf = generate(f);
+
+        for (int i : new int[]{1, 11, 20, 21}) {
+            Assert.assertEquals((int) Interpreter.invoke(lf, 0, 0, 0, 0, i), ifelseNested(0, 0, 0, 0, i));
+        }
+    }
+
+    @CodeReflection
+    static int loop(int n) {
+        int sum = 0;
+        for (int i = 0; i < n; i++) {
+            sum = sum + i;
+        }
+        return sum;
+    }
+
+    @Test
+    public void testLoop() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("loop");
+
+        CoreOp.FuncOp lf = generate(f);
+
+        Assert.assertEquals((int) Interpreter.invoke(lf, 10), loop(10));
+    }
+
+    @CodeReflection
+    static int nestedLoop(int n) {
+        int sum = 0;
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                sum = sum + i + j;
+            }
+        }
+        return sum;
+    }
+
+    @Test
+    public void testNestedLoop() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("nestedLoop");
+
+        CoreOp.FuncOp lf = generate(f);
+
+        Assert.assertEquals((int) Interpreter.invoke(lf, 10), nestedLoop(10));
+    }
+
+    static CoreOp.FuncOp generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        lf = SSA.transform(lf);
+        lf.writeTo(System.out);
+        return lf;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestSSA.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestTraverse.java b/test/jdk/java/lang/reflect/code/TestTraverse.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestTraverse.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestTraverse
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.CodeElement;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestTraverse {
+
+    @CodeReflection
+    private static int f(String s, int i, List<Object> acc) {
+        char c = s.charAt(i);
+        int d = (c - '0');
+        int n = s.length();
+        while (++i < n) {
+            c = s.charAt(i);
+            if (c >= '0' && c <= '9') {
+                d = d * 10 + (c - '0');
+                continue;
+            }
+            break;
+        }
+        acc.add(d);
+        return i;
+    }
+
+    @Test
+    public void test() {
+        CoreOp.FuncOp f = getFuncOp("f");
+        testTraverse(f);
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        testTraverse(f);
+
+        f = SSA.transform(f);
+        testTraverse(f);
+    }
+
+    void testTraverse(Op op) {
+        List<CodeElement<?, ?>> tl = op.traverse(new ArrayList<>(), (l, e) -> {
+            l.add(e);
+            return l;
+        });
+        Assert.assertEquals(op.elements().toList(), tl);
+
+        Assert.assertEquals(op.elements().limit(2).toList(), tl.subList(0, 2));
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTraverse.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestTry.java b/test/jdk/java/lang/reflect/code/TestTry.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestTry.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestTry
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+public class TestTry {
+
+    @CodeReflection
+    public static void catching(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            consume(e);
+            c.accept(1);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            consume(e);
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatching() {
+        CoreOp.FuncOp f = getFuncOp("catching");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTry::catching);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new NullPointerException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+
+    @CodeReflection
+    public static void catchThrowable(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            consume(e);
+            c.accept(1);
+            c.accept(-1);
+        } catch (Throwable e) {
+            consume(e);
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchThrowable() {
+        CoreOp.FuncOp f = getFuncOp("catchThrowable");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTry::catchThrowable);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+
+    @CodeReflection
+    public static void catchNested(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+            try {
+                c.accept(1);
+                c.accept(-1);
+            } catch (IllegalStateException e) {
+                consume(e);
+                c.accept(2);
+                c.accept(-1);
+            }
+            c.accept(3);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            consume(e);
+            c.accept(4);
+            c.accept(-1);
+        }
+        c.accept(5);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchNested() {
+        CoreOp.FuncOp f = getFuncOp("catchNested");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTry::catchNested);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+            if (i == 4) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 5) throw new RuntimeException();
+        });
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTry.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    static void consume(Throwable e) {
+    }
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestTryFinally.java b/test/jdk/java/lang/reflect/code/TestTryFinally.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestTryFinally.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestTryFinally
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+public class TestTryFinally {
+
+    @CodeReflection
+    public static void tryCatchFinally(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchFinally() {
+        CoreOp.FuncOp f = getFuncOp("tryCatchFinally");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinally::tryCatchFinally
+        );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void tryReturn(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+            return;
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testTryReturn() {
+        CoreOp.FuncOp f = getFuncOp("tryReturn");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinally::tryReturn
+                );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void catchThrow(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+            throw e;
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchThrow() {
+        CoreOp.FuncOp f = getFuncOp("catchThrow");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinally::catchThrow
+        );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void finallyReturn(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+            return;
+        }
+    }
+
+    @Test
+    public void finallyReturn() {
+        CoreOp.FuncOp f = getFuncOp("finallyReturn");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinally::finallyReturn
+        );
+
+        test(test);
+    }
+
+
+    static void test(Consumer<IntConsumer> test) {
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTryFinally.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestTryFinallyNested.java b/test/jdk/java/lang/reflect/code/TestTryFinallyNested.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestTryFinallyNested.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestTryFinallyNested
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+public class TestTryFinallyNested {
+    @CodeReflection
+    public static void tryCatchFinally(IntConsumer c, int i) {
+        try {
+            try {
+                if (i == 0) {
+                    return;
+                }
+                c.accept(0);
+            } catch (IllegalStateException e) {
+                if (i == 1) {
+                    return;
+                }
+                c.accept(1);
+            } finally {
+                if (i == 2) {
+                    return;
+                }
+                c.accept(2);
+            }
+            if (i == 3) {
+                return;
+            }
+            c.accept(3);
+        } catch (IllegalStateException e) {
+            if (i == 4) {
+                return;
+            }
+            c.accept(4);
+        } finally {
+            if (i == 5) {
+                return;
+            }
+            c.accept(5);
+        }
+        c.accept(6);
+    }
+
+    @Test
+    public void testCatchFinally() {
+        CoreOp.FuncOp f = getFuncOp("tryCatchFinally");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int ra = -1; ra < 6; ra++) {
+            int fra = ra;
+
+            Consumer<IntConsumer> test = testConsumer(
+                    c -> Interpreter.invoke(MethodHandles.lookup(), lf, c, fra),
+                    c -> tryCatchFinally(c, fra)
+            );
+
+            test.accept(i -> {});
+            for (int ea = 0; ea < 6; ea++) {
+                int fea = ea;
+                test.accept(i -> {
+                    if (i == fea) throw new IllegalStateException();
+                });
+                test.accept(i -> {
+                    if (i == fea) throw new RuntimeException();
+                });
+            }
+        }
+    }
+
+
+    @CodeReflection
+    public static void tryCatchFinallyBreak(IntConsumer c, int i) {
+        a: try {
+            try {
+                if (i == 0) {
+                    break a;
+                }
+                c.accept(0);
+            } catch (IllegalStateException e) {
+                if (i == 1) {
+                    break a;
+                }
+                c.accept(1);
+            } finally {
+                if (i == 2) {
+                    break a;
+                }
+                c.accept(2);
+            }
+            if (i == 3) {
+                break a;
+            }
+            c.accept(3);
+        } catch (IllegalStateException e) {
+            if (i == 4) {
+                break a;
+            }
+            c.accept(4);
+        } finally {
+            if (i == 5) {
+                break a;
+            }
+            c.accept(5);
+        }
+        c.accept(6);
+    }
+
+    @Test
+    public void testCatchFinallyBreak() {
+        CoreOp.FuncOp f = getFuncOp("tryCatchFinallyBreak");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int ra = -1; ra < 6; ra++) {
+            int fra = ra;
+
+            Consumer<IntConsumer> test = testConsumer(
+                    c -> Interpreter.invoke(MethodHandles.lookup(), lf, c, fra),
+                    c -> tryCatchFinallyBreak(c, fra)
+            );
+
+            test.accept(i -> {});
+            for (int ea = 0; ea < 6; ea++) {
+                int fea = ea;
+                test.accept(i -> {
+                    if (i == fea) throw new IllegalStateException();
+                });
+                test.accept(i -> {
+                    if (i == fea) throw new RuntimeException();
+                });
+            }
+        }
+    }
+
+
+    @CodeReflection
+    public static void tryForLoop(IntConsumer c) {
+        for (int i = 0; i < 8; i++) {
+            c.accept(0);
+            try {
+                if (i == 4) {
+                    continue;
+                } else if (i == 5) {
+                    break;
+                }
+                c.accept(1);
+            } finally {
+                c.accept(2);
+            }
+            c.accept(3);
+        }
+        c.accept(4);
+    }
+
+    @Test
+    public void testTryForLoop() {
+        CoreOp.FuncOp f = getFuncOp("tryForLoop");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinallyNested::tryForLoop
+        );
+
+        test.accept(i -> { });
+    }
+
+
+    @CodeReflection
+    public static void tryLabeledForLoop(IntConsumer c) {
+        a: for (int i = 0; i < 8; i++) {
+            c.accept(0);
+            b: {
+                try {
+                    if (i == 4) {
+                        continue a;
+                    } else if (i == 5) {
+                        break b;
+                    } else if (i == 6) {
+                        break a;
+                    }
+                    c.accept(1);
+                } finally {
+                    c.accept(2);
+                }
+                c.accept(3);
+            }
+            c.accept(4);
+        }
+        c.accept(5);
+    }
+
+    @Test
+    public void testTryLabeledForLoop() {
+        CoreOp.FuncOp f = getFuncOp("tryLabeledForLoop");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Consumer<IntConsumer> test = testConsumer(
+                c -> Interpreter.invoke(MethodHandles.lookup(), lf, c),
+                TestTryFinallyNested::tryLabeledForLoop
+        );
+
+        test.accept(i -> { });
+    }
+
+
+    @CodeReflection
+    public static void tryLambda(IntConsumer c, int i) {
+        try {
+            c.accept(0);
+            Runnable r = () -> {
+                if (i == 0) {
+                    c.accept(1);
+                    return;
+                } else {
+                    c.accept(2);
+                }
+                c.accept(3);
+            };
+            r.run();
+            c.accept(4);
+        } finally {
+            c.accept(5);
+        }
+    }
+
+    @Test
+    public void testTryLambda() {
+        CoreOp.FuncOp f = getFuncOp("tryLambda");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        for (int ra = 0; ra < 2; ra++) {
+            final int fra = ra;
+            Consumer<IntConsumer> test = testConsumer(
+                    c -> Interpreter.invoke(MethodHandles.lookup(), lf, c, fra),
+                    c -> tryLambda(c, fra)
+            );
+            test.accept(i -> { });
+        }
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTryFinallyNested.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestUsesDependsOn.java b/test/jdk/java/lang/reflect/code/TestUsesDependsOn.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestUsesDependsOn.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestUsesDependsOn
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.parser.OpParser;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class TestUsesDependsOn {
+
+    static final String OP = """
+            func @"f" (%0 : int, %1 : int)int -> {
+                %2 : int = add %0 %1;
+                %3 : boolean = lt %0 %1;
+                %4 : void = cbranch %3 ^b1(%2, %2) ^b2(%0, %1);
+
+              ^b1(%5 : int, %6 : int):
+                %7 : void = return %5;
+
+              ^b2(%8 : int, %9 : int):
+                %10 : void = return %8;
+            };
+            """;
+
+    @Test
+    public void testDependsOn() {
+        Op f = OpParser.fromStringOfFuncOp(OP);
+
+        Map<String, List<String>> dependsUpon = computeValueMap(f, Value::dependsOn);
+
+        var expected = Map.ofEntries(
+                Map.entry("0", List.of()),
+                Map.entry("1", List.of()),
+                Map.entry("2", List.of("0", "1")),
+                Map.entry("3", List.of("0", "1")),
+                Map.entry("4", List.of("3", "2", "0", "1")),
+                Map.entry("5", List.of()),
+                Map.entry("6", List.of()),
+                Map.entry("7", List.of("5")),
+                Map.entry("8", List.of()),
+                Map.entry("9", List.of()),
+                Map.entry("10", List.of("8"))
+        );
+
+        Assert.assertEquals(dependsUpon, expected);
+    }
+
+
+    @Test
+    public void testUses() {
+        Op f = OpParser.fromStringOfFuncOp(OP);
+        f.writeTo(System.out);
+
+        Map<String, List<String>> uses = computeValueMap(f, Value::uses);
+
+        var expected = Map.ofEntries(
+                Map.entry("0", List.of("2", "3", "4")),
+                Map.entry("1", List.of("2", "3", "4")),
+                Map.entry("2", List.of("4")),
+                Map.entry("3", List.of("4")),
+                Map.entry("4", List.of()),
+                Map.entry("5", List.of("7")),
+                Map.entry("6", List.of()),
+                Map.entry("7", List.of()),
+                Map.entry("8", List.of("10")),
+                Map.entry("9", List.of()),
+                Map.entry("10", List.of())
+        );
+        System.out.println(uses.toString());
+        System.out.println(expected);
+
+        Assert.assertEquals(uses, expected);
+    }
+
+    static Map<String, List<String>> computeValueMap(Op op, Function<Value, Set<? extends Value>> f) {
+        AtomicInteger ai = new AtomicInteger();
+
+        Map<Value, String> valueNameMap = computeValues(op, new HashMap<>(), (v, m) -> {
+            String name = Integer.toString(ai.getAndIncrement());
+            m.put(v, name);
+        });
+
+        return computeValues(op, new HashMap<>(), (v, m) -> {
+            m.put(valueNameMap.get(v), f.apply(v).stream().map(valueNameMap::get).toList());
+        });
+    }
+
+    static <T> T computeValues(Op op, T t, BiConsumer<Value, T> c) {
+        return op.traverse(t, (m, codeElement) -> {
+            return switch (codeElement) {
+                case Block b -> {
+                    for (var a : b.parameters()) {
+                        c.accept(a, m);
+                    }
+
+                    yield m;
+                }
+                case Op o -> {
+                    if (o.result() != null) {
+                        c.accept(o.result(), m);
+                    }
+
+                    yield m;
+                }
+                default -> m;
+            };
+        });
+    }
+}
diff a/test/jdk/java/lang/reflect/code/TestWhileOp.java b/test/jdk/java/lang/reflect/code/TestWhileOp.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/TestWhileOp.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestWhileOp
+ */
+
+public class TestWhileOp {
+
+    @CodeReflection
+    public static int whileLoop() {
+        int i = 0;
+        while (i < 10) {
+            i++;
+        }
+        return i;
+    }
+
+    @Test
+    public void testWhileLoop() {
+        CoreOp.FuncOp f = getFuncOp("whileLoop");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), whileLoop());
+    }
+
+    @CodeReflection
+    public static int doWhileLoop() {
+        int i = 0;
+        do {
+            i++;
+        } while (i < 10);
+        return i;
+    }
+
+    @Test
+    public void testDpWhileLoop() {
+        CoreOp.FuncOp f = getFuncOp("doWhileLoop");
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(lf), doWhileLoop());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestWhileOp.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/ad/ActiveSet.java b/test/jdk/java/lang/reflect/code/ad/ActiveSet.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/ad/ActiveSet.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.op.CoreOp;
+import java.util.*;
+
+public final class ActiveSet {
+
+    private ActiveSet() {
+    }
+
+    // Create active value set, in order, starting from the given block parameter
+    // following its users and following through block arguments
+    // to block parameters, and so on until the graph is traversed.
+
+    public static Set<Value> activeSet(CoreOp.FuncOp f, Block.Parameter fv) {
+        if (!f.body().blocks().get(0).parameters().contains(fv)) {
+            throw new IllegalArgumentException("Arg is not defined by function");
+        }
+        Deque<Value> q = new ArrayDeque<>();
+        q.push(fv);
+
+        Set<Value> active = new TreeSet<>();
+        while (!q.isEmpty()) {
+            Value v = q.pop();
+            if (active.contains(v)) {
+                continue;
+            }
+            active.add(v);
+
+            // @@@ assume uses are declared in order?
+            //     if so can push to queue in reverse order
+            for (Op.Result or : v.uses()) {
+                q.push(or);
+                Op op = or.op();
+
+                if (op instanceof Op.Terminating) {
+                    for (Block.Reference s : op.successors()) {
+                        for (int i = 0; i < s.arguments().size(); i++) {
+                            if (v == s.arguments().get(i)) {
+                                Block b = s.targetBlock();
+                                Block.Parameter ba = b.parameters().get(i);
+
+                                // Processing of block arguments may result in out of order
+                                // production of uses if two or more block arguments are added
+                                // for the same successor argument
+                                q.push(ba);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Ensure non-active block arguments of successors are added to the
+        // active set for blocks with corresponding active parameters
+        // Backtracking is not performed on the values as they are not strictly
+        // active set but may be required for initialization purposes.
+        Set<Value> bactive = new LinkedHashSet<>();
+        for (Value v : active) {
+            if (v instanceof Block.Parameter ba) {
+                Block b = ba.declaringBlock();
+                int i = b.parameters().indexOf(ba);
+
+                for (Block p : b.predecessors()) {
+                    Op to = p.terminatingOp();
+                    for (Block.Reference s : to.successors()) {
+                        if (s.targetBlock() == b) {
+                            Value arg = s.arguments().get(i);
+                            if (!active.contains(arg)) {
+                                bactive.add(arg);
+                                bactive.add(to.result());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        active.addAll(bactive);
+
+        return active;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/ad/ExpressionElimination.java b/test/jdk/java/lang/reflect/code/ad/ExpressionElimination.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/ad/ExpressionElimination.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.JavaType;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+import static java.lang.reflect.code.op.CoreOp.sub;
+import static java.lang.reflect.code.analysis.Patterns.*;
+
+public final class ExpressionElimination {
+    private ExpressionElimination() {
+    }
+
+    static final JavaType J_L_MATH = JavaType.type(Math.class);
+
+    static OpPattern negP(Pattern operand) {
+        return opP(CoreOp.NegOp.class, operand);
+    }
+
+    static OpPattern addP(Pattern lhs, Pattern rhs) {
+        return opP(CoreOp.AddOp.class, lhs, rhs);
+    }
+
+    static OpPattern mulP(Pattern lhs, Pattern rhs) {
+        return opP(CoreOp.MulOp.class, lhs, rhs);
+    }
+
+    public static <T extends Op> T eliminate(T f) {
+        // Note expression elimination and other forms of analysis is simplified if first of all expressions
+        // are normalized e.g. when they have an operand that is a constant expression
+        // and the operation is associative such as add(0, x) -> add(x, 0)
+
+        var actions = multiMatch(new HashMap<Op.Result, BiConsumer<Block.Builder, Op>>(), f)
+                .pattern(mulP(_P(), valueP(constantP(0.0d))))
+                .pattern(mulP(valueP(constantP(0.0d)), _P()))
+                .pattern(addP(valueP(), constantP(0.0d)))
+                .pattern(addP(constantP(0.0d), valueP()))
+                .pattern(mulP(constantP(1.0d), valueP()))
+                .pattern(mulP(valueP(), constantP(1.0d)))
+                .target((ms, as) -> {
+                    Value a = ms.matchedOperands().get(0);
+                    as.put(ms.op().result(), (block, op) -> {
+                        CopyContext cc = block.context();
+                        cc.mapValue(ms.op().result(), cc.getValue(a));
+                    });
+                    return as;
+                })
+                // add(neg(x), y) -> sub(y, x)
+                .pattern(addP(negP(valueP()), valueP()))
+                .target((ms, as) -> {
+                    Value x = ms.matchedOperands().get(0);
+                    Value y = ms.matchedOperands().get(1);
+
+                    as.put(ms.op().result(), (block, op) -> {
+                        CopyContext cc = block.context();
+                        Op.Result r = block.op(sub(cc.getValue(y), cc.getValue(x)));
+                        cc.mapValue(ms.op().result(), r);
+                    });
+                    return as;
+                })
+                .matchThenApply();
+
+        // Eliminate
+        Op ef = f.transform(CopyContext.create(), (block, op) -> {
+            BiConsumer<Block.Builder, Op> a = actions.get(op.result());
+            if (a != null) {
+                a.accept(block, op);
+            } else {
+                block.op(op);
+            }
+            return block;
+        });
+
+        Predicate<Op> testPure = op -> {
+            if (op instanceof Op.Pure) {
+                return true;
+            } else {
+                return op instanceof CoreOp.InvokeOp c && c.invokeDescriptor().refType().equals(J_L_MATH);
+            }
+        };
+
+        while (true) {
+            Set<Op> unused = matchUnusedPureOps(ef, testPure);
+            if (unused.isEmpty()) {
+                break;
+            }
+            // Remove unused ops
+            ef = ef.transform(CopyContext.create(), (block, op) -> {
+                if (!unused.contains(op)) {
+                    block.op(op);
+                }
+                return block;
+            });
+        }
+
+        @SuppressWarnings("unchecked")
+        T t = (T) ef;
+        return t;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/ad/ForwardDifferentiation.java b/test/jdk/java/lang/reflect/code/ad/ForwardDifferentiation.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/ad/ForwardDifferentiation.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Value;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.JavaType.DOUBLE;
+
+public final class ForwardDifferentiation {
+    // The function to differentiate
+    final FuncOp fcm;
+    // The independent variable
+    final Block.Parameter ind;
+    // The active set for the independent variable
+    final Set<Value> activeSet;
+    // The map of input value to it's (output) differentiated value
+    final Map<Value, Value> diffValueMapping;
+
+    // The constant value 0.0d
+    // Declared in the (output) function's entry block
+    Value zero;
+
+    private ForwardDifferentiation(FuncOp fcm, Block.Parameter ind) {
+        int indI = fcm.body().entryBlock().parameters().indexOf(ind);
+        if (indI == -1) {
+            throw new IllegalArgumentException("Independent argument not defined by function");
+        }
+        this.fcm = fcm;
+        this.ind = ind;
+
+        // Calculate the active set of dependent values for the independent value
+        this.activeSet = ActiveSet.activeSet(fcm, ind);
+        // A mapping of input values to their (output) differentiated values
+        this.diffValueMapping = new HashMap<>();
+    }
+
+    public static FuncOp partialDiff(FuncOp fcm, Block.Parameter ind) {
+        return new ForwardDifferentiation(fcm, ind).partialDiff();
+    }
+
+    FuncOp partialDiff() {
+        int indI = fcm.body().entryBlock().parameters().indexOf(ind);
+
+        AtomicBoolean first = new AtomicBoolean(true);
+        FuncOp dfcm = fcm.transform(String.format("d%s_darg%d", fcm.funcName(), indI),
+                (block, op) -> {
+                    if (first.getAndSet(false)) {
+                        // Initialize
+                        processBlocks(block);
+                    }
+
+                    // If the result of the operation is in the active set,
+                    // then differentiate it, otherwise copy it
+                    if (activeSet.contains(op.result())) {
+                        Value dor = diffOp(block, op);
+                        // Map the input result to its (output) differentiated result
+                        // so that it can be used when differentiating subsequent operations
+                        diffValueMapping.put(op.result(), dor);
+                    } else {
+                        block.apply(op);
+                    }
+                    return block;
+                });
+
+        return dfcm;
+    }
+
+    void processBlocks(Block.Builder block) {
+        // Declare constants at start
+        zero = block.op(constant(ind.type(), 0.0d));
+        // The differential of ind is 1
+        Value one = block.op(constant(ind.type(), 1.0d));
+        diffValueMapping.put(ind, one);
+
+        // Append differential block parameters to blocks
+        for (Value v : activeSet) {
+            if (v instanceof Block.Parameter ba) {
+                if (ba != ind) {
+                    // Get the output block builder for the input (declaring) block
+                    Block.Builder b = block.context().getBlock(ba.declaringBlock());
+                    // Add a new block parameter for differential parameter
+                    Block.Parameter dba = b.parameter(ba.type());
+                    // Place in mapping
+                    diffValueMapping.put(ba, dba);
+                }
+            }
+        }
+    }
+
+
+    static final JavaType J_L_MATH = JavaType.type(Math.class);
+    static final FunctionType D_D = FunctionType.functionType(DOUBLE, DOUBLE);
+    static final MethodRef J_L_MATH_SIN = MethodRef.method(J_L_MATH, "sin", D_D);
+    static final MethodRef J_L_MATH_COS = MethodRef.method(J_L_MATH, "cos", D_D);
+
+    Value diffOp(Block.Builder block, Op op) {
+        // Switch on the op, using pattern matching
+        return switch (op) {
+            case CoreOp.NegOp _ -> {
+                // Copy input operation
+                block.op(op);
+
+                // -diff(expr)
+                Value a = op.operands().get(0);
+                Value da = diffValueMapping.getOrDefault(a, zero);
+                yield block.op(neg(da));
+            }
+            case CoreOp.AddOp _ -> {
+                // Copy input operation
+                block.op(op);
+
+                // diff(l) + diff(r)
+                Value lhs = op.operands().get(0);
+                Value rhs = op.operands().get(1);
+                Value dlhs = diffValueMapping.getOrDefault(lhs, zero);
+                Value drhs = diffValueMapping.getOrDefault(rhs, zero);
+                yield block.op(add(dlhs, drhs));
+            }
+            case CoreOp.MulOp _ -> {
+                // Copy input operation
+                block.op(op);
+
+                // Product rule
+                // diff(l) * r + l * diff(r)
+                Value lhs = op.operands().get(0);
+                Value rhs = op.operands().get(1);
+                Value dlhs = diffValueMapping.getOrDefault(lhs, zero);
+                Value drhs = diffValueMapping.getOrDefault(rhs, zero);
+                Value outputLhs = block.context().getValue(lhs);
+                Value outputRhs = block.context().getValue(rhs);
+                yield block.op(add(
+                        block.op(mul(dlhs, outputRhs)),
+                        block.op(mul(outputLhs, drhs))));
+            }
+            case CoreOp.ConstantOp _ -> {
+                // Copy input operation
+                block.op(op);
+                // Differential of constant is zero
+                yield zero;
+            }
+            case CoreOp.InvokeOp c -> {
+                MethodRef md = c.invokeDescriptor();
+                String operationName = null;
+                if (md.refType().equals(J_L_MATH)) {
+                    operationName = md.name();
+                }
+                // Differentiate sin(x)
+                if ("sin".equals(operationName)) {
+                    // Copy input operation
+                    block.op(op);
+
+                    // Chain rule
+                    // cos(expr) * diff(expr)
+                    Value a = op.operands().get(0);
+                    Value da = diffValueMapping.getOrDefault(a, zero);
+                    Value outputA = block.context().getValue(a);
+                    Op.Result cosx = block.op(invoke(J_L_MATH_COS, outputA));
+                    yield block.op(mul(cosx, da));
+                } else {
+                    throw new UnsupportedOperationException("Operation not supported: " + op.opName());
+                }
+            }
+            case CoreOp.ReturnOp _ -> {
+                // Replace with return of differentiated value
+                Value a = op.operands().get(0);
+                Value da = diffValueMapping.getOrDefault(a, zero);
+                yield block.op(_return(da));
+            }
+            case Op.BlockTerminating _ -> {
+                // Update with differentiated block arguments
+                op.successors().forEach(s -> adaptSuccessor(block.context(), s));
+                yield block.op(op);
+            }
+            default -> throw new UnsupportedOperationException("Operation not supported: " + op.opName());
+        };
+    }
+
+    void adaptSuccessor(CopyContext cc, Block.Reference from) {
+        List<Value> as = from.arguments().stream()
+                .filter(activeSet::contains)
+                .toList();
+        if (!as.isEmpty()) {
+            // Get the successor arguments
+            List<Value> outputArgs = cc.getValues(from.arguments());
+            // Append the differential arguments, if any
+            for (Value a : as) {
+                Value da = diffValueMapping.get(a);
+                outputArgs.add(da);
+            }
+
+            // Map successor with appended arguments
+            Block.Reference to = cc.getBlock(from.targetBlock()).successor(outputArgs);
+            cc.mapSuccessor(from, to);
+        }
+    }
+
+}
diff a/test/jdk/java/lang/reflect/code/ad/TestForwardAutoDiff.java b/test/jdk/java/lang/reflect/code/ad/TestForwardAutoDiff.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/ad/TestForwardAutoDiff.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestForwardAutoDiff
+ */
+
+public class TestForwardAutoDiff {
+    static final double PI_4 = Math.PI / 4;
+
+    @Test
+    public void testExpression() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f");
+        f.writeTo(System.out);
+
+        f = SSA.transform(f);
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 0.0, 1.0), f(0.0, 1.0));
+        Assert.assertEquals(Interpreter.invoke(f, PI_4, PI_4), f(PI_4, PI_4));
+
+        Block.Parameter x = f.body().entryBlock().parameters().get(0);
+        Block.Parameter y = f.body().entryBlock().parameters().get(1);
+
+        CoreOp.FuncOp dff_dx = ExpressionElimination.eliminate(ForwardDifferentiation.partialDiff(f, x));
+        dff_dx.writeTo(System.out);
+        MethodHandle dff_dx_mh = generate(dff_dx);
+        Assert.assertEquals((double) dff_dx_mh.invoke(0.0, 1.0), df_dx(0.0, 1.0));
+        Assert.assertEquals((double) dff_dx_mh.invoke(PI_4, PI_4), df_dx(PI_4, PI_4));
+
+        CoreOp.FuncOp dff_dy = ExpressionElimination.eliminate(ForwardDifferentiation.partialDiff(f, y));
+        dff_dy.writeTo(System.out);
+        MethodHandle dff_dy_mh = generate(dff_dy);
+        Assert.assertEquals((double) dff_dy_mh.invoke(0.0, 1.0), df_dy(0.0, 1.0));
+        Assert.assertEquals((double) dff_dy_mh.invoke(PI_4, PI_4), df_dy(PI_4, PI_4));
+    }
+
+    @CodeReflection
+    static double f(double x, double y) {
+        return x * (-Math.sin(x * y) + y) * 4.0d;
+    }
+
+    static double df_dx(double x, double y) {
+        return (-Math.sin(x * y) + y - x * Math.cos(x * y) * y) * 4.0d;
+    }
+
+    static double df_dy(double x, double y) {
+        return x * (1 - Math.cos(x * y) * x) * 4.0d;
+    }
+
+    @Test
+    public void testControlFlow() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("fcf");
+        f.writeTo(System.out);
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        f.writeTo(System.out);
+
+        f = SSA.transform(f);
+        f.writeTo(System.out);
+
+        Assert.assertEquals(Interpreter.invoke(f, 2.0, 6), fcf(2.0, 6));
+        Assert.assertEquals(Interpreter.invoke(f, 2.0, 5), fcf(2.0, 5));
+        Assert.assertEquals(Interpreter.invoke(f, 2.0, 4), fcf(2.0, 4));
+
+        Block.Parameter x = f.body().entryBlock().parameters().get(0);
+
+        CoreOp.FuncOp df_dx = ForwardDifferentiation.partialDiff(f, x);
+        df_dx.writeTo(System.out);
+        MethodHandle df_dx_mh = generate(df_dx);
+
+        Assert.assertEquals((double) df_dx_mh.invoke(2.0, 6), dfcf_dx(2.0, 6));
+        Assert.assertEquals((double) df_dx_mh.invoke(2.0, 5), dfcf_dx(2.0, 5));
+        Assert.assertEquals((double) df_dx_mh.invoke(2.0, 4), dfcf_dx(2.0, 4));
+    }
+
+    @CodeReflection
+    static double fcf(/* independent */ double x, int y) {
+        /* dependent */
+        double o = 1.0;
+        for (int i = 0; i < y; i = i + 1) {
+            if (i > 1) {
+                if (i < 5) {
+                    o = o * x;
+                }
+            }
+        }
+        return o;
+    }
+
+    static double dfcf_dx(/* independent */ double x, int y) {
+        double d_o = 0.0;
+        double o = 1.0;
+        for (int i = 0; i < y; i = i + 1) {
+            if (i > 1) {
+                if (i < 5) {
+                    d_o = d_o * x + o * 1.0;
+                    o = o * x;
+                }
+            }
+        }
+        return d_o;
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        return BytecodeGenerator.generate(MethodHandles.lookup(), f);
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestForwardAutoDiff.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/jdk/internal/jimage/TEST.properties b/test/jdk/java/lang/reflect/code/bytecode/TEST.properties
--- a/test/jdk/jdk/internal/jimage/TEST.properties
+++ b/test/jdk/java/lang/reflect/code/bytecode/TEST.properties
diff a/test/jdk/java/lang/reflect/code/bytecode/TestArrayCreation.java b/test/jdk/java/lang/reflect/code/bytecode/TestArrayCreation.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestArrayCreation.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestArrayCreation
+ */
+
+public class TestArrayCreation {
+    @CodeReflection
+    public static String[] f() {
+        return new String[10];
+    }
+
+    @Test
+    public void testf() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals((String[]) mh.invoke(), f());
+    }
+
+    @CodeReflection
+    public static String[][] f2() {
+        return new String[10][];
+    }
+
+    @Test
+    public void testf2() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f2");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals((String[][]) mh.invoke(), f2());
+    }
+
+    @CodeReflection
+    public static String[][] f3() {
+        return new String[10][10];
+    }
+
+    @Test
+    public void testf3() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f3");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals((String[][]) mh.invoke(), f3());
+    }
+
+    @CodeReflection
+    public static String[][] f4() {
+        return new String[][]{{"one", "two"}, {"three"}};
+    }
+
+    @Test
+    public void testf4() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f4");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals((String[][]) mh.invoke(), f4());
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestArrayCreation.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestBranchCompactor.java b/test/jdk/java/lang/reflect/code/bytecode/TestBranchCompactor.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestBranchCompactor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.classfile.Attributes;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.Instruction;
+import java.lang.classfile.components.ClassPrinter;
+import static java.lang.classfile.Opcode.*;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.reflect.code.bytecode.BranchCompactor;
+import java.util.List;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestBranchCompactor
+ */
+public class TestBranchCompactor {
+
+    @Test
+    public void testBranchCompactor() {
+        var cc = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS);
+        var clm = cc.parse(cc.build(ClassDesc.of("c"), clb -> clb.withMethodBody("m", ConstantDescs.MTD_void, 0,
+                cb -> cb.transforming(new BranchCompactor(), cob -> {
+                    var l = cob.newLabel();
+                    cob.goto_(l) //compact
+                       .lineNumber(1)
+                       .labelBinding(l)
+                       .nop();
+
+                    l = cob.newLabel();
+                    cob.goto_w(l) //compact
+                       .lineNumber(2)
+                       .labelBinding(l);
+
+                    l = cob.newLabel();
+                    cob.goto_(l) //compact
+                       .labelBinding(l);
+
+                    cob.iconst_0();
+                    l = cob.newLabel();
+                    cob.ifeq(l) //do not compact
+                       .labelBinding(l);
+
+                    l = cob.newLabel();
+                    cob.goto_(l) //do not compact
+                       .nop()
+                       .labelBinding(l)
+                       .return_();
+                }))));
+        var code = clm.methods().get(0).code().get();
+        ClassPrinter.toYaml(code, ClassPrinter.Verbosity.TRACE_ALL, System.out::print);
+        Assert.assertEquals(
+                code.elementList().stream().mapMulti((e, ec) -> {if (e instanceof Instruction i) ec.accept(i.opcode());}).toList(),
+                List.of(NOP, ICONST_0, IFEQ, GOTO, NOP, RETURN));
+        Assert.assertEquals(code.findAttribute(Attributes.LINE_NUMBER_TABLE).get().lineNumbers().size(), 2);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java b/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.ClassModel;
+import java.lang.classfile.components.ClassPrinter;
+import java.lang.constant.MethodTypeDesc;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import org.testng.Assert;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.bytecode.BytecodeLift;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.runtime.CodeReflection;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng/othervm -Djdk.invoke.MethodHandle.dumpClassFiles=true TestBytecode
+ */
+
+public class TestBytecode {
+
+    @CodeReflection
+    static int intNumOps(int i, int j, int k) {
+        k++;
+        i = (i + j) / k - i % j;
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static byte byteNumOps(byte i, byte j, byte k) {
+        k++;
+        i = (byte) ((i + j) / k - i % j);
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static short shortNumOps(short i, short j, short k) {
+        k++;
+        i = (short) ((i + j) / k - i % j);
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static char charNumOps(char i, char j, char k) {
+        k++;
+        i = (char) ((i + j) / k - i % j);
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    static long longNumOps(long i, long j, long k) {
+        k++;
+        i = (i + j) / k - i % j;
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    static float floatNumOps(float i, float j, float k) {
+        k++;
+        i = (i + j) / k - i % j;
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    static double doubleNumOps(double i, double j, double k) {
+        k++;
+        i = (i + j) / k - i % j;
+        i--;
+        return i;
+    }
+
+    @CodeReflection
+    static int intBitOps(int i, int j, int k) {
+        return i & j | k ^ j;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static byte byteBitOps(byte i, byte j, byte k) {
+        return (byte) (i & j | k ^ j);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static short shortBitOps(short i, short j, short k) {
+        return (short) (i & j | k ^ j);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static char charBitOps(char i, char j, char k) {
+        return (char) (i & j | k ^ j);
+    }
+
+    @CodeReflection
+    static long longBitOps(long i, long j, long k) {
+        return i & j | k ^ j;
+    }
+
+    @CodeReflection
+    static boolean boolBitOps(boolean i, boolean j, boolean k) {
+        return i & j | k ^ j;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int intShiftOps(int i, int j, int k) {
+        return ((-1 >> i) << (j << k)) >>> (k - j);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static byte byteShiftOps(byte i, byte j, byte k) {
+        return (byte) (((-1 >> i) << (j << k)) >>> (k - j));
+    }
+
+    @CodeReflection
+    @SkipLift
+    static short shortShiftOps(short i, short j, short k) {
+        return (short) (((-1 >> i) << (j << k)) >>> (k - j));
+    }
+
+    @CodeReflection
+    @SkipLift
+    static char charShiftOps(char i, char j, char k) {
+        return (char) (((-1 >> i) << (j << k)) >>> (k - j));
+    }
+
+    @CodeReflection
+    @SkipLift
+    static long longShiftOps(long i, long j, long k) {
+        return ((-1 >> i) << (j << k)) >>> (k - j);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static Object[] boxingAndUnboxing(int i, byte b, short s, char c, Integer ii, Byte bb, Short ss, Character cc) {
+        ii += i; ii += b; ii += s; ii += c;
+        i += ii; i += bb; i += ss; i += cc;
+        b += ii; b += bb; b += ss; b += cc;
+        s += ii; s += bb; s += ss; s += cc;
+        c += ii; c += bb; c += ss; c += cc;
+        return new Object[]{i, b, s, c};
+    }
+
+    @CodeReflection
+    static String constructor(String s, int i, int j) {
+        return new String(s.getBytes(), i, j);
+    }
+
+    @CodeReflection
+    static Class<?> classArray(int i, int j) {
+        Class<?>[] ifaces = new Class[1 + i + j];
+        ifaces[0] = Function.class;
+        return ifaces[0];
+    }
+
+    @CodeReflection
+    static String[] stringArray(int i, int j) {
+        return new String[i];
+    }
+
+    @CodeReflection
+    static String[][] stringArray2(int i, int j) {
+        return new String[i][];
+    }
+
+    @CodeReflection
+    static String[][] stringArrayMulti(int i, int j) {
+        return new String[i][j];
+    }
+
+    @CodeReflection
+    static int[][] initializedIntArray(int i, int j) {
+        return new int[][]{{i, j}, {i + j}};
+    }
+
+    @CodeReflection
+    static int ifElseCompare(int i, int j) {
+        if (i < 3) {
+            i += 1;
+        } else {
+            j += 2;
+        }
+        return i + j;
+    }
+
+    @CodeReflection
+    static int ifElseEquality(int i, int j) {
+        if (j != 0) {
+            if (i != 0) {
+                i += 1;
+            } else {
+                i += 2;
+            }
+        } else {
+            if (j != 0) {
+                i += 3;
+            } else {
+                i += 4;
+            }
+        }
+        return i;
+    }
+
+    @CodeReflection
+    static int conditionalExpr(int i, int j) {
+        return ((i - 1 >= 0) ? i - 1 : j - 1);
+    }
+
+    @CodeReflection
+    static int nestedConditionalExpr(int i, int j) {
+        return (i < 2) ? (j < 3) ? i : j : i + j;
+    }
+
+    @CodeReflection
+    static int tryFinally(int i, int j) {
+        try {
+            i = i + j;
+        } finally {
+            i = i + j;
+        }
+        return i;
+    }
+
+    public record A(String s) {}
+
+    @CodeReflection
+    static A newWithArgs(int i, int j) {
+        return new A("hello world".substring(i, i + j));
+    }
+
+    @CodeReflection
+    static int loop(int n, int j) {
+        int sum = 0;
+        for (int i = 0; i < n; i++) {
+            sum = sum + j;
+        }
+        return sum;
+    }
+
+
+    @CodeReflection
+    static int ifElseNested(int a, int b) {
+        int c = a + b;
+        int d = 10 - a + b;
+        if (b < 3) {
+            if (a < 3) {
+                a += 1;
+            } else {
+                b += 2;
+            }
+            c += 3;
+        } else {
+            if (a > 2) {
+                a += 4;
+            } else {
+                b += 5;
+            }
+            d += 6;
+        }
+        return a + b + c + d;
+    }
+
+    @CodeReflection
+    static int nestedLoop(int m, int n) {
+        int sum = 0;
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                sum = sum + i + j;
+            }
+        }
+        return sum;
+    }
+
+    @CodeReflection
+    static int methodCall(int a, int b) {
+        int i = Math.max(a, b);
+        return Math.negateExact(i);
+    }
+
+    @CodeReflection
+    static int[] primitiveArray(int i, int j) {
+        int[] ia = new int[i + 1];
+        ia[0] = j;
+        return ia;
+    }
+
+    @CodeReflection
+    @SkipLift
+    static boolean not(boolean b) {
+        return !b;
+    }
+
+    @CodeReflection
+    static boolean notCompare(int i, int j) {
+        boolean b = i < j;
+        return !b;
+    }
+
+    @CodeReflection
+    static int mod(int i, int j) {
+        return i % (j + 1);
+    }
+
+    @CodeReflection
+    static int xor(int i, int j) {
+        return i ^ j;
+    }
+
+    @CodeReflection
+    static int whileLoop(int i, int n) { int
+        counter = 0;
+        while (i < n && counter < 3) {
+            counter++;
+            if (counter == 4) {
+                break;
+            }
+            i++;
+        }
+        return counter;
+    }
+
+    public interface Func {
+        int apply(int a);
+    }
+
+    public interface QuotableFunc extends Quotable {
+        int apply(int a);
+    }
+
+    static int consume(int i, Func f) {
+        return f.apply(i + 1);
+    }
+
+    static int consumeQuotable(int i, QuotableFunc f) {
+        Assert.assertNotNull(f.quoted());
+        Assert.assertNotNull(f.quoted().op());
+        Assert.assertTrue(f.quoted().op() instanceof CoreOp.LambdaOp);
+        return f.apply(i + 1);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int lambda(int i) {
+        return consume(i, a -> -a);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int quotableLambda(int i) {
+        return consumeQuotable(i, a -> -a);
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int lambdaWithCapture(int i, String s) {
+        return consume(i, a -> a + s.length());
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int quotableLambdaWithCapture(int i, String s) {
+        return consumeQuotable(i, a -> a + s.length());
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int nestedLambdasWithCaptures(int i, int j, String s) {
+        return consume(i, a -> consume(a, b -> a + b + j) + s.length());
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int nestedQuotableLambdasWithCaptures(int i, int j, String s) {
+        return consumeQuotable(i, a -> consumeQuotable(a, b -> a + b + j) + s.length());
+    }
+
+    @CodeReflection
+    @SkipLift
+    static int methodHandle(int i) {
+        return consume(i, Math::negateExact);
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface SkipLift {}
+
+    record TestData(Method testMethod) {
+        @Override
+        public String toString() {
+            String s = testMethod.getName() + Arrays.stream(testMethod.getParameterTypes())
+                    .map(Class::getSimpleName).collect(Collectors.joining(",", "(", ")"));
+            if (s.length() > 30) s = s.substring(0, 27) + "...";
+            return s;
+        }
+    }
+
+    @DataProvider(name = "testMethods")
+    public static TestData[]testMethods() {
+        return Stream.of(TestBytecode.class.getDeclaredMethods())
+                .filter(m -> m.isAnnotationPresent(CodeReflection.class))
+                .map(TestData::new).toArray(TestData[]::new);
+    }
+
+    private static byte[] CLASS_DATA;
+    private static ClassModel CLASS_MODEL;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        CLASS_DATA = TestBytecode.class.getResourceAsStream("TestBytecode.class").readAllBytes();
+        CLASS_MODEL = ClassFile.of().parse(CLASS_DATA);
+    }
+
+    private static MethodTypeDesc toMethodTypeDesc(Method m) {
+        return MethodTypeDesc.of(
+                m.getReturnType().describeConstable().orElseThrow(),
+                Arrays.stream(m.getParameterTypes())
+                        .map(cls -> cls.describeConstable().orElseThrow()).toList());
+    }
+
+
+    private static final Map<Class<?>, Object[]> TEST_ARGS = new IdentityHashMap<>();
+    private static Object[] values(Object... values) {
+        return values;
+    }
+    private static void initTestArgs(Object[] values, Class<?>... argTypes) {
+        for (var argType : argTypes) TEST_ARGS.put(argType, values);
+    }
+    static {
+        initTestArgs(values(1, 2, 3, 4), int.class, Integer.class);
+        initTestArgs(values((byte)1, (byte)2, (byte)3, (byte)4), byte.class, Byte.class);
+        initTestArgs(values((short)1, (short)2, (short)3, (short)4), short.class, Short.class);
+        initTestArgs(values((char)1, (char)2, (char)3, (char)4), char.class, Character.class);
+        initTestArgs(values(false, true), boolean.class, Boolean.class);
+        initTestArgs(values("Hello World"), String.class);
+        initTestArgs(values(1l, 2l, 3l, 4l), long.class, Long.class);
+        initTestArgs(values(1f, 2f, 3f, 4f), float.class, Float.class);
+        initTestArgs(values(1d, 2d, 3d, 4d), double.class, Double.class);
+    }
+
+    interface Executor {
+        void execute(Object[] args) throws Throwable;
+    }
+
+    private static void permutateAllArgs(Class<?>[] argTypes, Executor executor) throws Throwable {
+        final int argn = argTypes.length;
+        Object[][] argValues = new Object[argn][];
+        for (int i = 0; i < argn; i++) {
+            argValues[i] = TEST_ARGS.get(argTypes[i]);
+        }
+        int[] argIndexes = new int[argn];
+        Object[] args = new Object[argn];
+        while (true) {
+            for (int i = 0; i < argn; i++) {
+                args[i] = argValues[i][argIndexes[i]];
+            }
+            executor.execute(args);
+            int i = argn - 1;
+            while (i >= 0 && argIndexes[i] == argValues[i].length - 1) i--;
+            if (i < 0) return;
+            argIndexes[i++]++;
+            while (i < argn) argIndexes[i++] = 0;
+        }
+    }
+
+    @Test(dataProvider = "testMethods")
+    public void testLift(TestData d) throws Throwable {
+        if (d.testMethod.getAnnotation(SkipLift.class) != null) {
+            throw new SkipException("skipped");
+        }
+        CoreOp.FuncOp flift;
+        try {
+            flift = BytecodeLift.lift(CLASS_DATA, d.testMethod.getName(), toMethodTypeDesc(d.testMethod));
+        } catch (Throwable e) {
+            System.out.println("Lift failed, expected:");
+            d.testMethod.getCodeModel().ifPresent(f -> f.writeTo(System.out));
+            throw e;
+        }
+        try {
+            permutateAllArgs(d.testMethod.getParameterTypes(), args ->
+                Assert.assertEquals(invokeAndConvert(flift, args), d.testMethod.invoke(null, args)));
+        } catch (Throwable e) {
+            flift.writeTo(System.out);
+            throw e;
+        }
+    }
+
+    private static Object invokeAndConvert(CoreOp.FuncOp func, Object[] args) {
+        Object ret = Interpreter.invoke(func, args);
+        if (ret instanceof Integer i) {
+            TypeElement rt = func.invokableType().returnType();
+            if (rt.equals(JavaType.BOOLEAN)) {
+                return i != 0;
+            } else if (rt.equals(JavaType.BYTE)) {
+                return i.byteValue();
+            } else if (rt.equals(JavaType.CHAR)) {
+                return (short)i.intValue();
+            } else if (rt.equals(JavaType.SHORT)) {
+                return i.shortValue();
+            }
+        }
+        return ret;
+    }
+
+    @Test(dataProvider = "testMethods")
+    public void testGenerate(TestData d) throws Throwable {
+        CoreOp.FuncOp func = d.testMethod.getCodeModel().get();
+
+        CoreOp.FuncOp lfunc = func.transform(CopyContext.create(), OpTransformer.LOWERING_TRANSFORMER);
+
+        try {
+            MethodHandle mh = BytecodeGenerator.generate(MethodHandles.lookup(), lfunc);
+            permutateAllArgs(d.testMethod.getParameterTypes(), args ->
+                    Assert.assertEquals(mh.invokeWithArguments(args), d.testMethod.invoke(null, args)));
+        } catch (Throwable e) {
+            func.writeTo(System.out);
+            lfunc.writeTo(System.out);
+            String methodName = d.testMethod().getName();
+            for (var mm : CLASS_MODEL.methods()) {
+                if (mm.methodName().equalsString(methodName)
+                        || mm.methodName().stringValue().startsWith("lambda$" + methodName + "$")) {
+                    ClassPrinter.toYaml(mm,
+                                        ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES,
+                                        System.out::print);
+                }
+            }
+            Files.list(Path.of("DUMP_CLASS_FILES")).forEach(p -> {
+                if (p.getFileName().toString().matches(methodName + "\\..+\\.class")) try {
+                    ClassPrinter.toYaml(ClassFile.of().parse(p),
+                                        ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES,
+                                        System.out::print);
+                } catch (IOException ignore) {}
+            });
+            throw e;
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestLiftCustomBytecode.java b/test/jdk/java/lang/reflect/code/bytecode/TestLiftCustomBytecode.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestLiftCustomBytecode.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.Label;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.constant.MethodTypeDesc;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.bytecode.BytecodeLift;
+import java.lang.reflect.code.interpreter.Interpreter;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestLiftCustomBytecode
+ */
+
+public class TestLiftCustomBytecode {
+
+    @Test
+    public void testBackJumps() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp(ClassFile.of().build(ClassDesc.of("BackJumps"), clb ->
+                clb.withMethodBody("backJumps", MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int), ClassFile.ACC_STATIC, cob -> {
+                    Label l1 = cob.newLabel();
+                    Label l2 = cob.newLabel();
+                    Label l3 = cob.newLabel();
+                    Label l4 = cob.newLabel();
+                    // Code wrapped in back jumps requires multiple passes and block skipping
+                    cob.goto_(l1)
+                       .labelBinding(l2)
+                       .goto_(l3)
+                       .labelBinding(l4)
+                       .iload(0)
+                       .ireturn()
+                       .labelBinding(l1)
+                       .goto_(l2)
+                       .labelBinding(l3)
+                       .goto_(l4);
+                })), "backJumps");
+
+        Assert.assertEquals((int) Interpreter.invoke(f, 42), 42);
+    }
+
+    static CoreOp.FuncOp getFuncOp(byte[] classdata, String method) {
+        CoreOp.FuncOp flift = BytecodeLift.lift(classdata, method);
+        flift.writeTo(System.out);
+        return flift;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestLiftExample.java b/test/jdk/java/lang/reflect/code/bytecode/TestLiftExample.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestLiftExample.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.bytecode.BytecodeLift;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.util.function.Function;
+
+/*
+ * @test
+ * @run testng TestLiftExample
+ */
+
+public class TestLiftExample {
+
+    public static <T, R> Function<T, R> proxy(Function<T, R> f) {
+        @SuppressWarnings("unchecked")
+        Function<T, R> pf = (Function<T, R>) Proxy.newProxyInstance(
+                TestLiftExample.class.getClassLoader(),
+                new Class[]{Function.class},
+                // @@@ Change to lambda
+                handler(f));
+        return pf;
+    }
+
+    static InvocationHandler handler(Function<?, ?> f) {
+        return (proxy, method, args) -> {
+            if (method.getName().equals("apply")) {
+                int r = (int) method.invoke(f, args);
+                return r + 1;
+            } else {
+                return method.invoke(f, args);
+            }
+        };
+    }
+
+    @Test
+    public void testF() throws Throwable {
+        URL resource = TestLiftExample.class.getClassLoader().getResource(TestLiftExample.class.getName().replace('.', '/') + ".class");
+        byte[] classdata = resource.openStream().readAllBytes();
+        CoreOp.FuncOp flift = BytecodeLift.lift(classdata, "proxy");
+        flift.writeTo(System.out);
+
+        Function<Integer, Integer> f = i -> i;
+        @SuppressWarnings("unchecked")
+        Function<Integer, Integer> pf = (Function<Integer, Integer>) Interpreter.invoke(MethodHandles.lookup(),
+                flift, f);
+
+        Assert.assertEquals((int) pf.apply(1), 2);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestQuoted.java b/test/jdk/java/lang/reflect/code/bytecode/TestQuoted.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestQuoted.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+
+/*
+ * @test
+ * @run testng TestQuoted
+ */
+
+public class TestQuoted {
+
+    @Test
+    public void testQuoted() throws Throwable {
+        Quoted q = (int i, int j) -> {
+            i = i + j;
+            return i;
+        };
+        CoreOp.ClosureOp cop = (CoreOp.ClosureOp) q.op();
+
+        MethodHandle mh = generate(cop);
+
+        Assert.assertEquals(3, (int) mh.invoke(1, 2));
+    }
+
+    static <O extends Op & Op.Invokable> MethodHandle generate(O f) {
+        f.writeTo(System.out);
+
+        @SuppressWarnings("unchecked")
+        O lf = (O) f.transform(CopyContext.create(), OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestSlots.java b/test/jdk/java/lang/reflect/code/bytecode/TestSlots.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestSlots.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestSlots
+ */
+
+public class TestSlots {
+    @CodeReflection
+    static double f(double i, double j) {
+        i = i + j;
+
+        double k = 4.0;
+        k += i;
+        return k;
+    }
+
+    @Test
+    public void testF() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals(f(1.0d, 2.0d), (double) mh.invoke(1.0d, 2.0d));
+    }
+
+    @CodeReflection
+    static double f2(double x, double y) {
+        return x * (-Math.sin(x * y) + y) * 4.0d;
+    }
+
+    @Test
+    public void testF2() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f2");
+
+        MethodHandle mh = generate(f);
+
+        Assert.assertEquals(f2(1.0d, 2.0d), (double) mh.invoke(1.0d, 2.0d));
+    }
+
+    @CodeReflection
+    static double f3(/* independent */ double x, int y) {
+        /* dependent */
+        double o = 1.0;
+        for (int i = 0; i < y; i = i + 1) {
+            if (i > 1) {
+                if (i < 5) {
+                    o = o * x;
+                }
+            }
+        }
+        return o;
+    }
+
+    @Test
+    public void testF3() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f3");
+
+        MethodHandle mh = generate(f);
+
+        for (int i = 0; i < 7; i++) {
+            Assert.assertEquals(f3(2.0d, i), (double) mh.invoke(2.0d, i));
+        }
+    }
+
+    @CodeReflection
+    static int f4(/* Unused */ int a, int b) {
+        return b;
+    }
+
+    @Test
+    public void testF4() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f4");
+
+        MethodHandle mh;
+        try {
+            mh = generate(f);
+        } catch (VerifyError e) {
+            Assert.fail("invalid class file generated", e);
+            return;
+        }
+
+        Assert.assertEquals(f4(1, 2), (int) mh.invoke(1, 2));
+    }
+
+    @CodeReflection
+    static double f5(/* Unused */ double a, double b) {
+        return b;
+    }
+
+    @Test
+    public void testF5() throws Throwable {
+        CoreOp.FuncOp f = getFuncOp("f5");
+
+        MethodHandle mh;
+        try {
+            mh = generate(f);
+        } catch (VerifyError e) {
+            Assert.fail("invalid class file generated", e);
+            return;
+        }
+
+        Assert.assertEquals(f5(1.0, 2.0), (double) mh.invoke(1.0, 2.0));
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestSlots.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestSmallCorpus.java b/test/jdk/java/lang/reflect/code/bytecode/TestSmallCorpus.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestSmallCorpus.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.Instruction;
+import java.lang.classfile.Label;
+import java.lang.classfile.MethodModel;
+import java.lang.classfile.Opcode;
+import java.lang.classfile.instruction.*;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.AccessFlag;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.bytecode.BytecodeLift;
+import java.lang.reflect.code.op.CoreOp;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.testng.Assert;
+import org.testng.annotations.Ignore;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestSmallCorpus
+ */
+public class TestSmallCorpus {
+
+    private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/"));
+    private static final ClassFile CF = ClassFile.of(ClassFile.DebugElementsOption.DROP_DEBUG,
+                                                     ClassFile.LineNumbersOption.DROP_LINE_NUMBERS);
+    private static final int COLUMN_WIDTH = 150;
+
+    private int passed, notMatching;
+    private Map<String, Map<String, Integer>> errorStats;
+
+    @Ignore
+    @Test
+    public void testDoubleRoundtripStability() throws Exception {
+        passed = 0;
+        notMatching = 0;
+        errorStats = new LinkedHashMap<>();
+        for (Path p : Files.walk(JRT.getPath("modules/java.base/java"))
+                .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class"))
+                .toList()) {
+            testDoubleRoundtripStability(p);
+        }
+
+        for (var stats : errorStats.entrySet()) {
+            System.out.println(String.format("""
+
+            %s errors:
+            -----------------------------------------------------
+            """, stats.getKey()));
+            stats.getValue().entrySet().stream().sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())).forEach(e -> System.out.println(e.getValue() +"x " + e.getKey() + "\n"));
+        }
+
+        // @@@ There is still several failing cases and a lot of errors
+        Assert.assertTrue(notMatching < 31 && passed > 5400, String.format("""
+
+                    passed: %d
+                    not matching: %d
+                    %s
+                """,
+                passed,
+                notMatching,
+                errorStats.entrySet().stream().map(e -> e.getKey() +
+                        " errors: "
+                        + e.getValue().values().stream().mapToInt(Integer::intValue).sum()).collect(Collectors.joining("\n    "))
+                ));
+    }
+
+    private void testDoubleRoundtripStability(Path path) throws Exception {
+        var clm = CF.parse(path);
+        for (var originalModel : clm.methods()) {
+            if (originalModel.flags().has(AccessFlag.STATIC) && originalModel.code().isPresent()) try {
+                CoreOp.FuncOp firstLift = lift(originalModel);
+                try {
+                    CoreOp.FuncOp firstTransform = transform(firstLift);
+                    try {
+                        MethodModel firstModel = lower(firstTransform);
+                        try {
+                            CoreOp.FuncOp secondLift = lift(firstModel);
+                            try {
+                                CoreOp.FuncOp secondTransform = transform(secondLift);
+                                try {
+                                    MethodModel secondModel = lower(secondTransform);
+
+                                    // testing only methods passing through
+                                    var firstNormalized = normalize(firstModel);
+                                    var secondNormalized = normalize(secondModel);
+                                    if (!firstNormalized.equals(secondNormalized)) {
+                                        notMatching++;
+                                        System.out.println(clm.thisClass().asInternalName() + "::" + originalModel.methodName().stringValue() + originalModel.methodTypeSymbol().displayDescriptor());
+                                        printInColumns(firstLift, secondLift);
+                                        printInColumns(firstTransform, secondTransform);
+                                        printInColumns(firstNormalized, secondNormalized);
+                                        System.out.println();
+                                    } else {
+                                        passed++;
+                                    }
+                                } catch (Exception e) {
+                                    error("second lower", e);
+                                }
+                            } catch (Exception e) {
+                                error("second transform", e);
+                            }
+                        } catch (Exception e) {
+                            error("second lift", e);
+                        }
+                    } catch (Exception e) {
+                        error("first lower", e);
+                    }
+                } catch (Exception e) {
+                    error("first transform", e);
+                }
+            } catch (Exception e) {
+                error("first lift", e);
+            }
+        }
+    }
+    private static void printInColumns(CoreOp.FuncOp first, CoreOp.FuncOp second) {
+        StringWriter fw = new StringWriter();
+        first.writeTo(fw);
+        StringWriter sw = new StringWriter();
+        second.writeTo(sw);
+        printInColumns(fw.toString().lines().toList(), sw.toString().lines().toList());
+    }
+
+    private static void printInColumns(List<String> first, List<String> second) {
+        System.out.println("-".repeat(COLUMN_WIDTH ) + "--+-" + "-".repeat(COLUMN_WIDTH ));
+        for (int i = 0; i < first.size() || i < second.size(); i++) {
+            String f = i < first.size() ? first.get(i) : "";
+            String s = i < second.size() ? second.get(i) : "";
+            System.out.println(" " + f + (f.length() < COLUMN_WIDTH ? " ".repeat(COLUMN_WIDTH - f.length()) : "") + (f.equals(s) ? " | " : " x ") + s);
+        }
+    }
+
+    private static CoreOp.FuncOp lift(MethodModel mm) {
+        return BytecodeLift.lift(mm);
+    }
+
+    private static CoreOp.FuncOp transform(CoreOp.FuncOp func) {
+        return SSA.transform(func.transform(OpTransformer.LOWERING_TRANSFORMER));
+    }
+
+    private static MethodModel lower(CoreOp.FuncOp func) {
+        return CF.parse(BytecodeGenerator.generateClassData(
+                MethodHandles.lookup(),
+                func)).methods().get(0);
+    }
+
+
+    public static List<String> normalize(MethodModel mm) {
+        record El(int index, String format, Label... targets) {
+            public El(int index, Instruction i, Object format, Label... targets) {
+                this(index, trim(i.opcode()) + " " + format, targets);
+            }
+            public String toString(Map<Label, Integer> targetsMap) {
+                return "%3d: ".formatted(index) + (targets.length == 0 ? format : format.formatted(Stream.of(targets).map(l -> targetsMap.get(l)).toArray()));
+            }
+        }
+
+        Map<Label, Integer> targetsMap = new HashMap<>();
+        List<El> elements = new ArrayList<>();
+        Label lastLabel = null;
+        int i = 0;
+        for (var e : mm.code().orElseThrow()) {
+            var er = switch (e) {
+                case LabelTarget lt -> {
+                    lastLabel = lt.label();
+                    yield null;
+                }
+                case ExceptionCatch ec ->
+                    new El(i++, "ExceptionCatch start: @%d end: @%d handler: @%d" + ec.catchType().map(ct -> " catch type: " + ct.asInternalName()).orElse(""), ec.tryStart(), ec.tryEnd(), ec.handler());
+                case BranchInstruction ins ->
+                    new El(i++, ins, "@%d", ins.target());
+                case ConstantInstruction ins ->
+                    new El(i++, "LDC " + ins.constantValue());
+                case FieldInstruction ins ->
+                    new El(i++, ins, ins.owner().asInternalName() + "." + ins.name().stringValue());
+                case InvokeDynamicInstruction ins ->
+                    new El(i++, ins, ins.name().stringValue() + ins.typeSymbol() + " " + ins.bootstrapMethod() + "(" + ins.bootstrapArgs() + ")");
+                case InvokeInstruction ins ->
+                    new El(i++, ins, ins.owner().asInternalName() + "::" + ins.name().stringValue() + ins.typeSymbol().displayDescriptor());
+                case LoadInstruction ins ->
+                    new El(i++, ins, "#" + ins.slot());
+                case StoreInstruction ins ->
+                    new El(i++, ins, "#" + ins.slot());
+                case IncrementInstruction ins ->
+                    new El(i++, ins, "#" + ins.slot() + " " + ins.constant());
+                case LookupSwitchInstruction ins ->
+                    new El(i++, ins, "default: @%d" + ins.cases().stream().map(c -> ", " + c.caseValue() + ": @%d").collect(Collectors.joining()),
+                            Stream.concat(Stream.of(ins.defaultTarget()), ins.cases().stream().map(SwitchCase::target)).toArray(Label[]::new));
+                case NewMultiArrayInstruction ins ->
+                    new El(i++, ins, ins.arrayType().asInternalName() + "(" + ins.dimensions() + ")");
+                case NewObjectInstruction ins ->
+                    new El(i++, ins, ins.className().asInternalName());
+                case NewPrimitiveArrayInstruction ins ->
+                    new El(i++, ins, ins.typeKind());
+                case NewReferenceArrayInstruction ins ->
+                    new El(i++, ins, ins.componentType().asInternalName());
+                case TableSwitchInstruction ins ->
+                    new El(i++, ins, "default: @%d" + ins.cases().stream().map(c -> ", " + c.caseValue() + ": @%d").collect(Collectors.joining()),
+                            Stream.concat(Stream.of(ins.defaultTarget()), ins.cases().stream().map(SwitchCase::target)).toArray(Label[]::new));
+                case TypeCheckInstruction ins ->
+                    new El(i++, ins, ins.type().asInternalName());
+                case Instruction ins ->
+                    new El(i++, ins, "");
+                default -> null;
+            };
+            if (er != null) {
+                if (lastLabel != null) {
+                    targetsMap.put(lastLabel, elements.size());
+                    lastLabel = null;
+                }
+                elements.add(er);
+            }
+        }
+        return elements.stream().map(el -> el.toString(targetsMap)).toList();
+    }
+
+    private static String trim(Opcode opcode) {
+        var name = opcode.toString();
+        int i = name.indexOf('_');
+        return i > 2 ? name.substring(0, i) : name;
+    }
+
+    private void error(String category, Exception e) {
+        StringWriter sw = new StringWriter();
+        e.printStackTrace(new PrintWriter(sw));
+        errorStats.computeIfAbsent(category, _ -> new HashMap<>())
+                  .compute(sw.toString(), (_, i) -> i == null ? 1 : i + 1);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestTry.java b/test/jdk/java/lang/reflect/code/bytecode/TestTry.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestTry.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestTry
+ */
+
+public class TestTry {
+
+    @CodeReflection
+    public static void catching(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            consume(e);
+            c.accept(1);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            consume(e);
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatching() {
+        CoreOp.FuncOp f = getFuncOp("catching");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTry::catching);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new NullPointerException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+    @CodeReflection
+    public static void catchThrowable(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            consume(e);
+            c.accept(1);
+            c.accept(-1);
+        } catch (Throwable e) {
+            consume(e);
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchThrowable() {
+        CoreOp.FuncOp f = getFuncOp("catchThrowable");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTry::catchThrowable);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalStateException();
+        });
+    }
+
+
+    @CodeReflection
+    public static void catchNested(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+            try {
+                c.accept(1);
+                c.accept(-1);
+            } catch (IllegalStateException e) {
+                consume(e);
+                c.accept(2);
+                c.accept(-1);
+            }
+            c.accept(3);
+            c.accept(-1);
+        } catch (IllegalArgumentException e) {
+            consume(e);
+            c.accept(4);
+            c.accept(-1);
+        }
+        c.accept(5);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchNested() {
+        CoreOp.FuncOp f = getFuncOp("catchNested");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTry::catchNested);
+
+        test.accept(i -> {
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 1) throw new IllegalStateException();
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new IllegalArgumentException();
+            if (i == 4) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 5) throw new RuntimeException();
+        });
+    }
+
+
+    static void consume(Throwable e) {
+    }
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+
+    static <T> Consumer<T> asConsumer(MethodHandle mh) {
+        return c -> {
+            try {
+                mh.invoke(c);
+            } catch (Throwable e) {
+                throw erase(e);
+            }
+        };
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> E erase(Throwable e) throws E {
+        return (E) e;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTry.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestTryFinally.java b/test/jdk/java/lang/reflect/code/bytecode/TestTryFinally.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestTryFinally.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestTryFinally
+ */
+
+public class TestTryFinally {
+
+    @CodeReflection
+    public static void tryCatchFinally(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchFinally() {
+        CoreOp.FuncOp f = getFuncOp("tryCatchFinally");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinally::tryCatchFinally
+        );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void tryReturn(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+            return;
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testTryReturn() {
+        CoreOp.FuncOp f = getFuncOp("tryReturn");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinally::tryReturn
+                );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void catchThrow(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+            throw e;
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+        }
+        c.accept(3);
+        c.accept(-1);
+    }
+
+    @Test
+    public void testCatchThrow() {
+        CoreOp.FuncOp f = getFuncOp("catchThrow");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinally::catchThrow
+        );
+
+        test(test);
+    }
+
+
+    @CodeReflection
+    public static void finallyReturn(IntConsumer c) {
+        try {
+            c.accept(0);
+            c.accept(-1);
+        } catch (IllegalStateException e) {
+            c.accept(1);
+            c.accept(-1);
+        } finally {
+            c.accept(2);
+            c.accept(-1);
+            return;
+        }
+    }
+
+    @Test
+    public void finallyReturn() {
+        CoreOp.FuncOp f = getFuncOp("finallyReturn");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinally::finallyReturn
+        );
+
+        test(test);
+    }
+
+
+    static void test(Consumer<IntConsumer> test) {
+        test.accept(i -> {});
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 2) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 0) throw new IllegalStateException();
+            if (i == 1) throw new RuntimeException();
+        });
+        test.accept(i -> {
+            if (i == 3) throw new RuntimeException();
+        });
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+
+    static <T> Consumer<T> asConsumer(MethodHandle mh) {
+        return c -> {
+            try {
+                mh.invoke(c);
+            } catch (Throwable e) {
+                throw erase(e);
+            }
+        };
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> E erase(Throwable e) throws E {
+        return (E) e;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTryFinally.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+}
diff a/test/jdk/java/lang/reflect/code/bytecode/TestTryFinallyNested.java b/test/jdk/java/lang/reflect/code/bytecode/TestTryFinallyNested.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/bytecode/TestTryFinallyNested.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.bytecode.BytecodeGenerator;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestTryFinallyNested
+ */
+
+public class TestTryFinallyNested {
+    @CodeReflection
+    public static void tryCatchFinally(IntConsumer c, int i) {
+        try {
+            try {
+                if (i == 0) {
+                    return;
+                }
+                c.accept(0);
+            } catch (IllegalStateException e) {
+                if (i == 1) {
+                    return;
+                }
+                c.accept(1);
+            } finally {
+                if (i == 2) {
+                    return;
+                }
+                c.accept(2);
+            }
+            if (i == 3) {
+                return;
+            }
+            c.accept(3);
+        } catch (IllegalStateException e) {
+            if (i == 4) {
+                return;
+            }
+            c.accept(4);
+        } finally {
+            if (i == 5) {
+                return;
+            }
+            c.accept(5);
+        }
+        c.accept(6);
+    }
+
+    @Test
+    public void testCatchFinally() {
+        CoreOp.FuncOp f = getFuncOp("tryCatchFinally");
+
+        MethodHandle mh = generate(f);
+
+        for (int ra = -1; ra < 6; ra++) {
+            int fra = ra;
+
+            Consumer<IntConsumer> test = testConsumer(
+                    c -> invoke(mh, c, fra),
+                    c -> tryCatchFinally(c, fra)
+            );
+
+            test.accept(i -> {});
+            for (int ea = 0; ea < 6; ea++) {
+                int fea = ea;
+                test.accept(i -> {
+                    if (i == fea) throw new IllegalStateException();
+                });
+                test.accept(i -> {
+                    if (i == fea) throw new RuntimeException();
+                });
+            }
+        }
+    }
+
+    @CodeReflection
+    public static void tryForLoop(IntConsumer c) {
+        for (int i = 0; i < 8; i++) {
+            c.accept(0);
+            try {
+                if (i == 4) {
+                    continue;
+                } else if (i == 5) {
+                    break;
+                }
+                c.accept(1);
+            } finally {
+                c.accept(2);
+            }
+            c.accept(3);
+        }
+        c.accept(4);
+    }
+
+    @Test
+    public void testTryForLoop() {
+        CoreOp.FuncOp f = getFuncOp("tryForLoop");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinallyNested::tryForLoop
+        );
+
+        test.accept(i -> { });
+    }
+
+
+    @CodeReflection
+    public static void tryLabeledForLoop(IntConsumer c) {
+        a: for (int i = 0; i < 8; i++) {
+            c.accept(0);
+            b: {
+                try {
+                    if (i == 4) {
+                        continue a;
+                    } else if (i == 5) {
+                        break b;
+                    } else if (i == 6) {
+                        break a;
+                    }
+                    c.accept(1);
+                } finally {
+                    c.accept(2);
+                }
+                c.accept(3);
+            }
+            c.accept(4);
+        }
+        c.accept(5);
+    }
+
+    @Test
+    public void testTryLabeledForLoop() {
+        CoreOp.FuncOp f = getFuncOp("tryLabeledForLoop");
+
+        MethodHandle mh = generate(f);
+
+        Consumer<IntConsumer> test = testConsumer(
+                asConsumer(mh),
+                TestTryFinallyNested::tryLabeledForLoop
+        );
+
+        test.accept(i -> { });
+    }
+
+
+    static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
+        return c -> {
+            List<Integer> actual = new ArrayList<>();
+            IntConsumer actualC = actual::add;
+            Throwable actualT = null;
+            try {
+                actualR.accept(actualC.andThen(c));
+            } catch (Interpreter.InterpreterException e) {
+                throw e;
+            } catch (Throwable t) {
+                actualT = t;
+                if (t instanceof AssertionError) {
+                    t.printStackTrace();
+                }
+            }
+
+            List<Integer> expected = new ArrayList<>();
+            IntConsumer expectedC = expected::add;
+            Throwable expectedT = null;
+            try {
+                expectedR.accept(expectedC.andThen(c));
+            } catch (Throwable t) {
+                expectedT = t;
+            }
+
+            Assert.assertEquals(
+                    actualT != null ? actualT.getClass() : null,
+                    expectedT != null ? expectedT.getClass() : null);
+            Assert.assertEquals(actual, expected);
+        };
+    }
+
+    static MethodHandle generate(CoreOp.FuncOp f) {
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        return BytecodeGenerator.generate(MethodHandles.lookup(), lf);
+    }
+
+    static <T> Consumer<T> asConsumer(MethodHandle mh) {
+        return c -> {
+            try {
+                mh.invoke(c);
+            } catch (Throwable e) {
+                throw erase(e);
+            }
+        };
+    }
+
+    static void invoke(MethodHandle mh, IntConsumer c, int i) {
+        try {
+            mh.invoke(c, i);
+        } catch (Throwable e) {
+            throw erase(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> E erase(Throwable e) throws E {
+        return (E) e;
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestTryFinallyNested.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/expression/ExpressionElimination.java b/test/jdk/java/lang/reflect/code/expression/ExpressionElimination.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/expression/ExpressionElimination.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.util.HashMap;
+import java.util.function.BiConsumer;
+
+import static java.lang.reflect.code.op.CoreOp.sub;
+import static java.lang.reflect.code.analysis.Patterns.*;
+
+public final class ExpressionElimination {
+    private ExpressionElimination() {
+    }
+
+    static OpPattern negP(Pattern operand) {
+        return opP(CoreOp.NegOp.class, operand);
+    }
+
+    static OpPattern addP(Pattern lhs, Pattern rhs) {
+        return opP(CoreOp.AddOp.class, lhs, rhs);
+    }
+
+    static OpPattern mulP(Pattern lhs, Pattern rhs) {
+        return opP(CoreOp.MulOp.class, lhs, rhs);
+    }
+
+    public static <T extends Op> T eliminate(T f) {
+        // Note expression elimination and other forms of analysis is simplified if first of all expressions
+        // are normalized e.g. when they have an operand that is a constant expression
+        // and the operation is associative such as add(0, x) -> add(x, 0)
+
+        var actions = multiMatch(new HashMap<Op.Result, BiConsumer<Block.Builder, Op>>(), f)
+                .pattern(mulP(_P(), valueP(constantP(0.0d))))
+                .pattern(mulP(valueP(constantP(0.0d)), _P()))
+                .pattern(addP(valueP(), constantP(0.0d)))
+                .pattern(addP(constantP(0.0d), valueP()))
+                .pattern(mulP(constantP(1.0d), valueP()))
+                .pattern(mulP(valueP(), constantP(1.0d)))
+                .target((ms, as) -> {
+                    Value a = ms.matchedOperands().get(0);
+                    as.put(ms.op().result(), (block, op) -> {
+                        CopyContext cc = block.context();
+                        cc.mapValue(ms.op().result(), cc.getValue(a));
+                    });
+                    return as;
+                })
+                // add(neg(x), y) -> sub(y, x)
+                .pattern(addP(negP(valueP()), valueP()))
+                .target((ms, as) -> {
+                    Value x = ms.matchedOperands().get(0);
+                    Value y = ms.matchedOperands().get(1);
+
+                    as.put(ms.op().result(), (block, op) -> {
+                        CopyContext cc = block.context();
+                        Op.Result r = block.op(sub(cc.getValue(y), cc.getValue(x)));
+                        cc.mapValue(ms.op().result(), r);
+                    });
+                    return as;
+                })
+                .matchThenApply();
+
+        // Eliminate
+        Op ef = f.transform(CopyContext.create(), (block, op) -> {
+            BiConsumer<Block.Builder, Op> a = actions.get(op.result());
+            if (a != null) {
+                a.accept(block, op);
+            } else {
+                block.op(op);
+            }
+            return block;
+        });
+
+        // Remove dead ops
+        ef = ef.transform(CopyContext.create(), (block, op) -> {
+            if (!(op instanceof Op.Pure) ||
+                    !op.result().uses().isEmpty()) {
+                block.op(op);
+            }
+            return block;
+        });
+
+        @SuppressWarnings("unchecked")
+        T t = (T) ef;
+        return t;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/expression/TestExpressionElimination.java b/test/jdk/java/lang/reflect/code/expression/TestExpressionElimination.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/expression/TestExpressionElimination.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.CopyContext;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.interpreter.Interpreter;
+
+/*
+ * @test
+ * @run testng TestExpressionElimination
+ */
+
+public class TestExpressionElimination {
+
+    @Test
+    public void testAddZero() {
+        CoreOp.ClosureOp lf = generate((double a) -> a + 0.0);
+
+        Assert.assertEquals((double) Interpreter.invoke(lf, 1.0d), 1.0d);
+    }
+
+    @Test
+    public void testF() {
+        CoreOp.ClosureOp lf = generate((double a, double b) -> -a + b);
+
+        Assert.assertEquals((double) Interpreter.invoke(lf, 1.0d, 1.0d), 0.0d);
+    }
+
+    static CoreOp.ClosureOp generate(Quoted q) {
+        return generateF((CoreOp.ClosureOp) q.op());
+    }
+
+    static <T extends Op & Op.Invokable> T generateF(T f) {
+        f.writeTo(System.out);
+
+        @SuppressWarnings("unchecked")
+        T lf = (T) f.transform(CopyContext.create(), OpTransformer.LOWERING_TRANSFORMER);
+        lf.writeTo(System.out);
+
+        lf = SSA.transform(lf);
+        lf.writeTo(System.out);
+
+        lf = ExpressionElimination.eliminate(lf);
+        lf.writeTo(System.out);
+        return lf;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/interpreter/TestAssert.java b/test/jdk/java/lang/reflect/code/interpreter/TestAssert.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/interpreter/TestAssert.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.util.List;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.runtime.CodeReflection;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @run testng/othervm -ea TestAssert
+ */
+public class TestAssert {
+
+    public static final String FAILURESTRING = "failure";
+    public static final char FAILURECHAR = 'o';
+
+    public static final float FAILUREFLOAT = -1.0f;
+    public static final double FAILUREDOUBLE = -1.0d;
+    public static final byte FAILUREBYTE = -1;
+    public static final short FAILURESHORT = -1;
+    public static final int FAILUREINT = -1;
+
+    public static final long FAILURELONG = -1;
+
+    public static final String FAILUREOBJECTMSG = "FAILURE OBJECT";
+
+    public static final Object FAILUREOBJECT = new FailureObject();
+
+
+    @Test
+    public void testAssertThrows(){
+        testThrows("assertThrow");
+    }
+
+    @Test
+    public void testAssertString(){
+        AssertionError ae = testThrows("assertThrowWithMessage");
+        if (ae.getMessage() == null || !ae.getMessage().equals(FAILURESTRING)) {
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertChar() {
+        AssertionError ae = testThrows("assertChar");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILURECHAR))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertFloat() {
+        AssertionError ae = testThrows("assertFloat");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREFLOAT))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertDouble() {
+        AssertionError ae = testThrows("assertDouble");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREDOUBLE))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertByte() {
+        AssertionError ae = testThrows("assertByte");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREBYTE))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertShort() {
+        AssertionError ae = testThrows("assertShort");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILURESHORT))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertInt() {
+        AssertionError ae = testThrows("assertInt");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREINT))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertLong() {
+        AssertionError ae = testThrows("assertLong");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILURELONG))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertObject() {
+        AssertionError ae = testThrows("assertObject");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREOBJECT))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertExpr1() {
+        AssertionError ae = testThrows("assertExpr1");
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREINT + FAILURELONG))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertExpr2() {
+        AssertionError ae = testThrows("assertExpr2", List.of(int.class), 52);
+        if (ae.getMessage() == null || !ae.getMessage().equals(String.valueOf(FAILUREINT))){
+            Assert.fail("Assertion failure messages do not match.");
+        }
+    }
+
+    @Test
+    public void testAssertExpr3() {
+         testRun("assertExpr3", List.of(int.class), 52);
+    }
+
+    private static AssertionError testThrows(String methodName) {
+        return testThrows(methodName, List.of());
+    }
+    private static void testRun(String methodName, List<Class<?>> params, Object...args) {
+        try {
+            Class<TestAssert> clazz = TestAssert.class;
+            Method method = clazz.getDeclaredMethod(methodName,params.toArray(new Class[params.size()]));
+            CoreOp.FuncOp f = method.getCodeModel().orElseThrow();
+
+            //Ensure we're fully lowered before testing.
+            final var fz = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+            Interpreter.invoke(MethodHandles.lookup(), fz ,args);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static AssertionError testThrows(String methodName, List<Class<?>> params, Object...args) {
+        try {
+            Class<TestAssert> clazz = TestAssert.class;
+            Method method = clazz.getDeclaredMethod(methodName,params.toArray(new Class[params.size()]));
+            CoreOp.FuncOp f = method.getCodeModel().orElseThrow();
+
+            //Ensure we're fully lowered before testing.
+            final var fz = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+
+            AssertionError ae = (AssertionError) retCatch(() -> Interpreter.invoke(MethodHandles.lookup(), fz ,args));
+            Assert.assertNotNull(ae);
+            return ae;
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Throwable retCatch(Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            return t;
+        }
+        return null;
+    }
+
+
+
+    @CodeReflection
+    public static void assertThrow() {
+        assert false;
+    }
+
+    @CodeReflection
+    public static void assertThrowWithMessage() {
+        assert false : FAILURESTRING;
+    }
+
+    @CodeReflection
+    public static void assertChar() {
+        char c = FAILURECHAR;
+        assert false : c;
+    }
+
+    @CodeReflection
+    public static void assertFloat() {
+        float f = FAILUREFLOAT;
+        assert false : f;
+    }
+
+    @CodeReflection
+    public static void assertDouble() {
+        double d = FAILUREDOUBLE;
+        assert false : d;
+    }
+
+    @CodeReflection
+    public static void assertByte() {
+        byte b = FAILUREBYTE;
+        assert false : b;
+    }
+
+    @CodeReflection
+    public static void assertShort() {
+        short s = FAILURESHORT;
+        assert false : s;
+    }
+
+    @CodeReflection
+    public static void assertInt() {
+        int i = FAILUREINT;
+        assert false : i;
+    }
+
+    @CodeReflection
+    public static void assertLong() {
+        long l = FAILURELONG;
+        assert false : l;
+    }
+
+    @CodeReflection
+    public static void assertObject() {
+        Object o = FAILUREOBJECT;
+        assert false : o;
+    }
+
+    @CodeReflection
+    public static void assertExpr1() {
+        int i = FAILUREINT;
+        long l = FAILURELONG;
+        assert false : i + l;
+        String y = "test";
+    }
+
+    @CodeReflection
+    public static void assertExpr2(int iz) {
+        int i = FAILUREINT;
+        long l = FAILURELONG;
+        assert false : (i > iz) ? i + l : i;
+        String s = "";
+    }
+
+    @CodeReflection
+    public static void assertExpr3(int iz) {
+        int i = FAILUREINT;
+        long l = FAILURELONG;
+        assert true : (i > iz) ? i + l : i;
+        String s = "";
+    }
+
+    static class FailureObject {
+        @Override
+        public String toString(){
+           return FAILUREOBJECTMSG;
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/interpreter/TestThrowing.java b/test/jdk/java/lang/reflect/code/interpreter/TestThrowing.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/interpreter/TestThrowing.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.runtime.CodeReflection;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @run testng TestThrowing
+ */
+
+public class TestThrowing {
+
+    @Test(dataProvider = "methods-exceptions")
+    public void testThrowsCorrectException(String methodName, Class<? extends Throwable> expectedExceptionType) throws NoSuchMethodException {
+        Method method = TestThrowing.class.getDeclaredMethod(methodName);
+        Assert.assertThrows(expectedExceptionType, () -> Interpreter.invoke(MethodHandles.lookup(), method.getCodeModel().orElseThrow()));
+    }
+
+    @DataProvider(name = "methods-exceptions")
+    static Object[][] testData() throws NoSuchMethodException {
+        return new Object[][]{
+                {"throwsError", TestError.class},
+                {"throwsRuntimeException", TestRuntimeException.class},
+                {"throwsCheckedException", TestCheckedException.class},
+        };
+    }
+
+    public static class TestError extends Error {
+
+    }
+
+    public static class TestRuntimeException extends RuntimeException {
+
+    }
+
+    public static class TestCheckedException extends Exception {
+
+    }
+
+    @CodeReflection
+    static void throwsError() {
+        throw new TestError();
+    }
+
+    @CodeReflection
+    static void throwsRuntimeException() {
+        throw new TestRuntimeException();
+    }
+
+    @CodeReflection
+    static void throwsCheckedException() throws TestCheckedException {
+        throw new TestCheckedException();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/linq/QueryProvider.java b/test/jdk/java/lang/reflect/code/linq/QueryProvider.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/QueryProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.JavaType;
+
+public abstract class QueryProvider {
+    public abstract <T> Queryable<T> query(Class<T> elementType);
+
+    protected abstract Queryable<?> createQuery(JavaType elementType, CoreOp.FuncOp queryExpression);
+
+    protected abstract QueryResult<?> createQueryResult(JavaType resultType, CoreOp.FuncOp expression);
+}
diff a/test/jdk/java/lang/reflect/code/linq/QueryResult.java b/test/jdk/java/lang/reflect/code/linq/QueryResult.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/QueryResult.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.JavaType;
+
+public interface QueryResult<T> {
+    JavaType TYPE = JavaType.type(QueryResult.class);
+
+    // T
+    JavaType resultType();
+
+    // Queryable<T> -> QueryResult<T>
+    CoreOp.FuncOp expression();
+}
diff a/test/jdk/java/lang/reflect/code/linq/Queryable.java b/test/jdk/java/lang/reflect/code/linq/Queryable.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/Queryable.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.JavaType;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.type.JavaType.parameterized;
+import static java.lang.reflect.code.type.MethodRef.method;
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public interface Queryable<T> {
+    JavaType TYPE = type(Queryable.class);
+
+    QueryProvider provider();
+
+    // T
+    JavaType elementType();
+
+    // Queryable<T> -> Queryable<U>
+    FuncOp expression();
+
+    @SuppressWarnings("unchecked")
+    default Queryable<T> where(QuotablePredicate<T> f) {
+        LambdaOp l = (LambdaOp) f.quoted().op();
+        return (Queryable<T>) insertQuery(elementType(), "where", l);
+    }
+
+    @SuppressWarnings("unchecked")
+    default <R> Queryable<R> select(QuotableFunction<T, R> f) {
+        LambdaOp l = (LambdaOp) f.quoted().op();
+        return (Queryable<R>) insertQuery((JavaType) l.invokableType().returnType(), "select", l);
+    }
+
+    private Queryable<?> insertQuery(JavaType elementType, String methodName, LambdaOp lambdaOp) {
+        // Copy function expression, replacing return operation
+        FuncOp queryExpression = expression();
+        JavaType queryableType = parameterized(Queryable.TYPE, elementType);
+        FuncOp nextQueryExpression = func("query",
+                functionType(queryableType, queryExpression.invokableType().parameterTypes()))
+                .body(b -> b.inline(queryExpression, b.parameters(), (block, query) -> {
+                    Op.Result fi = block.op(lambdaOp);
+
+                    MethodRef md = method(Queryable.TYPE, methodName,
+                            functionType(Queryable.TYPE, ((ClassType) lambdaOp.functionalInterface()).rawType()));
+                    Op.Result queryable = block.op(invoke(queryableType, md, query, fi));
+
+                    block.op(_return(queryable));
+                }));
+
+        return provider().createQuery(elementType, nextQueryExpression);
+    }
+
+    @SuppressWarnings("unchecked")
+    default QueryResult<Stream<T>> elements() {
+        JavaType resultType = parameterized(type(Stream.class), elementType());
+        return (QueryResult<Stream<T>>) insertQueryResult(resultType, "elements");
+    }
+
+    @SuppressWarnings("unchecked")
+    default QueryResult<Long> count() {
+        JavaType resultType = JavaType.LONG;
+        return (QueryResult<Long>) insertQueryResult(resultType, "count");
+    }
+
+    private QueryResult<?> insertQueryResult(JavaType resultType, String methodName) {
+        // Copy function expression, replacing return operation
+        FuncOp queryExpression = expression();
+        JavaType queryResultType = parameterized(QueryResult.TYPE, resultType);
+        FuncOp queryResultExpression = func("queryResult",
+                functionType(queryResultType, queryExpression.invokableType().parameterTypes()))
+                .body(b -> b.inline(queryExpression, b.parameters(), (block, query) -> {
+                    MethodRef md = method(Queryable.TYPE, methodName,
+                            functionType(QueryResult.TYPE));
+                    Op.Result queryResult = block.op(invoke(queryResultType, md, query));
+
+                    block.op(_return(queryResult));
+                }));
+
+        return provider().createQueryResult(resultType, queryResultExpression);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/linq/QuotableFunction.java b/test/jdk/java/lang/reflect/code/linq/QuotableFunction.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/QuotableFunction.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.Quotable;
+import java.util.function.Function;
+
+@FunctionalInterface
+public interface QuotableFunction<T, R> extends Quotable, Function<T, R> {
+}
diff a/test/jdk/java/lang/reflect/code/linq/QuotablePredicate.java b/test/jdk/java/lang/reflect/code/linq/QuotablePredicate.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/QuotablePredicate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.Quotable;
+import java.util.function.Predicate;
+
+@FunctionalInterface
+public interface QuotablePredicate<T> extends Quotable, Predicate<T> {
+}
diff a/test/jdk/java/lang/reflect/code/linq/TestLinq.java b/test/jdk/java/lang/reflect/code/linq/TestLinq.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/TestLinq.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @enablePreview
+ * @run testng TestLinq
+ */
+
+public class TestLinq {
+
+    // A record modeling a table with three columns, one for each component
+    record Customer(String contactName, String phone, String city) {
+    }
+
+    @Test
+    public void testSimpleQuery() {
+        QueryProvider qp = new TestQueryProvider();
+
+        // Query all customers based in London, and return their names
+        QueryResult<Stream<String>> qr = qp.query(Customer.class)
+                .where(c -> c.city.equals("London"))
+                .select(c -> c.contactName)
+                .elements();
+
+        System.out.println(qr.expression().toText());
+
+        @SuppressWarnings("unchecked")
+        QueryResult<Stream<String>> qr2 = (QueryResult<Stream<String>>) Interpreter.invoke(MethodHandles.lookup(),
+                qr.expression(), qp.query(Customer.class));
+        System.out.println(qr2.expression().toText());
+
+        Assert.assertEquals(qr.expression().toText(), qr2.expression().toText());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/linq/TestQueryProvider.java b/test/jdk/java/lang/reflect/code/linq/TestQueryProvider.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/linq/TestQueryProvider.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.JavaType;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.func;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.parameterized;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public final class TestQueryProvider extends QueryProvider {
+    public TestQueryProvider() {
+    }
+
+    @Override
+    public <T> Queryable<T> query(Class<T> elementType) {
+        return new TestQueryable<>(elementType, this);
+    }
+
+    @Override
+    protected Queryable<?> createQuery(JavaType elementType, CoreOp.FuncOp expression) {
+        return new TestQueryable<>(elementType, this, expression);
+    }
+
+    @Override
+    protected QueryResult<?> createQueryResult(JavaType resultType, CoreOp.FuncOp expression) {
+        return new TestQueryResult<>(resultType, expression);
+    }
+
+    static final class TestQueryable<T> implements Queryable<T> {
+        final JavaType elementType;
+        final TestQueryProvider provider;
+        final CoreOp.FuncOp expression;
+
+        TestQueryable(Class<T> tableClass, TestQueryProvider qp) {
+            this.elementType = type(tableClass);
+            this.provider = qp;
+
+            JavaType queryableType = parameterized(Queryable.TYPE, elementType);
+            // Initial expression is an identity function
+            var funType = functionType(queryableType, queryableType);
+            this.expression = func("query", funType)
+                    .body(b -> b.op(_return(b.parameters().get(0))));
+        }
+
+        TestQueryable(JavaType elementType, TestQueryProvider provider, CoreOp.FuncOp expression) {
+            this.elementType = elementType;
+            this.provider = provider;
+            this.expression = expression;
+        }
+
+        @Override
+        public QueryProvider provider() {
+            return provider;
+        }
+
+        @Override
+        public JavaType elementType() {
+            return elementType;
+        }
+
+        @Override
+        public CoreOp.FuncOp expression() {
+            return expression;
+        }
+    }
+
+    record TestQueryResult<T>(JavaType resultType, CoreOp.FuncOp expression) implements QueryResult<T> {
+    }
+}
diff a/test/jdk/java/lang/reflect/code/location/ClassWithReflectedMethod.java b/test/jdk/java/lang/reflect/code/location/ClassWithReflectedMethod.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/location/ClassWithReflectedMethod.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+public class ClassWithReflectedMethod {
+    @CodeReflection       // 27
+    static String f() {   // 28
+        String a = "29";  // 29
+        String b = "30";  // 30
+        m("31");       // 31
+        return "32";      // 32
+    }                     // 33
+
+    static void m(String s) {}
+}
diff a/test/jdk/java/lang/reflect/code/location/TestLocation.java b/test/jdk/java/lang/reflect/code/location/TestLocation.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/location/TestLocation.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestLocation
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.Location;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestLocation {
+    @Test
+    public void testLocation() {
+        CoreOp.FuncOp f = getFuncOp(ClassWithReflectedMethod.class, "f");
+        f.traverse(null, (o, ce) -> {
+            if (ce instanceof CoreOp.ConstantOp cop) {
+                Location loc = cop.location();
+                Assert.assertNotNull(loc);
+
+                int actualLine = loc.line();
+                int expectedLine = Integer.parseInt((String) cop.value());
+                Assert.assertEquals(actualLine, expectedLine);
+            }
+            return null;
+        });
+    }
+
+    @CodeReflection
+    static int f(int m, int n) {
+        int sum = 0;
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                sum++;
+            }
+        }
+        return sum;
+    }
+
+    @Test
+    public void dropLocationTransform() {
+        CoreOp.FuncOp f = getFuncOp(TestLocation.class, "f");
+
+        CoreOp.FuncOp tf = f.transform(OpTransformer.DROP_LOCATION_TRANSFORMER);
+        tf.setLocation(Location.NO_LOCATION);
+        testNoLocations(tf);
+
+        CoreOp.FuncOp tlf = lower(f).transform(OpTransformer.DROP_LOCATION_TRANSFORMER);
+        tlf.setLocation(Location.NO_LOCATION);
+        testNoLocations(tlf);
+    }
+
+    @Test
+    public void dropLocationWriter() {
+        CoreOp.FuncOp f = getFuncOp(TestLocation.class, "f");
+
+        StringWriter w = new StringWriter();
+        OpWriter.writeTo(w, f, OpWriter.LocationOption.DROP_LOCATION);
+        String tfText = w.toString();
+        CoreOp.FuncOp tf = (CoreOp.FuncOp) OpParser.fromString(ExtendedOp.FACTORY, tfText).getFirst();
+        testNoLocations(tf);
+    }
+
+    static CoreOp.FuncOp lower(CoreOp.FuncOp f) {
+        return f.transform(OpTransformer.LOWERING_TRANSFORMER);
+    }
+
+    static void testNoLocations(Op op) {
+        boolean noLocations = op.elements().filter(ce -> ce instanceof Op)
+                .allMatch(ce -> ((Op) ce).location() == Location.NO_LOCATION);
+        Assert.assertTrue(noLocations);
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(Class<?> c, String name) {
+        Optional<Method> om = Stream.of(c.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/lower/CodeReflectionTester.java b/test/jdk/java/lang/reflect/code/lower/CodeReflectionTester.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/lower/CodeReflectionTester.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.StringWriter;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+
+public class CodeReflectionTester {
+
+    public static void main(String[] args) throws ReflectiveOperationException {
+        if (args.length != 1) {
+            System.err.println("Usage: CodeReflectionTester <classname>");
+            System.exit(1);
+        }
+        Class<?> clazz = Class.forName(args[0]);
+        for (Method m : clazz.getDeclaredMethods()) {
+            check(m);
+        }
+    }
+
+    static void check(Method method) throws ReflectiveOperationException {
+        if (!method.isAnnotationPresent(CodeReflection.class)) {
+            return;
+        }
+
+        LoweredModel lma = method.getAnnotation(LoweredModel.class);
+        if (lma == null) {
+            throw new AssertionError("No @IR annotation found on reflective method");
+        }
+
+        CoreOp.FuncOp f = method.getCodeModel().orElseThrow(() ->
+                new AssertionError("No code model for reflective method"));
+        f = lower(f, lma.ssa());
+
+        String actual = canonicalizeModel(method, f);
+        String expected = canonicalizeModel(method, lma.value());
+        if (!actual.equals(expected)) {
+            throw new AssertionError(String.format("Bad code model\nFound:\n%s\n\nExpected:\n%s", actual, expected));
+        }
+    }
+
+    static CoreOp.FuncOp lower(CoreOp.FuncOp f, boolean ssa) {
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        f.writeTo(System.out);
+
+        if (ssa) {
+            f = SSA.transform(f);
+            f.writeTo(System.out);
+        }
+
+        return f;
+    }
+
+    // serializes dropping location information, parses, and then serializes, dropping location information
+    static String canonicalizeModel(Member m, Op o) {
+        return canonicalizeModel(m, serialize(o));
+    }
+
+    // parses, and then serializes, dropping location information
+    static String canonicalizeModel(Member m, String d) {
+        Op o;
+        try {
+            o = OpParser.fromString(ExtendedOp.FACTORY, d).get(0);
+        } catch (Exception e) {
+            throw new IllegalStateException(m.toString(), e);
+        }
+        return serialize(o);
+    }
+
+    // serializes, dropping location information
+    static String serialize(Op o) {
+        StringWriter w = new StringWriter();
+        OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
+        return w.toString();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/lower/LoweredModel.java b/test/jdk/java/lang/reflect/code/lower/LoweredModel.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/lower/LoweredModel.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.annotation.*;
+
+@Target({ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LoweredModel {
+    String value();
+    boolean ssa() default false;
+}
diff a/test/jdk/java/lang/reflect/code/lower/TestLoop.java b/test/jdk/java/lang/reflect/code/lower/TestLoop.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/lower/TestLoop.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary test lowering of loops
+ * @build TestLoop
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester TestLoop
+ */
+
+public class TestLoop {
+    @CodeReflection
+    @LoweredModel(value = """
+            func @"testFor" (%0 : int[])int -> {
+                %1 : Var<int[]> = var %0 @"a";
+                %2 : int = constant @"0";
+                %3 : Var<int> = var %2 @"sum";
+                %4 : int = constant @"0";
+                %5 : Var<int> = var %4 @"i";
+                branch ^block_0;
+
+              ^block_0:
+                %6 : int = var.load %5;
+                %7 : int[] = var.load %1;
+                %8 : int = array.length %7;
+                %9 : boolean = lt %6 %8;
+                cbranch %9 ^block_1 ^block_2;
+
+              ^block_1:
+                %10 : int = var.load %3;
+                %11 : int[] = var.load %1;
+                %12 : int = var.load %5;
+                %13 : int = array.load %11 %12;
+                %14 : int = add %10 %13;
+                var.store %3 %14;
+                branch ^block_3;
+
+              ^block_3:
+                %15 : int = var.load %5;
+                %16 : int = constant @"1";
+                %17 : int = add %15 %16;
+                var.store %5 %17;
+                branch ^block_0;
+
+              ^block_2:
+                %18 : int = var.load %3;
+                return %18;
+            };
+            """, ssa = false)
+    static int testFor(int[] a) {
+        int sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            sum += a[i];
+        }
+        return sum;
+    }
+
+    @CodeReflection
+    @LoweredModel(value = """
+            func @"testForSSA" (%0 : int[])int -> {
+                %1 : int = constant @"0";
+                %2 : int = constant @"0";
+                branch ^block_0(%1, %2);
+
+              ^block_0(%3 : int, %4 : int):
+                %5 : int = array.length %0;
+                %6 : boolean = lt %4 %5;
+                cbranch %6 ^block_1 ^block_2;
+
+              ^block_1:
+                %7 : int = array.load %0 %4;
+                %8 : int = add %3 %7;
+                branch ^block_3;
+
+              ^block_3:
+                %9 : int = constant @"1";
+                %10 : int = add %4 %9;
+                branch ^block_0(%8, %10);
+
+              ^block_2:
+                return %3;
+            };
+            """, ssa = true)
+    static int testForSSA(int[] a) {
+        int sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            sum += a[i];
+        }
+        return sum;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/parser/TestParse.java b/test/jdk/java/lang/reflect/code/parser/TestParse.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/parser/TestParse.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @modules java.base/java.lang.reflect.code.parser
+ * @run testng TestParse
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Block;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.parser.OpParser;
+import java.util.List;
+import java.util.function.IntUnaryOperator;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.add;
+import static java.lang.reflect.code.op.CoreOp.constant;
+import static java.lang.reflect.code.op.CoreOp.func;
+import static java.lang.reflect.code.op.CoreOp.lambda;
+import static java.lang.reflect.code.type.FunctionType.functionType;
+import static java.lang.reflect.code.type.JavaType.INT;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public class TestParse {
+
+    static final MethodRef INT_UNARY_OPERATOR_METHOD = MethodRef.method(
+            IntUnaryOperator.class, "applyAsInt",
+            int.class, int.class);
+
+    @Test
+    public void testParseLambdaOp() {
+        // functional type = (int)int
+        CoreOp.FuncOp f = func("f", functionType(INT, INT))
+                .body(block -> {
+                    Block.Parameter i = block.parameters().get(0);
+
+                    // functional type = (int)int
+                    // op type = ()IntUnaryOperator
+                    //   captures i
+                    CoreOp.LambdaOp lambda = lambda(block.parentBody(),
+                            functionType(INT, INT), type(IntUnaryOperator.class))
+                            .body(lbody -> {
+                                Block.Builder lblock = lbody.entryBlock();
+                                Block.Parameter li = lblock.parameters().get(0);
+
+                                lblock.op(_return(
+                                        lblock.op(add(i, li))));
+                            });
+
+                    Op.Result fi = block.op(lambda);
+                    Op.Result fortyTwo = block.op(constant(INT, 42));
+                    Op.Result or = block.op(CoreOp.invoke(INT_UNARY_OPERATOR_METHOD, fi, fortyTwo));
+                    block.op(_return(or));
+                });
+
+        List<Op> ops = OpParser.fromString(CoreOp.FACTORY, f.toText());
+        assertTextEquals(f, ops.get(0));
+    }
+
+
+    static final String NAMED_BODY = """
+            func @"test" ^body1(%0 : int, %1 : int)int -> {
+                %2 : int = constant @"5";
+                %3 : int = constant @"2";
+                branch ^b1(%2, %3);
+
+              ^b1(%0 : int, %1 : int):
+                return %0;
+            };
+            """;
+    @Test
+    void testParseNamedBody() {
+        Op opE = OpParser.fromString(CoreOp.FACTORY, NAMED_BODY).get(0);
+        Op opA = OpParser.fromString(CoreOp.FACTORY, opE.toText()).get(0);
+        assertTextEquals(opA, opE);
+    }
+
+
+    static final String ESCAPED_STRING = """
+            func @"test" ()String -> {
+                %0 : java.lang.String = constant @"\\b \\f \\n \\r \\t \\' \\" \\\\";
+                return %0;
+            };
+            """;
+    @Test
+    void testEscapedString() {
+        Op opE = OpParser.fromString(CoreOp.FACTORY, ESCAPED_STRING).get(0);
+        Op opA = OpParser.fromString(CoreOp.FACTORY, opE.toText()).get(0);
+        assertTextEquals(opA, opE);
+
+        CoreOp.ConstantOp cop = (CoreOp.ConstantOp) opE.bodies().get(0).entryBlock().firstOp();
+        String v = (String) cop.value();
+        Assert.assertEquals(v, "\b \f \n \r \t \' \" \\");
+    }
+
+    static void assertTextEquals(Op a, Op b) {
+        Assert.assertEquals(a.toText(), b.toText());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/parser/TestScanner.java b/test/jdk/java/lang/reflect/code/parser/TestScanner.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/parser/TestScanner.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.parser.impl.Scanner;
+import java.lang.reflect.code.parser.impl.Tokens;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.reflect.code.parser.impl.Tokens.TokenKind.*;
+
+/*
+ * @test
+ * @modules java.base/java.lang.reflect.code.parser.impl
+ * @run testng TestScanner
+ */
+
+public class TestScanner {
+
+    @DataProvider
+    Object[][] data() {
+        return new Object[][] {
+                {"java.lang.Integer", List.of(IDENTIFIER, DOT, IDENTIFIER, DOT, IDENTIFIER)},
+                {"java.lang.Integer", List.of("java", DOT, "lang", DOT, "Integer")},
+                {"a<a<a, a>,a<a, a>>", List.of("a", LT,
+                        "a", LT, "a", COMMA, "a", GT,
+                        COMMA,
+                        "a", LT, "a", COMMA, "a", GT,
+                        GT)},
+                {"_->(){}[],.=><?:;+-&^@", List.of(
+                        UNDERSCORE,
+                        ARROW,
+                        LPAREN,
+                        RPAREN,
+                        LBRACE,
+                        RBRACE,
+                        LBRACKET,
+                        RBRACKET,
+                        COMMA,
+                        DOT,
+                        EQ,
+                        GT,
+                        LT,
+                        QUES,
+                        COLON,
+                        SEMI,
+                        PLUS,
+                        SUB,
+                        AMP,
+                        CARET,
+                        MONKEYS_AT
+                        )},
+                {"%1 %a %_1", List.of(VALUE_IDENTIFIER, VALUE_IDENTIFIER, VALUE_IDENTIFIER)},
+                {"%1 %a %_1", List.of("%1", "%a", "%_1")},
+                {"\"abc\\n\"", List.of(STRINGLITERAL)},
+                {"\"abc \\b \\f \\n \\r \\t \\' \\\" \\\\\"", List.of("abc \b \f \n \r \t \' \" \\")},
+        };
+    }
+
+    @Test(dataProvider = "data")
+    public void test(String content, List<Object> expectedTokens) {
+        Scanner.Factory factory = Scanner.factory();
+
+        Scanner s = factory.newScanner(content);
+        s.nextToken();
+        List<Tokens.Token> actualTokens = new ArrayList<>();
+        while (s.token().kind != EOF) {
+            actualTokens.add(s.token());
+            s.nextToken();
+        }
+
+        Assert.assertEquals(actualTokens.size(), expectedTokens.size());
+        for (int i = 0; i < expectedTokens.size(); i++) {
+            Object e = expectedTokens.get(i);
+            Tokens.Token a = actualTokens.get(i);
+            if (e instanceof Tokens.TokenKind t) {
+                Assert.assertEquals(a.kind, t);
+            } else if (e instanceof String v) {
+                String as = switch (a.kind.tag) {
+                    case NAMED -> a.name();
+                    case STRING, NUMERIC -> a.stringVal();
+                    case DEFAULT -> a.kind.name;
+                };
+                Assert.assertEquals(as, v);
+            } else {
+                assert false;
+            }
+        }
+    }
+}
diff a/test/jdk/java/lang/reflect/code/stream/StreamFuser.java b/test/jdk/java/lang/reflect/code/stream/StreamFuser.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/stream/StreamFuser.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp.JavaEnhancedForOp;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.op.ExtendedOp._continue;
+import static java.lang.reflect.code.op.ExtendedOp.enhancedFor;
+import static java.lang.reflect.code.type.JavaType.parameterized;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public final class StreamFuser {
+
+    StreamFuser() {}
+
+    public static StreamExprBuilder fromList(JavaType elementType) {
+        // java.util.List<E>
+        JavaType listType = parameterized(type(List.class), elementType);
+        return new StreamExprBuilder(listType, elementType,
+                (b, v) -> StreamExprBuilder.enhancedForLoop(b, elementType, v)::body);
+    }
+
+    public static class StreamExprBuilder {
+        static class StreamOp {
+            final Quoted quotedClosure;
+
+            StreamOp(Quoted quotedClosure) {
+                if (!(quotedClosure.op() instanceof CoreOp.ClosureOp)) {
+                    throw new IllegalArgumentException("Quoted operation is not closure operation");
+                }
+                this.quotedClosure = quotedClosure;
+            }
+
+            CoreOp.ClosureOp op() {
+                return (CoreOp.ClosureOp) quotedClosure.op();
+            }
+        }
+
+        static class MapStreamOp extends StreamOp {
+            public MapStreamOp(Quoted quotedClosure) {
+                super(quotedClosure);
+                // @@@ Check closure signature
+            }
+        }
+
+        static class FlatMapStreamOp extends StreamOp {
+            public FlatMapStreamOp(Quoted quotedClosure) {
+                super(quotedClosure);
+                // @@@ Check closure signature
+            }
+        }
+
+        static class FilterStreamOp extends StreamOp {
+            public FilterStreamOp(Quoted quotedClosure) {
+                super(quotedClosure);
+                // @@@ Check closure signature
+            }
+        }
+
+        final JavaType sourceType;
+        final JavaType sourceElementType;
+        final BiFunction<Body.Builder, Value, Function<Consumer<Block.Builder>, Op>> loopSupplier;
+        final List<StreamOp> streamOps;
+
+        StreamExprBuilder(JavaType sourceType, JavaType sourceElementType,
+                          BiFunction<Body.Builder, Value, Function<Consumer<Block.Builder>, Op>> loopSupplier) {
+            this.sourceType = sourceType;
+            this.sourceElementType = sourceElementType;
+            this.loopSupplier = loopSupplier;
+            this.streamOps = new ArrayList<>();
+        }
+
+        static JavaEnhancedForOp.BodyBuilder enhancedForLoop(Body.Builder ancestorBody, JavaType elementType,
+                                                             Value iterable) {
+            return enhancedFor(ancestorBody, iterable.type(), elementType)
+                    .expression(b -> {
+                        b.op(_yield(iterable));
+                    })
+                    .definition(b -> {
+                        b.op(_yield(b.parameters().get(0)));
+                    });
+        }
+
+        public StreamExprBuilder map(Quoted f) {
+            streamOps.add(new MapStreamOp(f));
+            return this;
+        }
+
+        public StreamExprBuilder flatMap(Quoted f) {
+            streamOps.add(new FlatMapStreamOp(f));
+            return this;
+        }
+
+        public StreamExprBuilder filter(Quoted f) {
+            streamOps.add(new FilterStreamOp(f));
+            return this;
+        }
+
+        void fuseIntermediateOperations(Block.Builder body, BiConsumer<Block.Builder, Value> terminalConsumer) {
+            fuseIntermediateOperation(0, body, body.parameters().get(0), null, terminalConsumer);
+        }
+
+        void fuseIntermediateOperation(int i, Block.Builder body, Value element, Block.Builder continueBlock,
+                                       BiConsumer<Block.Builder, Value> terminalConsumer) {
+            if (i == streamOps.size()) {
+                terminalConsumer.accept(body, element);
+                return;
+            }
+
+            StreamOp sop = streamOps.get(i);
+            if (sop instanceof MapStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, value) -> {
+                    fuseIntermediateOperation(i + 1, block, value, continueBlock, terminalConsumer);
+                });
+            } else if (sop instanceof FilterStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, p) -> {
+                    Block.Builder _if = block.block();
+                    Block.Builder _else = continueBlock;
+                    if (continueBlock == null) {
+                        _else = block.block();
+                        _else.op(_continue());
+                    }
+
+                    block.op(conditionalBranch(p, _if.successor(), _else.successor()));
+
+                    fuseIntermediateOperation(i + 1, _if, element, _else, terminalConsumer);
+                });
+            } else if (sop instanceof FlatMapStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, iterable) -> {
+                    JavaEnhancedForOp forOp = enhancedFor(block.parentBody(),
+                            iterable.type(), ((ClassType) iterable.type()).typeArguments().get(0))
+                            .expression(b -> {
+                                b.op(_yield(iterable));
+                            })
+                            .definition(b -> {
+                                b.op(_yield(b.parameters().get(0)));
+                            })
+                            .body(b -> {
+                                fuseIntermediateOperation(i + 1,
+                                        b,
+                                        b.parameters().get(0),
+                                        null, terminalConsumer);
+                            });
+
+                    block.op(forOp);
+                    block.op(_continue());
+                });
+            }
+        }
+
+        public FuncOp forEach(Quoted quotedConsumer) {
+            if (!(quotedConsumer.op() instanceof CoreOp.ClosureOp consumer)) {
+                throw new IllegalArgumentException("Quoted consumer is not closure operation");
+            }
+
+            return func("fused.forEach", FunctionType.functionType(JavaType.VOID, sourceType))
+                    .body(b -> {
+                        Value source = b.parameters().get(0);
+
+                        Op sourceLoop = loopSupplier.apply(b.parentBody(), source)
+                                .apply(loopBlock -> {
+                                    fuseIntermediateOperations(loopBlock, (terminalBlock, resultValue) -> {
+                                        terminalBlock.inline(consumer, List.of(resultValue),
+                                                (_, _) -> {
+                                                });
+                                        terminalBlock.op(_continue());
+                                    });
+
+                                });
+                        b.op(sourceLoop);
+                        b.op(_return());
+                    });
+        }
+
+        // Supplier<C> supplier, BiConsumer<C, T> accumulator
+        public FuncOp collect(Quoted quotedSupplier, Quoted quotedAccumulator) {
+            if (!(quotedSupplier.op() instanceof CoreOp.ClosureOp supplier)) {
+                throw new IllegalArgumentException("Quoted supplier is not closure operation");
+            }
+            if (!(quotedAccumulator.op() instanceof CoreOp.ClosureOp accumulator)) {
+                throw new IllegalArgumentException("Quoted accumulator is not closure operation");
+            }
+
+            JavaType collectType = (JavaType) supplier.invokableType().returnType();
+            return func("fused.collect", FunctionType.functionType(collectType, sourceType))
+                    .body(b -> {
+                        Value source = b.parameters().get(0);
+
+                        b.inline(supplier, List.of(), (block, collect) -> {
+                            Op sourceLoop = loopSupplier.apply(block.parentBody(), source)
+                                    .apply(loopBlock -> {
+                                        fuseIntermediateOperations(loopBlock, (terminalBlock, resultValue) -> {
+                                            terminalBlock.inline(accumulator, List.of(collect, resultValue),
+                                                    (_, _) -> {
+                                                    });
+                                            terminalBlock.op(_continue());
+                                        });
+                                    });
+                            block.op(sourceLoop);
+                            block.op(_return(collect));
+                        });
+                    });
+        }
+
+    }
+}
diff a/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java b/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.ExtendedOp.JavaEnhancedForOp;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.FunctionType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.*;
+
+import static java.lang.reflect.code.op.CoreOp.*;
+import static java.lang.reflect.code.op.ExtendedOp._continue;
+import static java.lang.reflect.code.op.ExtendedOp.enhancedFor;
+import static java.lang.reflect.code.type.JavaType.parameterized;
+import static java.lang.reflect.code.type.JavaType.type;
+
+public final class StreamFuserUsingQuotable {
+
+    // Quotable functional interfaces
+
+    public interface QuotablePredicate<T> extends Quotable, Predicate<T> {
+    }
+
+    public interface QuotableFunction<T, R> extends Quotable, Function<T, R> {
+    }
+
+    public interface QuotableSupplier<T> extends Quotable, Supplier<T> {
+    }
+
+    public interface QuotableConsumer<T> extends Quotable, Consumer<T> {
+    }
+
+    public interface QuotableBiConsumer<T, U> extends Quotable, BiConsumer<T, U> {
+    }
+
+
+    StreamFuserUsingQuotable() {}
+
+    public static <T> StreamExprBuilder<T> fromList(Class<T> elementClass) {
+        JavaType elementType = type(elementClass);
+        // java.util.List<E>
+        JavaType listType = parameterized(type(List.class), elementType);
+        return new StreamExprBuilder<>(listType, elementType,
+                (b, v) -> StreamExprBuilder.enhancedForLoop(b, elementType, v)::body);
+    }
+
+    public static class StreamExprBuilder<T> {
+        static class StreamOp {
+            final LambdaOp lambdaOp;
+
+            StreamOp(Quotable quotedLambda) {
+                if (!(quotedLambda.quoted().op() instanceof LambdaOp lambdaOp)) {
+                    throw new IllegalArgumentException("Quotable operation is not lambda operation");
+                }
+                if (!(quotedLambda.quoted().capturedValues().isEmpty())) {
+                    throw new IllegalArgumentException("Quotable operation captures values");
+                }
+                this.lambdaOp = lambdaOp;
+            }
+
+            LambdaOp op() {
+                return lambdaOp;
+            }
+        }
+
+        static class MapStreamOp extends StreamOp {
+            public MapStreamOp(Quotable quotedLambda) {
+                super(quotedLambda);
+            }
+        }
+
+        static class FlatMapStreamOp extends StreamOp {
+            public FlatMapStreamOp(Quotable quotedLambda) {
+                super(quotedLambda);
+            }
+        }
+
+        static class FilterStreamOp extends StreamOp {
+            public FilterStreamOp(Quotable quotedLambda) {
+                super(quotedLambda);
+            }
+        }
+
+        final JavaType sourceType;
+        final JavaType sourceElementType;
+        final BiFunction<Body.Builder, Value, Function<Consumer<Block.Builder>, Op>> loopSupplier;
+        final List<StreamOp> streamOps;
+
+        StreamExprBuilder(JavaType sourceType, JavaType sourceElementType,
+                          BiFunction<Body.Builder, Value, Function<Consumer<Block.Builder>, Op>> loopSupplier) {
+            this.sourceType = sourceType;
+            this.sourceElementType = sourceElementType;
+            this.loopSupplier = loopSupplier;
+            this.streamOps = new ArrayList<>();
+        }
+
+        static JavaEnhancedForOp.BodyBuilder enhancedForLoop(Body.Builder ancestorBody, JavaType elementType,
+                                                             Value iterable) {
+            return enhancedFor(ancestorBody, iterable.type(), elementType)
+                    .expression(b -> {
+                        b.op(_yield(iterable));
+                    })
+                    .definition(b -> {
+                        b.op(_yield(b.parameters().get(0)));
+                    });
+        }
+
+        @SuppressWarnings("unchecked")
+        public <R> StreamExprBuilder<R> map(QuotableFunction<T, R> f) {
+            streamOps.add(new MapStreamOp(f));
+            return (StreamExprBuilder<R>) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public <R> StreamExprBuilder<R> flatMap(QuotableFunction<T, Iterable<R>> f) {
+            streamOps.add(new FlatMapStreamOp(f));
+            return (StreamExprBuilder<R>) this;
+        }
+
+        public StreamExprBuilder<T> filter(QuotablePredicate<T> f) {
+            streamOps.add(new FilterStreamOp(f));
+            return this;
+        }
+
+        void fuseIntermediateOperations(Block.Builder body, BiConsumer<Block.Builder, Value> terminalConsumer) {
+            fuseIntermediateOperation(0, body, body.parameters().get(0), null, terminalConsumer);
+        }
+
+        void fuseIntermediateOperation(int i, Block.Builder body, Value element, Block.Builder continueBlock,
+                                       BiConsumer<Block.Builder, Value> terminalConsumer) {
+            if (i == streamOps.size()) {
+                terminalConsumer.accept(body, element);
+                return;
+            }
+
+            StreamOp sop = streamOps.get(i);
+            if (sop instanceof MapStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, value) -> {
+                    fuseIntermediateOperation(i + 1, block, value, continueBlock, terminalConsumer);
+                });
+            } else if (sop instanceof FilterStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, p) -> {
+                    Block.Builder _if = block.block();
+                    Block.Builder _else = continueBlock;
+                    if (continueBlock == null) {
+                        _else = block.block();
+                        _else.op(_continue());
+                    }
+
+                    block.op(conditionalBranch(p, _if.successor(), _else.successor()));
+
+                    fuseIntermediateOperation(i + 1, _if, element, _else, terminalConsumer);
+                });
+            } else if (sop instanceof FlatMapStreamOp) {
+                body.inline(sop.op(), List.of(element), (block, iterable) -> {
+                    JavaEnhancedForOp forOp = enhancedFor(block.parentBody(),
+                            iterable.type(), ((ClassType) iterable.type()).typeArguments().get(0))
+                            .expression(b -> {
+                                b.op(_yield(iterable));
+                            })
+                            .definition(b -> {
+                                b.op(_yield(b.parameters().get(0)));
+                            })
+                            .body(b -> {
+                                fuseIntermediateOperation(i + 1,
+                                        b,
+                                        b.parameters().get(0),
+                                        null, terminalConsumer);
+                            });
+
+                    block.op(forOp);
+                    block.op(_continue());
+                });
+            }
+        }
+
+        public FuncOp forEach(QuotableConsumer<T> quotableConsumer) {
+            if (!(quotableConsumer.quoted().op() instanceof LambdaOp consumer)) {
+                throw new IllegalArgumentException("Quotable consumer is not lambda operation");
+            }
+            if (!(quotableConsumer.quoted().capturedValues().isEmpty())) {
+                throw new IllegalArgumentException("Quotable consumer captures values");
+            }
+
+            return func("fused.forEach", FunctionType.functionType(JavaType.VOID, sourceType))
+                    .body(b -> {
+                        Value source = b.parameters().get(0);
+
+                        Op sourceLoop = loopSupplier.apply(b.parentBody(), source)
+                                .apply(loopBlock -> {
+                                    fuseIntermediateOperations(loopBlock, (terminalBlock, resultValue) -> {
+                                        terminalBlock.inline(consumer, List.of(resultValue),
+                                                (_, _) -> {
+                                                });
+                                        terminalBlock.op(_continue());
+                                    });
+
+                                });
+                        b.op(sourceLoop);
+                        b.op(_return());
+                    });
+        }
+
+        public <C> FuncOp collect(QuotableSupplier<C> quotableSupplier, QuotableBiConsumer<C, T> quotableAccumulator) {
+            if (!(quotableSupplier.quoted().op() instanceof LambdaOp supplier)) {
+                throw new IllegalArgumentException("Quotable supplier is not lambda operation");
+            }
+            if (!(quotableSupplier.quoted().capturedValues().isEmpty())) {
+                throw new IllegalArgumentException("Quotable supplier captures values");
+            }
+            if (!(quotableAccumulator.quoted().op() instanceof LambdaOp accumulator)) {
+                throw new IllegalArgumentException("Quotable accumulator is not lambda operation");
+            }
+            if (!(quotableAccumulator.quoted().capturedValues().isEmpty())) {
+                throw new IllegalArgumentException("Quotable accumulator captures values");
+            }
+
+            JavaType collectType = (JavaType) supplier.invokableType().returnType();
+            return func("fused.collect", FunctionType.functionType(collectType, sourceType))
+                    .body(b -> {
+                        Value source = b.parameters().get(0);
+
+                        b.inline(supplier, List.of(), (block, collect) -> {
+                            Op sourceLoop = loopSupplier.apply(block.parentBody(), source)
+                                    .apply(loopBlock -> {
+                                        fuseIntermediateOperations(loopBlock, (terminalBlock, resultValue) -> {
+                                            terminalBlock.inline(accumulator, List.of(collect, resultValue),
+                                                    (_, _) -> {
+                                                    });
+                                            terminalBlock.op(_continue());
+                                        });
+                                    });
+                            block.op(sourceLoop);
+                            block.op(_return(collect));
+                        });
+                    });
+        }
+
+    }
+}
+
diff a/test/jdk/java/lang/reflect/code/stream/TestStream.java b/test/jdk/java/lang/reflect/code/stream/TestStream.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/stream/TestStream.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestStream
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.code.type.JavaType.type;
+
+public class TestStream {
+
+    @Test
+    public void testMapFilterForEach() {
+        CoreOp.FuncOp f = StreamFuser.fromList(type(Integer.class))
+                .map((Integer i) -> i.toString())
+                .filter((String s) -> s.length() < 10)
+                .map((String s) -> s.concat("_XXX"))
+                .filter((String s) -> s.length() < 10)
+                .forEach((String s) -> System.out.println(s));
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Interpreter.invoke(lf, List.of(List.of(1, 2, 3, 4, 5, 100_000_000, 10_000, 100_000, 20)));
+    }
+
+    @Test
+    public void testMapFlatMapFilterCollect() {
+        CoreOp.FuncOp f = StreamFuser.fromList(type(Integer.class))
+                .map((Integer i) -> i.toString())
+                .flatMap((String s) -> List.of(s, s))
+                .filter((String s) -> s.length() < 10)
+                .map((String s) -> s.concat("_XXX"))
+                .filter((String s) -> s.length() < 10)
+                .collect(() -> new ArrayList<String>(), (List<String> l, String e) -> l.add(e));
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        List<Integer> source = List.of(1, 2, 3, 4, 5, 100_000_000, 10_000, 20);
+
+        List<String> expected = source.stream()
+                .map(Object::toString)
+                .flatMap(s -> Stream.of(s, s))
+                .filter(s -> s.length() < 10)
+                .map(s -> s.concat("_XXX"))
+                .filter(s -> s.length() < 10)
+                .toList();
+
+        @SuppressWarnings("unchecked")
+        List<String> actual = (List<String>) Interpreter.invoke(lf, List.of(source));
+
+        Assert.assertEquals(expected, actual);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/stream/TestStreamUsingQuotable.java b/test/jdk/java/lang/reflect/code/stream/TestStreamUsingQuotable.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/stream/TestStreamUsingQuotable.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestStreamUsingQuotable
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.op.CoreOp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class TestStreamUsingQuotable {
+
+    @Test
+    public void testMapFilterForEach() {
+        CoreOp.FuncOp f = StreamFuserUsingQuotable.fromList(Integer.class)
+                .map(Object::toString)
+                .filter(s -> s.length() < 10)
+                .map(s -> s.concat("_XXX"))
+                .filter(s -> s.length() < 10)
+                // Cannot use method reference since it captures the result of the expression "System.out"
+                .forEach(s -> System.out.println(s));
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        Interpreter.invoke(lf, List.of(List.of(1, 2, 3, 4, 5, 100_000_000, 10_000, 100_000, 20)));
+    }
+
+    @Test
+    public void testMapFlatMapFilterCollect() {
+        CoreOp.FuncOp f = StreamFuserUsingQuotable.fromList(Integer.class)
+                .map(Object::toString)
+                .flatMap(s -> List.of(s, s))
+                .filter(s -> s.length() < 10)
+                .map(s -> s.concat("_XXX"))
+                .filter(s -> s.length() < 10)
+                .collect(ArrayList::new, ArrayList::add);
+
+        f.writeTo(System.out);
+
+        CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        lf.writeTo(System.out);
+
+        List<Integer> source = List.of(1, 2, 3, 4, 5, 100_000_000, 10_000, 20);
+
+        List<String> expected = source.stream()
+                .map(Object::toString)
+                .flatMap(s -> Stream.of(s, s))
+                .filter(s -> s.length() < 10)
+                .map(s -> s.concat("_XXX"))
+                .filter(s -> s.length() < 10)
+                .toList();
+
+        @SuppressWarnings("unchecked")
+        List<String> actual = (List<String>) Interpreter.invoke(lf, List.of(source));
+
+        Assert.assertEquals(expected, actual);
+    }
+}
diff a/test/jdk/java/lang/reflect/code/type/TestErasure.java b/test/jdk/java/lang/reflect/code/type/TestErasure.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/type/TestErasure.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestErasure
+ */
+
+import static org.testng.Assert.*;
+import org.testng.annotations.*;
+
+import java.lang.reflect.code.type.ArrayType;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.JavaType;
+import java.lang.reflect.code.type.PrimitiveType;
+import java.lang.reflect.code.type.TypeVarRef;
+import java.lang.reflect.code.type.WildcardType.BoundKind;
+import java.util.ArrayList;
+import java.util.List;
+
+@Test
+public class TestErasure {
+
+    @Test(dataProvider = "typesAndErasures")
+    public void testErasure(String testName, TypeAndErasure<?> typeAndErasure) {
+        assertEquals(typeAndErasure.type.erasure(), typeAndErasure.erasure);
+        assertEquals(typeAndErasure.type.toBasicType(), typeAndErasure.erasure.toBasicType());
+        assertEquals(typeAndErasure.type.toNominalDescriptor(), typeAndErasure.erasure.toNominalDescriptor());
+    }
+
+    @DataProvider
+    public static Object[][] typesAndErasures() {
+        List<TypeAndErasure<?>> typeAndErasures = new ArrayList<>();
+        typeAndErasures.addAll(primitives());
+        typeAndErasures.addAll(references());
+        typeAndErasures.addAll(genericReferences());
+        typeAndErasures.addAll(arrays());
+        typeAndErasures.addAll(typeVars());
+        return typeAndErasures.stream()
+                .map(t -> new Object[] { t.type.toString(), t })
+                .toArray(Object[][]::new);
+    }
+
+    static List<TypeAndErasure<PrimitiveType>> primitives() {
+        return List.of(
+                new TypeAndErasure<>(JavaType.BOOLEAN, JavaType.BOOLEAN),
+                new TypeAndErasure<>(JavaType.CHAR, JavaType.CHAR),
+                new TypeAndErasure<>(JavaType.BYTE, JavaType.BYTE),
+                new TypeAndErasure<>(JavaType.SHORT, JavaType.SHORT),
+                new TypeAndErasure<>(JavaType.INT, JavaType.INT),
+                new TypeAndErasure<>(JavaType.FLOAT, JavaType.FLOAT),
+                new TypeAndErasure<>(JavaType.LONG, JavaType.LONG),
+                new TypeAndErasure<>(JavaType.DOUBLE, JavaType.DOUBLE),
+                new TypeAndErasure<>(JavaType.VOID, JavaType.VOID));
+    }
+
+    static List<TypeAndErasure<ClassType>> references() {
+        return List.of(
+                new TypeAndErasure<>(JavaType.J_L_STRING, JavaType.J_L_STRING),
+                new TypeAndErasure<>(JavaType.J_L_OBJECT, JavaType.J_L_OBJECT));
+    }
+
+    static List<TypeAndErasure<ClassType>> genericReferences() {
+        JavaType LIST = JavaType.type(List.class);
+        List<TypeAndErasure<ClassType>> genericTypes = new ArrayList<>();
+        BoundKind[] kinds = new BoundKind[] { null, BoundKind.EXTENDS, BoundKind.SUPER };
+        for (BoundKind kind : kinds) {
+            for (TypeAndErasure<ClassType> t : references()) {
+                JavaType arg = t.type;
+                if (kind != null) {
+                    arg = JavaType.wildcard(kind, arg);
+                }
+                genericTypes.add(new TypeAndErasure<>(JavaType.parameterized(LIST, arg), LIST));
+            }
+            for (TypeAndErasure<PrimitiveType> t : primitives()) {
+                JavaType arg = JavaType.array(t.type);
+                if (kind != null) {
+                    arg = JavaType.wildcard(kind, arg);
+                }
+                genericTypes.add(new TypeAndErasure<>(JavaType.parameterized(LIST, arg), LIST));
+            }
+        }
+        return genericTypes;
+    }
+
+    static List<TypeAndErasure<ArrayType>> arrays() {
+        List<TypeAndErasure<ArrayType>> arrayTypes = new ArrayList<>();
+        for (int dims = 1 ; dims <= 3 ; dims++) {
+            for (TypeAndErasure<PrimitiveType> t : primitives()) {
+                if (t.type.isVoid()) continue; // void is not a valid array component type
+                arrayTypes.add(new TypeAndErasure<>(JavaType.array(t.type, dims), JavaType.array(t.erasure, dims)));
+            }
+            for (TypeAndErasure<ClassType> t : references()) {
+                arrayTypes.add(new TypeAndErasure<>(JavaType.array(t.type, dims), JavaType.array(t.erasure, dims)));
+            }
+            for (TypeAndErasure<ClassType> t : genericReferences()) {
+                arrayTypes.add(new TypeAndErasure<>(JavaType.array(t.type, dims), JavaType.array(t.erasure, dims)));
+            }
+        }
+        return arrayTypes;
+    }
+
+    static List<TypeAndErasure<TypeVarRef>> typeVars() {
+        List<TypeAndErasure<TypeVarRef>> typeVars = new ArrayList<>();
+        for (int dims = 1 ; dims <= 3 ; dims++) {
+            for (TypeAndErasure<ClassType> t : references()) {
+                typeVars.add(new TypeAndErasure<>(JavaType.typeVarRef("X", JavaType.J_L_OBJECT, t.type), t.erasure));
+            }
+            for (TypeAndErasure<ClassType> t : genericReferences()) {
+                typeVars.add(new TypeAndErasure<>(JavaType.typeVarRef("X", JavaType.J_L_OBJECT, t.type), t.erasure));
+            }
+            for (TypeAndErasure<ArrayType> t : arrays()) {
+                typeVars.add(new TypeAndErasure<>(JavaType.typeVarRef("X", JavaType.J_L_OBJECT, t.type), t.erasure));
+            }
+        }
+        return typeVars;
+    }
+
+    record TypeAndErasure<T extends JavaType>(T type, JavaType erasure) { }
+}
diff a/test/jdk/java/lang/reflect/code/type/TestJavaType.java b/test/jdk/java/lang/reflect/code/type/TestJavaType.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/type/TestJavaType.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.lang.reflect.code.type.ArrayType;
+import java.lang.reflect.code.type.ClassType;
+import java.lang.reflect.code.type.JavaType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+
+/*
+ * @test
+ * @run testng TestJavaType
+ */
+
+public class TestJavaType {
+
+    @DataProvider
+    public Object[][] JavaTypes() {
+        return new Object[][]{
+                {"boolean", "Z"},
+                {"byte", "B"},
+                {"char", "C"},
+                {"short", "S"},
+                {"int", "I"},
+                {"long", "J"},
+                {"float", "F"},
+                {"double", "D"},
+                {"void", "V"},
+                {"int[]", "[I"},
+                {"int[][][][]", "[[[[I"},
+                {"java.lang.String", "Ljava/lang/String;"},
+                {"java.lang.String[][]", "[[Ljava/lang/String;"},
+                {"a.b.C$D", "La/b/C$D;"},
+        };
+    }
+
+    @Test(dataProvider = "JavaTypes")
+    public void testJavaType(String tds, String bcd) {
+        JavaType jt = JavaType.ofString(tds);
+        Assert.assertEquals(jt.toString(), tds);
+        Assert.assertEquals(jt.toNominalDescriptor().descriptorString(), bcd);
+        Assert.assertEquals(jt, JavaType.type(ClassDesc.ofDescriptor(bcd)));
+    }
+
+    @DataProvider
+    public Object[][] classDescriptors() {
+        return new Object[][]{
+                {"java.lang.String", "java.lang.String"},
+        };
+    }
+
+    @Test(dataProvider = "classDescriptors")
+    public void classDescriptor(String tds, String bcd) {
+        ClassType jt = (ClassType)JavaType.ofString(tds);
+        Assert.assertEquals(jt.toString(), tds);
+        Assert.assertEquals(jt.toClassName(), bcd);
+    }
+
+
+    @DataProvider
+    public Object[][] basicJavaTypes() {
+        return new Object[][]{
+                {"boolean", "int"},
+                {"byte", "int"},
+                {"char", "int"},
+                {"short", "int"},
+                {"int", "int"},
+                {"long", "long"},
+                {"float", "float"},
+                {"double", "double"},
+                {"void", "void"},
+                {"int[]", "java.lang.Object"},
+                {"int[][][][]", "java.lang.Object"},
+                {"java.lang.String", "java.lang.Object"},
+                {"java.lang.String[][]", "java.lang.Object"},
+                {"a.b.C$D", "java.lang.Object"},
+                {"java.util.List<T>", "java.lang.Object"},
+                {"java.util.List<T>[]", "java.lang.Object"},
+        };
+    }
+
+    @Test(dataProvider = "basicJavaTypes")
+    public void testBasicJavaType(String tds, String btds) {
+        JavaType jt = JavaType.ofString(tds);
+        Assert.assertEquals(jt.toString(), tds);
+        Assert.assertEquals(jt.toBasicType().toString(), btds);
+    }
+
+
+    @DataProvider
+    public Object[][] argumentJavaTypes() {
+        return new Object[][]{
+                {"java.util.List<T>", "T"},
+                {"java.util.List<T>[]", "T"},
+                {"java.util.List<java.util.function.Supplier<T>>", "java.util.function.Supplier<T>"},
+                {"java.util.List<java.util.function.Supplier<T>>[][]", "java.util.function.Supplier<T>"},
+                {"java.util.Map<K, V>", "K", "V"},
+                {"ab<cd<S<T, V>, N>>", "cd<S<T, V>, N>"},
+                {"java.util.Consumer<java.util.Function<String, Number>>", "java.util.Function<String, Number>"},
+        };
+    }
+
+    @Test(dataProvider = "argumentJavaTypes")
+    public void testArgumentJavaType(String tds, String... argTypes) {
+        JavaType jt = JavaType.ofString(tds);
+        Assert.assertEquals(jt.toString(), tds);
+
+        while (jt instanceof ArrayType) {
+            jt = ((ArrayType)jt).componentType();
+        }
+        ClassType ct = (ClassType)jt;
+
+        Assert.assertEquals(argTypes.length, ct.typeArguments().size());
+
+        Assert.assertEquals(ct.typeArguments(), Stream.of(argTypes).map(JavaType::ofString).toList());
+    }
+
+    @Test(dataProvider = "classDescs")
+    public void testClassDescRoundTrip(ClassDesc classDesc) {
+        Assert.assertEquals(classDesc, JavaType.type(classDesc).toNominalDescriptor());
+    }
+
+    @DataProvider
+    public Object[][] classDescs() throws ReflectiveOperationException {
+        List<Object[]> classDescs = new ArrayList<>();
+        for (Field f : ConstantDescs.class.getDeclaredFields()) {
+            if (f.getName().startsWith("CD_")) {
+                ClassDesc cd = (ClassDesc)f.get(null);
+                classDescs.add(new Object[] { cd });
+                if (!cd.equals(ConstantDescs.CD_void)) {
+                    classDescs.add(new Object[]{cd.arrayType()});
+                    classDescs.add(new Object[]{cd.arrayType().arrayType()});
+                }
+            }
+        }
+        return classDescs.stream().toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "types")
+    public void testTypeRoundTrip(Type type) throws ReflectiveOperationException {
+        Assert.assertEquals(type, JavaType.type(type).resolve(MethodHandles.lookup()));
+    }
+
+    @DataProvider
+    public Object[][] types() throws ReflectiveOperationException {
+        List<Object[]> types = new ArrayList<>();
+        for (Field f : TypeHolder.class.getDeclaredFields()) {
+            types.add(new Object[] { f.getGenericType() });
+        }
+        return types.stream().toArray(Object[][]::new);
+    }
+
+    static class TypeHolder<X extends Number> {
+        boolean p1;
+        char p2;
+        byte p3;
+        short p4;
+        int p5;
+        long p6;
+        float p7;
+        double p8;
+
+        boolean[] ap1;
+        char[] ap2;
+        byte[] ap3;
+        short[] ap4;
+        int[] ap5;
+        long[] ap6;
+        float[] ap7;
+        double[] ap8;
+
+        boolean[][] aap1;
+        char[][] aap2;
+        byte[][] aap3;
+        short[][] aap4;
+        int[][] aap5;
+        long[][] aap6;
+        float[][] aap7;
+        double[][] aap8;
+
+        String r1;
+        Map<String, String> r2;
+        Map<String, ?  extends String> r3;
+        Map<? extends String, String> r4;
+        Map<? extends String, ?  extends String> r5;
+        Map<? extends List<? extends String>, ? super List<? extends String>> r6;
+        Map<? extends List<? extends String>[], ? super List<? extends String>[]> r7;
+        List<boolean[]> r8;
+        List<char[]> r9;
+        List<byte[]> r10;
+        List<short[]> r11;
+        List<int[]> r12;
+        List<long[]> r13;
+        List<float[]> r14;
+        List<double[]> r15;
+
+        String[] ar1;
+        Map<String, String>[] ar2;
+        Map<String, ?  extends String>[] ar3;
+        Map<? extends String, String>[] ar4;
+        Map<? extends String, ?  extends String>[] ar5;
+        Map<? extends List<? extends String>, ? super List<? extends String>>[] ar6;
+        Map<? extends List<? extends String>[], ? super List<? extends String>[]>[] ar7;
+        List<boolean[]>[] ar8;
+        List<char[]>[] ar9;
+        List<byte[]>[] ar10;
+        List<short[]>[] ar11;
+        List<int[]>[] ar12;
+        List<long[]>[] ar13;
+        List<float[]>[] ar14;
+        List<double[]>[] ar15;
+
+        String[][] aar1;
+        Map<String, String>[][] aar2;
+        Map<String, ?  extends String>[][] aar3;
+        Map<? extends String, String>[][] aar4;
+        Map<? extends String, ?  extends String>[][] aar5;
+        Map<? extends List<? extends String>, ? super List<? extends String>>[][] aar6;
+        Map<? extends List<? extends String>[], ? super List<? extends String>[]>[][] aar7;
+        List<boolean[]>[][] aar8;
+        List<char[]>[][] aar9;
+        List<byte[]>[][] aar10;
+        List<short[]>[][] aar11;
+        List<int[]>[][] aar12;
+        List<long[]>[][] aar13;
+        List<float[]>[][] aar14;
+        List<double[]>[][] aar15;
+
+        X x1;
+        Map<X, X> x2;
+        Map<X, ?  extends X> x3;
+        Map<? extends X, X> x4;
+        Map<? extends X, ?  extends X> x5;
+        Map<? extends List<? extends X>, ? super List<? extends X>> x6;
+        Map<? extends List<? extends X>[], ? super List<? extends X>[]> x7;
+        List<X[]> x8;
+
+        X[] ax1;
+        Map<X, X>[] ax2;
+        Map<X, ?  extends X>[] ax3;
+        Map<? extends X, X>[] ax4;
+        Map<? extends X, ?  extends X>[] ax5;
+        Map<? extends List<? extends X>, ? super List<? extends X>>[] ax6;
+        Map<? extends List<? extends X>[], ? super List<? extends X>[]>[] ax7;
+        List<X[]>[] ax8;
+
+        X[][] aax1;
+        Map<X, X>[][] aax2;
+        Map<X, ?  extends X>[][] aax3;
+        Map<? extends X, X>[][] aax4;
+        Map<? extends X, ?  extends X>[][] aax5;
+        Map<? extends List<? extends X>, ? super List<? extends X>>[][] aax6;
+        Map<? extends List<? extends X>[], ? super List<? extends X>[]>[][] aax7;
+        List<X[]>[][] aax8;
+    }
+}
diff a/test/jdk/java/lang/reflect/code/type/TestReferences.java b/test/jdk/java/lang/reflect/code/type/TestReferences.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/type/TestReferences.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.type.FieldRef;
+import java.lang.reflect.code.type.MethodRef;
+import java.lang.reflect.code.type.RecordTypeRef;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+
+/*
+ * @test
+ * @run testng TestReferences
+ */
+
+public class TestReferences {
+
+    @DataProvider
+    public Object[][] methodRefs() {
+        return new Object[][]{
+                {"a::b()void", "a", "b"},
+                {"a.b::c(int)int", "a.b", "c"},
+                {"a.b.c::d(int, int)int", "a.b.c", "d"},
+                {"a::b(Func<String, Number>, Entry<List<String>, val>, int, long)void", "a", "b"},
+                {"java.io.PrintStream::println(java.lang.String)void", "java.io.PrintStream", "println"},
+                {"MethodReferenceTest$A::m(java.lang.Object)java.lang.Object", "MethodReferenceTest$A", "m"},
+                {"MethodReferenceTest$X::<new>(int)MethodReferenceTest$X", "MethodReferenceTest$X", "<new>"},
+                {"MethodReferenceTest$A[]::<new>(int)MethodReferenceTest$A[]", "MethodReferenceTest$A[]", "<new>"}
+        };
+    }
+
+    @Test(dataProvider = "methodRefs")
+    public void testMethodRef(String mds, String refType, String name) {
+        MethodRef mr = MethodRef.ofString(mds);
+        Assert.assertEquals(mr.toString(), mds);
+        Assert.assertEquals(mr.refType().toString(), refType);
+        Assert.assertEquals(mr.name(), name);
+    }
+
+
+    @DataProvider
+    public Object[][] fieldRefs() {
+        return new Object[][]{
+                {"a.b::c()int", "a.b", "c", "int"},
+                {"a.b.c::d()int", "a.b.c", "d", "int"},
+                {"java.lang.System::out()java.io.PrintStream", "java.lang.System", "out", "java.io.PrintStream"},
+        };
+    }
+
+    @Test(dataProvider = "fieldRefs")
+    public void testFieldRef(String fds, String refType, String name, String type) {
+        FieldRef fr = FieldRef.ofString(fds);
+        Assert.assertEquals(fr.toString(), fds);
+        Assert.assertEquals(fr.refType().toString(), refType);
+        Assert.assertEquals(fr.name(), name);
+        Assert.assertEquals(fr.type().toString(), type);
+    }
+
+
+    @DataProvider
+    public Object[][] recordTypeRefs() {
+        return new Object[][]{
+                {"()A"},
+                {"(B b)A"},
+                {"(B b, C c)A"},
+                {"(p.Func<String, Number> f, Entry<List<String>, val> e, int i, long l)p.A<R>"},
+        };
+    }
+
+    @Test(dataProvider = "recordTypeRefs")
+    public void testRecordTypeRef(String rtds) {
+        RecordTypeRef rtr = RecordTypeRef.ofString(rtds);
+        Assert.assertEquals(rtr.toString(), rtds);
+    }
+
+
+    @CodeReflection
+    static void x() {}
+
+    @Test
+    public void testAccessCodeModel() throws ReflectiveOperationException {
+        MethodRef xr = MethodRef.method(TestReferences.class, "x", void.class);
+        Optional<CoreOp.FuncOp> m = xr.codeModel(MethodHandles.lookup());
+        Assert.assertTrue(m.isPresent());
+    }
+}
diff a/test/jdk/java/lang/reflect/code/writer/TestCodeBuilder.java b/test/jdk/java/lang/reflect/code/writer/TestCodeBuilder.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/writer/TestCodeBuilder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.type.CoreTypeFactory;
+import java.lang.reflect.code.writer.OpBuilder;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @run testng TestCodeBuilder
+ */
+
+public class TestCodeBuilder {
+
+    @CodeReflection
+    static void constants() {
+        boolean bool = false;
+        byte b = 1;
+        char c = 'a';
+        short s = 1;
+        int i = 1;
+        long l = 1L;
+        float f = 1.0f;
+        double d = 1.0;
+        String str = "1";
+        Object obj = null;
+        Class<?> klass = Object.class;
+    }
+
+    @Test
+    public void testConstants() {
+        testWithTransforms(getFuncOp("constants"));
+    }
+
+    static record X(int f) {
+        void m() {}
+    }
+
+    @CodeReflection
+    static void reflect() {
+        X x = new X(1);
+        int i = x.f;
+        x.m();
+        X[] ax = new X[1];
+        int l = ax.length;
+        x = ax[0];
+
+        Object o = x;
+        x = (X) o;
+        if (o instanceof X) {
+            return;
+        }
+        if (o instanceof X(var a)) {
+            return;
+        }
+    }
+
+    @Test
+    public void testReflect() {
+        testWithTransforms(getFuncOp("reflect"));
+    }
+
+    @CodeReflection
+    static int bodies(int m, int n) {
+        int sum = 0;
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                sum += i + j;
+            }
+        }
+        return m > 10 ? sum : 0;
+    }
+
+    @Test
+    public void testBodies() {
+        testWithTransforms(getFuncOp("bodies"));
+    }
+
+    public void testWithTransforms(CoreOp.FuncOp f) {
+        test(f);
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+        test(f);
+
+        f = SSA.transform(f);
+        test(f);
+    }
+
+    static void test(CoreOp.FuncOp fExpected) {
+        CoreOp.FuncOp fb = OpBuilder.createBuilderFunction(fExpected);
+        CoreOp.FuncOp fActual = (CoreOp.FuncOp) Interpreter.invoke(MethodHandles.lookup(),
+                fb, ExtendedOp.FACTORY, CoreTypeFactory.CORE_TYPE_FACTORY);
+        Assert.assertEquals(fActual.toText(), fExpected.toText());
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestCodeBuilder.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/writer/TestNaming.java b/test/jdk/java/lang/reflect/code/writer/TestNaming.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/writer/TestNaming.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestNaming
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.CodeItem;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.OpTransformer;
+import java.lang.reflect.code.analysis.SSA;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class TestNaming {
+
+    @CodeReflection
+    static int f(int n, int m) {
+        int sum = 0;
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                sum += i;
+                sum += j;
+            }
+        }
+        return sum;
+    }
+
+    @Test
+    public void testHigh() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        testModel(f);
+    }
+
+    @Test
+    public void testLow() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        f = f.transform(OpTransformer.LOWERING_TRANSFORMER);
+
+        f = SSA.transform(f);
+
+        testModel(f);
+    }
+
+    static void testModel(Op op) {
+        Function<CodeItem, String> cNamer = OpWriter.computeGlobalNames(op);
+
+        String actual = OpWriter.toText(op, OpWriter.CodeItemNamerOption.of(cNamer));
+        String expected = op.toText();
+
+        Assert.assertEquals(actual, expected);
+    }
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestNaming.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/jdk/java/lang/reflect/code/writer/TestOptions.java b/test/jdk/java/lang/reflect/code/writer/TestOptions.java
--- /dev/null
+++ b/test/jdk/java/lang/reflect/code/writer/TestOptions.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @run testng TestOptions
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.code.op.CoreOp;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TestOptions {
+
+    @CodeReflection
+    static int f(int n) {
+        return n;
+    }
+
+    @Test
+    public void testDropWriteVoid() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        Assert.assertFalse(OpWriter.toText(f).contains("void"));
+        Assert.assertFalse(OpWriter.toText(f, OpWriter.VoidOpResultOption.DROP_VOID).contains("void"));
+        Assert.assertTrue(OpWriter.toText(f, OpWriter.VoidOpResultOption.WRITE_VOID).contains("void"));
+    }
+
+    @Test
+    public void testDropWriteDescendants() {
+        CoreOp.FuncOp f = getFuncOp("f");
+
+        Assert.assertTrue(OpWriter.toText(f).lines().count() > 1);
+        Assert.assertTrue(OpWriter.toText(f, OpWriter.OpDescendantsOption.WRITE_DESCENDANTS).lines().count() > 1);
+        Assert.assertTrue(OpWriter.toText(f, OpWriter.OpDescendantsOption.DROP_DESCENDANTS).lines().count() == 1);
+    }
+
+
+    static CoreOp.FuncOp getFuncOp(String name) {
+        Optional<Method> om = Stream.of(TestOptions.class.getDeclaredMethods())
+                .filter(m -> m.getName().equals(name))
+                .findFirst();
+
+        Method m = om.get();
+        return m.getCodeModel().get();
+    }
+}
diff a/test/langtools/tools/javac/diags/examples.not-yet.txt b/test/langtools/tools/javac/diags/examples.not-yet.txt
--- a/test/langtools/tools/javac/diags/examples.not-yet.txt
+++ b/test/langtools/tools/javac/diags/examples.not-yet.txt
@@ -209,10 +209,17 @@
 compiler.err.source.target.conflict
 compiler.err.target.default.source.conflict
 compiler.err.preview.not.latest
 compiler.err.preview.without.source.or.release
 compiler.misc.illegal.signature                               # the compiler can now detect more non-denotable types before class writing
+compiler.misc.feature.reflect.methods                         # code reflection
+compiler.note.method.ir.dump                                  # code reflection
+compiler.note.method.ir.skip                                  # code reflection
+compiler.err.cant.infer.quoted.lambda.return.type             # code reflection
+compiler.err.quoted.lambda.must.be.explicit                   # code reflection
+compiler.note.quoted.ir.dump                                  # code reflection
+compiler.note.quoted.ir.skip                                  # code reflection
 
 # this one needs a forged class file to be reproduced
 compiler.err.annotation.unrecognized.attribute.name
 
 # this one is transitional (waiting for FFM API to exit preview)
diff a/test/langtools/tools/javac/reflect/ArrayAccessTest.java b/test/langtools/tools/javac/reflect/ArrayAccessTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ArrayAccessTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with array access.
+ * @build ArrayAccessTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ArrayAccessTest
+ */
+
+public class ArrayAccessTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : ArrayAccessTest, %1 : int[])int -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : int = array.load %3 %4;
+                return %5;
+            };
+            """)
+    int test1(int[] ia) {
+        return ia[0];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : ArrayAccessTest, %1 : int[], %2 : int)int -> {
+                %3 : Var<int[]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[] = var.load %3;
+                %6 : int = var.load %4;
+                %7 : int = constant @"1";
+                %8 : int = add %6 %7;
+                %9 : int = array.load %5 %8;
+                return %9;
+            };
+            """)
+    int test2(int[] ia, int i) {
+        return ia[i + 1];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : ArrayAccessTest, %1 : int[])void -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : int = constant @"1";
+                array.store %3 %4 %5;
+                return;
+            };
+            """)
+    void test3(int[] ia) {
+        ia[0] = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : ArrayAccessTest, %1 : int[], %2 : int)void -> {
+                %3 : Var<int[]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[] = var.load %3;
+                %6 : int = var.load %4;
+                %7 : int = constant @"1";
+                %8 : int = add %6 %7;
+                %9 : int = constant @"1";
+                array.store %5 %8 %9;
+                return;
+            };
+            """)
+    void test4(int[] ia, int i) {
+        ia[i + 1] = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : ArrayAccessTest, %1 : int[][], %2 : int)int -> {
+                %3 : Var<int[][]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[][] = var.load %3;
+                %6 : int = var.load %4;
+                %7 : int = constant @"1";
+                %8 : int = add %6 %7;
+                %9 : int[] = array.load %5 %8;
+                %10 : int = var.load %4;
+                %11 : int = constant @"2";
+                %12 : int = add %10 %11;
+                %13 : int = array.load %9 %12;
+                return %13;
+            };
+            """)
+    int test5(int[][] ia, int i) {
+        return ia[i + 1][i + 2];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : ArrayAccessTest, %1 : int[][], %2 : int)void -> {
+                %3 : Var<int[][]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[][] = var.load %3;
+                %6 : int = var.load %4;
+                %7 : int = constant @"1";
+                %8 : int = add %6 %7;
+                %9 : int[] = array.load %5 %8;
+                %10 : int = var.load %4;
+                %11 : int = constant @"2";
+                %12 : int = add %10 %11;
+                %13 : int = constant @"1";
+                array.store %9 %12 %13;
+                return;
+            };
+            """)
+    void test6(int[][] ia, int i) {
+        ia[i + 1][i + 2] = 1;
+    }
+
+    int[] ia;
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : ArrayAccessTest)int -> {
+                %1 : int[] = field.load %0 @"ArrayAccessTest::ia()int[]";
+                %2 : int = constant @"0";
+                %3 : int = array.load %1 %2;
+                return %3;
+            };
+            """)
+    int test7() {
+        return ia[0];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : ArrayAccessTest)int -> {
+                %1 : int[] = field.load %0 @"ArrayAccessTest::ia()int[]";
+                %2 : int = constant @"0";
+                %3 : int = array.load %1 %2;
+                return %3;
+            };
+            """)
+    int test8() {
+        return this.ia[0];
+    }
+
+    static class A {
+        int i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : ArrayAccessTest, %1 : ArrayAccessTest$A[])int -> {
+                %2 : Var<ArrayAccessTest$A[]> = var %1 @"aa";
+                %3 : ArrayAccessTest$A[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : ArrayAccessTest$A = array.load %3 %4;
+                %6 : int = field.load %5 @"ArrayAccessTest$A::i()int";
+                return %6;
+            };
+            """)
+    int test9(A[] aa) {
+        return aa[0].i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : ArrayAccessTest, %1 : ArrayAccessTest$A[])void -> {
+                %2 : Var<ArrayAccessTest$A[]> = var %1 @"aa";
+                %3 : ArrayAccessTest$A[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : ArrayAccessTest$A = array.load %3 %4;
+                %6 : int = constant @"1";
+                field.store %5 %6 @"ArrayAccessTest$A::i()int";
+                return;
+            };
+            """)
+    void test10(A[] aa) {
+        aa[0].i = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : ArrayAccessTest, %1 : int[])void -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : int = array.load %3 %4;
+                %6 : int = constant @"1";
+                %7 : int = add %5 %6;
+                array.store %3 %4 %7;
+                return;
+            };
+            """)
+    void test11(int[] ia) {
+        ia[0] += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0 : ArrayAccessTest, %1 : int[], %2 : int)void -> {
+                %3 : Var<int[]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[] = var.load %3;
+                %6 : int = constant @"1";
+                %7 : int[] = var.load %3;
+                %8 : int = var.load %4;
+                %9 : int = constant @"2";
+                %10 : int = add %8 %9;
+                %11 : int = array.load %7 %10;
+                %12 : int = constant @"1";
+                %13 : int = add %11 %12;
+                array.store %7 %10 %13;
+                array.store %5 %6 %13;
+                return;
+            };
+            """)
+    void test12(int[] ia, int i) {
+        ia[1] = ia[i + 2] += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test13" (%0 : ArrayAccessTest, %1 : int[], %2 : int)void -> {
+                %3 : Var<int[]> = var %1 @"ia";
+                %4 : Var<int> = var %2 @"i";
+                %5 : int[] = var.load %3;
+                %6 : int = constant @"1";
+                %7 : int = array.load %5 %6;
+                %8 : int[] = var.load %3;
+                %9 : int = var.load %4;
+                %10 : int = constant @"2";
+                %11 : int = add %9 %10;
+                %12 : int = array.load %8 %11;
+                %13 : int = constant @"1";
+                %14 : int = add %12 %13;
+                array.store %8 %11 %14;
+                %15 : int = add %7 %14;
+                array.store %5 %6 %15;
+                return;
+            };
+            """)
+    void test13(int[] ia, int i) {
+        ia[1] += ia[i + 2] += 1;
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test14" (%0 : ArrayAccessTest, %1 : int[])void -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : int = array.load %3 %4;
+                %6 : int = constant @"1";
+                %7 : int = add %5 %6;
+                array.store %3 %4 %7;
+                %8 : Var<int> = var %5 @"x";
+                %9 : int[] = var.load %2;
+                %10 : int = constant @"0";
+                %11 : int = array.load %9 %10;
+                %12 : int = constant @"1";
+                %13 : int = sub %11 %12;
+                array.store %9 %10 %13;
+                %14 : Var<int> = var %11 @"y";
+                return;
+            };
+            """)
+    void test14(int[] ia) {
+        int x = ia[0]++;
+        int y = ia[0]--;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test15" (%0 : ArrayAccessTest, %1 : int[])void -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = constant @"0";
+                %5 : int = array.load %3 %4;
+                %6 : int = constant @"1";
+                %7 : int = add %5 %6;
+                array.store %3 %4 %7;
+                %8 : Var<int> = var %7 @"x";
+                %9 : int[] = var.load %2;
+                %10 : int = constant @"0";
+                %11 : int = array.load %9 %10;
+                %12 : int = constant @"1";
+                %13 : int = sub %11 %12;
+                array.store %9 %10 %13;
+                %14 : Var<int> = var %13 @"y";
+                return;
+            };
+            """)
+    void test15(int[] ia) {
+        int x = ++ia[0];
+        int y = --ia[0];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test16" (%0 : ArrayAccessTest, %1 : int[])int -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                %3 : int[] = var.load %2;
+                %4 : int = array.length %3;
+                %5 : int[] = var.load %2;
+                %6 : int = invoke %5 @"java.lang.Object::hashCode()int";
+                %7 : int = add %4 %6;
+                return %7;
+            };
+            """)
+    int test16(int[] ia) {
+        return ia.length + ia.hashCode();
+    }
+
+}
diff a/test/langtools/tools/javac/reflect/AssertTest.java b/test/langtools/tools/javac/reflect/AssertTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/AssertTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with array access.
+ * @build AssertTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester AssertTest
+ */
+public class AssertTest {
+
+    @CodeReflection
+    @IR("""
+              func @"assertTest" (%0 : int)void -> {
+                  %1 : Var<int> = var %0 @"i";
+                  assert
+                      ()boolean -> {
+                          %2 : int = var.load %1;
+                          %3 : int = constant @"1";
+                          %4 : boolean = eq %2 %3;
+                          yield %4;
+                      }
+                      ()java.lang.String -> {
+                          %5 : java.lang.String = constant @"i does not equal 1";
+                          yield %5;
+                      };
+                  return;
+              };
+            """)
+    public static void assertTest(int i) {
+        assert (i == 1) : "i does not equal 1";
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"assertTest2" (%0 : int)void -> {
+                  %1 : Var<int> = var %0 @"i";
+                  assert ()boolean -> {
+                      %2 : int = var.load %1;
+                      %3 : int = constant @"1";
+                      %4 : boolean = eq %2 %3;
+                      yield %4;
+                  };
+                  return;
+              };
+            """)
+    public static void assertTest2(int i) {
+        assert (i == 1);
+    }
+}
diff a/test/langtools/tools/javac/reflect/BinopTest.java b/test/langtools/tools/javac/reflect/BinopTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/BinopTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with binary operations.
+ * @build BinopTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester BinopTest
+ */
+
+import java.lang.runtime.CodeReflection;
+
+public class BinopTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test" (%0 : BinopTest)int -> {
+                %1 : int = constant @"5";
+                %2 : int = constant @"2";
+                %3 : int = constant @"4";
+                %4 : int = mul %2 %3;
+                %5 : int = add %1 %4;
+                %6 : int = constant @"3";
+                %7 : int = sub %5 %6;
+                return %7;
+            };
+            """)
+    int test() {
+        return 5 + 2 * 4 - 3;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : BinopTest)int -> {
+                %1 : int = constant @"1";
+                %2 : int = constant @"2";
+                %3 : int = constant @"3";
+                %4 : int = constant @"4";
+                %5 : int = add %3 %4;
+                %6 : int = add %2 %5;
+                %7 : int = add %1 %6;
+                return %7;
+            };
+            """)
+    int test2() {
+        return 1 + (2 + (3 + 4));
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : BinopTest)int -> {
+                %1 : int = constant @"1";
+                %2 : int = constant @"2";
+                %3 : int = add %1 %2;
+                %4 : int = constant @"3";
+                %5 : int = add %3 %4;
+                %6 : int = constant @"4";
+                %7 : int = add %5 %6;
+                return %7;
+            };
+            """)
+    int test3() {
+        return ((1 + 2) + 3) + 4;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : BinopTest, %1 : int)int -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = constant @"1";
+                %5 : int = add %3 %4;
+                var.store %2 %5;
+                %6 : int = var.load %2;
+                %7 : int = constant @"1";
+                %8 : int = mul %6 %7;
+                var.store %2 %8;
+                %9 : int = add %5 %8;
+                %10 : int = var.load %2;
+                %11 : int = constant @"1";
+                %12 : int = div %10 %11;
+                var.store %2 %12;
+                %13 : int = add %9 %12;
+                %14 : int = var.load %2;
+                %15 : int = constant @"1";
+                %16 : int = sub %14 %15;
+                var.store %2 %16;
+                %17 : int = add %13 %16;
+                %18 : int = var.load %2;
+                %19 : int = constant @"1";
+                %20 : int = mod %18 %19;
+                var.store %2 %20;
+                %21 : int = add %17 %20;
+                return %21;
+            };
+            """)
+    int test4(int i) {
+        return (i += 1) + (i *= 1) + (i /= 1) + (i -= 1) + (i %= 1);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : BinopTest, %1 : int)boolean -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = constant @"0";
+                %5 : boolean = eq %3 %4;
+                %6 : boolean = not %5;
+                return %6;
+            };
+            """)
+    boolean test5(int i) {
+        return !(i == 0);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : BinopTest)int -> {
+                %1 : int = constant @"5";
+                %2 : int = constant @"2";
+                %3 : int = mod %1 %2;
+                return %3;
+            };
+            """)
+    int test6() {
+        return 5 % 2;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : BinopTest, %1 : double)void -> {
+                %2 : Var<double> = var %1 @"d";
+                %3 : double = var.load %2;
+                %4 : int = constant @"1";
+                %5 : double = conv %4;
+                %6 : double = add %3 %5;
+                var.store %2 %6;
+                %7 : long = constant @"1";
+                %8 : double = conv %7;
+                %9 : double = var.load %2;
+                %10 : double = add %8 %9;
+                var.store %2 %10;
+                %11 : double = var.load %2;
+                %12 : long = constant @"1";
+                %13 : double = conv %12;
+                %14 : double = sub %11 %13;
+                var.store %2 %14;
+                %15 : int = constant @"1";
+                %16 : double = conv %15;
+                %17 : double = var.load %2;
+                %18 : double = sub %16 %17;
+                var.store %2 %18;
+                %19 : double = var.load %2;
+                %20 : int = constant @"1";
+                %21 : double = conv %20;
+                %22 : double = mul %19 %21;
+                var.store %2 %22;
+                %23 : long = constant @"1";
+                %24 : double = conv %23;
+                %25 : double = var.load %2;
+                %26 : double = mul %24 %25;
+                var.store %2 %26;
+                %27 : double = var.load %2;
+                %28 : long = constant @"1";
+                %29 : double = conv %28;
+                %30 : double = div %27 %29;
+                var.store %2 %30;
+                %31 : int = constant @"1";
+                %32 : double = conv %31;
+                %33 : double = var.load %2;
+                %34 : double = div %32 %33;
+                var.store %2 %34;
+                %35 : double = var.load %2;
+                %36 : int = constant @"1";
+                %37 : double = conv %36;
+                %38 : double = mod %35 %37;
+                var.store %2 %38;
+                %39 : long = constant @"1";
+                %40 : double = conv %39;
+                %41 : double = var.load %2;
+                %42 : double = mod %40 %41;
+                var.store %2 %42;
+                %43 : int = constant @"-1";
+                %44 : double = conv %43;
+                var.store %2 %44;
+                return;
+            };
+            """)
+    void test7(double d) {
+        d = d + 1;
+        d = 1L + d;
+
+        d = d - 1L;
+        d = 1 - d;
+
+        d = d * 1;
+        d = 1L * d;
+
+        d = d / 1L;
+        d = 1 / d;
+
+        d = d % 1;
+        d = 1L % d;
+
+        d = -1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : BinopTest, %1 : double)void -> {
+                %2 : Var<double> = var %1 @"d";
+                %3 : double = var.load %2;
+                %4 : int = constant @"1";
+                %5 : double = conv %4;
+                %6 : double = add %3 %5;
+                var.store %2 %6;
+                %7 : double = var.load %2;
+                %8 : long = constant @"1";
+                %9 : double = conv %8;
+                %10 : double = sub %7 %9;
+                var.store %2 %10;
+                %11 : double = var.load %2;
+                %12 : int = constant @"1";
+                %13 : double = conv %12;
+                %14 : double = mul %11 %13;
+                var.store %2 %14;
+                %15 : double = var.load %2;
+                %16 : long = constant @"1";
+                %17 : double = conv %16;
+                %18 : double = div %15 %17;
+                var.store %2 %18;
+                %19 : double = var.load %2;
+                %20 : int = constant @"1";
+                %21 : double = conv %20;
+                %22 : double = mod %19 %21;
+                var.store %2 %22;
+                return;
+            };
+            """)
+    void test8(double d) {
+        d += 1;
+
+        d -= 1L;
+
+        d *= 1;
+
+        d /= 1L;
+
+        d %= 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : BinopTest, %1 : byte, %2 : byte, %3 : short)void -> {
+                %4 : Var<byte> = var %1 @"a";
+                %5 : Var<byte> = var %2 @"b";
+                %6 : Var<short> = var %3 @"s";
+                %7 : byte = var.load %4;
+                %8 : byte = var.load %5;
+                %9 : byte = add %7 %8;
+                var.store %4 %9;
+                %10 : byte = var.load %4;
+                %11 : short = var.load %6;
+                %12 : byte = conv %11;
+                %13 : byte = div %10 %12;
+                var.store %4 %13;
+                %14 : byte = var.load %4;
+                %15 : double = constant @"3.5";
+                %16 : byte = conv %15;
+                %17 : byte = mul %14 %16;
+                var.store %4 %17;
+                %18 : byte = var.load %4;
+                %19 : byte = var.load %5;
+                %20 : byte = lshl %18 %19;
+                var.store %4 %20;
+                %21 : byte = var.load %4;
+                %22 : int = constant @"1";
+                %23 : byte = conv %22;
+                %24 : byte = ashr %21 %23;
+                var.store %4 %24;
+                %25 : byte = var.load %4;
+                %26 : long = constant @"1";
+                %27 : byte = conv %26;
+                %28 : byte = ashr %25 %27;
+                var.store %4 %28;
+                return;
+            };
+            """)
+    void test9(byte a, byte b, short s) {
+        a += b;
+
+        a /= s;
+
+        a *= 3.5d;
+
+        a <<= b;
+
+        a >>= 1;
+
+        a >>= 1L;
+    }
+}
diff a/test/langtools/tools/javac/reflect/BlockTest.java b/test/langtools/tools/javac/reflect/BlockTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/BlockTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.function.Consumer;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with blocks.
+ * @build BlockTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester BlockTest
+ */
+
+public class BlockTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : BlockTest)void -> {
+                java.block ()void -> {
+                    %1 : int = constant @"0";
+                    %2 : Var<int> = var %1 @"i";
+                    yield;
+                };
+                java.block ()void -> {
+                    %3 : int = constant @"1";
+                    %4 : Var<int> = var %3 @"i";
+                    java.block ()void -> {
+                        %5 : int = constant @"2";
+                        %6 : Var<int> = var %5 @"j";
+                        yield;
+                    };
+                    yield;
+                };
+                return;
+            };
+            """)
+    void test1() {
+        {
+            int i = 0;
+        }
+
+        {
+            int i = 1;
+
+            {
+                int j = 2;
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : BlockTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                java.if
+                    ()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^then()void -> {
+                        java.block ()void -> {
+                            %6 : int = var.load %2;
+                            %7 : int = constant @"1";
+                            %8 : int = add %6 %7;
+                            var.store %2 %8;
+                            yield;
+                        };
+                        yield;
+                    }
+                    ^else_if()boolean -> {
+                        %9 : int = var.load %2;
+                        %10 : int = constant @"2";
+                        %11 : boolean = lt %9 %10;
+                        yield %11;
+                    }
+                    ^then()void -> {
+                        java.block ()void -> {
+                            %12 : int = var.load %2;
+                            %13 : int = constant @"2";
+                            %14 : int = add %12 %13;
+                            var.store %2 %14;
+                            yield;
+                        };
+                        yield;
+                    }
+                    ^else()void -> {
+                        java.block ()void -> {
+                            %15 : int = var.load %2;
+                            %16 : int = constant @"3";
+                            %17 : int = add %15 %16;
+                            var.store %2 %17;
+                            yield;
+                        };
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test2(int i) {
+        if (i < 1) {
+            {
+                i += 1;
+            }
+        } else if (i < 2) {
+            {
+                i += 2;
+            }
+        } else {
+            {
+                i += 3;
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : BlockTest)void -> {
+                java.for
+                    ^init()void -> {
+                        yield;
+                    }
+                    ^cond()boolean -> {
+                        %1 : boolean = constant @"true";
+                        yield %1;
+                    }
+                    ^update()void -> {
+                        yield;
+                    }
+                    ^body()void -> {
+                        java.block ()void -> {
+                            %2 : int = constant @"0";
+                            %3 : Var<int> = var %2 @"i";
+                            yield;
+                        };
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test3() {
+        for (;;) {
+            {
+                int i = 0;
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : BlockTest, %1 : int[])void -> {
+                %2 : Var<int[]> = var %1 @"ia";
+                java.enhancedFor
+                    ^expr()int[] -> {
+                        %3 : int[] = var.load %2;
+                        yield %3;
+                    }
+                    ^def(%4 : int)Var<int> -> {
+                        %5 : Var<int> = var %4 @"i";
+                        yield %5;
+                    }
+                    ^body(%6 : Var<int>)void -> {
+                        java.block ()void -> {
+                            %7 : int = var.load %6;
+                            %8 : int = constant @"1";
+                            %9 : int = add %7 %8;
+                            var.store %6 %9;
+                            yield;
+                        };
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test4(int[] ia) {
+        for (int i : ia) {
+            {
+                i++;
+            }
+        }
+   }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : BlockTest)void -> {
+                %1 : java.util.function.Consumer<java.lang.String> = lambda (%2 : java.lang.String)void -> {
+                    %3 : Var<java.lang.String> = var %2 @"s";
+                    java.block ()void -> {
+                        %4 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %5 : java.lang.String = var.load %3;
+                        invoke %4 %5 @"java.io.PrintStream::println(java.lang.String)void";
+                        yield;
+                    };
+                    return;
+                };
+                %6 : Var<java.util.function.Consumer<java.lang.String>> = var %1 @"c";
+                return;
+            };
+            """)
+   void test5() {
+        Consumer<String> c = s -> {
+            {
+                System.out.println(s);
+            }
+        };
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : BlockTest)void -> {
+                 java.if
+                     ()boolean -> {
+                         %1 : boolean = constant @"true";
+                         yield %1;
+                     }
+                     ^then()void -> {
+                         java.block ()void -> {
+                             return;
+                         };
+                         yield;
+                     }
+                     ^else()void -> {
+                         yield;
+                     };
+                 java.if
+                     ()boolean -> {
+                         %2 : boolean = constant @"true";
+                         yield %2;
+                     }
+                     ^then()void -> {
+                         java.block ()void -> {
+                             %3 : java.lang.RuntimeException = new @"func<java.lang.RuntimeException>";
+                             throw %3;
+                         };
+                         yield;
+                     }
+                     ^else()void -> {
+                         yield;
+                     };
+                 return;
+             };
+             """)
+    void test6() {
+        if (true) {
+            {
+                return;
+            }
+        }
+
+        if (true) {
+            {
+                throw new RuntimeException();
+            }
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/BoxingConversionTest.java b/test/langtools/tools/javac/reflect/BoxingConversionTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/BoxingConversionTest.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Supplier;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with boxing conversions.
+ * @build BoxingConversionTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester BoxingConversionTest
+ */
+
+public class BoxingConversionTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : BoxingConversionTest)void -> {
+                  %1 : long = constant @"1";
+                  %2 : java.lang.Long = invoke %1 @"java.lang.Long::valueOf(long)java.lang.Long";
+                  %3 : Var<java.lang.Long> = var %2 @"x";
+                  return;
+            };
+            """)
+    void test1() {
+        Long x = 1L;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : BoxingConversionTest, %1 : java.lang.Long)void -> {
+                %2 : Var<java.lang.Long> = var %1 @"L";
+                %3 : java.lang.Long = var.load %2;
+                %4 : long = invoke %3 @"java.lang.Long::longValue()long";
+                %5 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test2(Long L) {
+        long l = L;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : BoxingConversionTest)void -> {
+                %1 : long = constant @"0";
+                %2 : java.lang.Long = invoke %1 @"java.lang.Long::valueOf(long)java.lang.Long";
+                %3 : Var<java.lang.Object> = var %2 @"o";
+                return;
+            };
+            """)
+    void test3() {
+        Object o = 0L;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : BoxingConversionTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Object = var.load %2;
+                %4 : java.lang.Long = cast %3 @"java.lang.Long";
+                %5 : long = invoke %4 @"java.lang.Long::longValue()long";
+                %6 : Var<long> = var %5 @"l";
+                return;
+            };
+            """)
+    void test4(Object o) {
+        long l = (long)o;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : BoxingConversionTest, %1 : java.lang.Integer)void -> {
+                %2 : Var<java.lang.Integer> = var %1 @"i2";
+                %3 : java.lang.Integer = var.load %2;
+                %4 : int = constant @"1";
+                %5 : int = invoke %3 @"java.lang.Integer::intValue()int";
+                %6 : int = add %5 %4;
+                %7 : java.lang.Integer = invoke %6 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                var.store %2 %7;
+                return;
+            };
+            """)
+    void test5(Integer i2) {
+        i2++;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : BoxingConversionTest, %1 : java.lang.Integer)void -> {
+                %2 : Var<java.lang.Integer> = var %1 @"i2";
+                %3 : java.lang.Integer = var.load %2;
+                %4 : int = constant @"3";
+                %5 : int = invoke %3 @"java.lang.Integer::intValue()int";
+                %6 : int = add %5 %4;
+                %7 : java.lang.Integer = invoke %6 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                var.store %2 %7;
+                return;
+            };
+            """)
+    void test6(Integer i2) {
+        i2 += 3;
+    }
+
+    static class Box {
+        Integer i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : BoxingConversionTest)void -> {
+                %1 : BoxingConversionTest$Box = new @"func<BoxingConversionTest$Box>";
+                %2 : java.lang.Integer = field.load %1 @"BoxingConversionTest$Box::i()java.lang.Integer";
+                %3 : int = constant @"1";
+                %4 : int = invoke %2 @"java.lang.Integer::intValue()int";
+                %5 : int = add %4 %3;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                field.store %1 %6 @"BoxingConversionTest$Box::i()java.lang.Integer";
+                return;
+            };
+            """)
+    void test7() {
+        new Box().i++;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : BoxingConversionTest)void -> {
+                %1 : BoxingConversionTest$Box = new @"func<BoxingConversionTest$Box>";
+                %2 : java.lang.Integer = field.load %1 @"BoxingConversionTest$Box::i()java.lang.Integer";
+                %3 : int = constant @"3";
+                %4 : int = invoke %2 @"java.lang.Integer::intValue()int";
+                %5 : int = add %4 %3;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                field.store %1 %6 @"BoxingConversionTest$Box::i()java.lang.Integer";
+                return;
+            };
+            """)
+    void test8() {
+        new Box().i += 3;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : BoxingConversionTest, %1 : int[], %2 : java.lang.Integer)void -> {
+                %3 : Var<int[]> = var %1 @"ia";
+                %4 : Var<java.lang.Integer> = var %2 @"i";
+                %5 : int[] = var.load %3;
+                %6 : int = constant @"0";
+                %7 : int = array.load %5 %6;
+                %8 : java.lang.Integer = var.load %4;
+                %9 : int = invoke %8 @"java.lang.Integer::intValue()int";
+                %10 : int = add %7 %9;
+                array.store %5 %6 %10;
+                return;
+            };
+            """)
+    void test9(int[] ia, Integer i) {
+        ia[0] += i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : BoxingConversionTest, %1 : boolean, %2 : java.lang.Integer)void -> {
+                %3 : Var<boolean> = var %1 @"cond";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = java.cexpression
+                    ^cond()boolean -> {
+                        %6 : boolean = var.load %3;
+                        yield %6;
+                    }
+                    ^truepart()int -> {
+                        %7 : java.lang.Integer = var.load %4;
+                        %8 : int = invoke %7 @"java.lang.Integer::intValue()int";
+                        yield %8;
+                    }
+                    ^falsepart()int -> {
+                        %9 : int = constant @"2";
+                        yield %9;
+                    };
+                %10 : Var<int> = var %5 @"res";
+                return;
+            };
+            """)
+    void test10(boolean cond, Integer I) {
+        int res = cond ? I : 2;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : BoxingConversionTest, %1 : boolean, %2 : java.lang.Integer)void -> {
+                %3 : Var<boolean> = var %1 @"cond";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = java.cexpression
+                    ^cond()boolean -> {
+                        %6 : boolean = var.load %3;
+                        yield %6;
+                    }
+                    ^truepart()int -> {
+                        %7 : int = constant @"2";
+                        yield %7;
+                    }
+                    ^falsepart()int -> {
+                        %8 : java.lang.Integer = var.load %4;
+                        %9 : int = invoke %8 @"java.lang.Integer::intValue()int";
+                        yield %9;
+                    };
+                %10 : Var<int> = var %5 @"res";
+                return;
+            };
+            """)
+    void test11(boolean cond, Integer I) {
+        int res = cond ? 2 : I;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0 : BoxingConversionTest, %1 : boolean)void -> {
+                 %2 : Var<boolean> = var %1 @"cond";
+                 %3 : int = java.cexpression
+                     ^cond()boolean -> {
+                         %4 : boolean = var.load %2;
+                         yield %4;
+                     }
+                     ^truepart()int -> {
+                         %5 : int = constant @"1";
+                         yield %5;
+                     }
+                     ^falsepart()int -> {
+                         %6 : int = constant @"2";
+                         yield %6;
+                     };
+                 %7 : java.lang.Integer = invoke %3 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                 %8 : Var<java.lang.Integer> = var %7 @"x";
+                 return;
+             };
+             """)
+    void test12(boolean cond) {
+        Integer x = cond ? 1 : 2;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test13" (%0 : BoxingConversionTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.Integer> = lambda ()java.lang.Integer -> {
+                    %2 : int = constant @"1";
+                    %3 : java.lang.Integer = invoke %2 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                    return %3;
+                };
+                %4 : Var<java.util.function.Supplier<java.lang.Integer>> = var %1 @"s";
+                return;
+            };
+            """)
+    void test13() {
+        Supplier<Integer> s = () -> { return 1; };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test14" (%0 : BoxingConversionTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.Integer> = lambda ()java.lang.Integer -> {
+                    %2 : int = constant @"1";
+                    %3 : java.lang.Integer = invoke %2 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                    return %3;
+                };
+                %4 : Var<java.util.function.Supplier<java.lang.Integer>> = var %1 @"s";
+                return;
+            };
+            """)
+    void test14() {
+        Supplier<Integer> s = () -> 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test15" (%0 : BoxingConversionTest, %1 : int, %2 : java.lang.Integer)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = var.load %3;
+                %6 : int = java.switch.expression %5
+                    ^constantCaseLabel(%7 : int)boolean -> {
+                        %8 : int = constant @"1";
+                        %9 : boolean = eq %7 %8;
+                        yield %9;
+                    }
+                    ()int -> {
+                        %10 : java.lang.Integer = var.load %4;
+                        %11 : int = invoke %10 @"java.lang.Integer::intValue()int";
+                        yield %11;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()int -> {
+                        %12 : int = constant @"0";
+                        yield %12;
+                    };
+                %13 : Var<int> = var %6 @"x";
+                return;
+            };
+            """)
+    void test15(int i, Integer I) {
+        int x = switch (i) {
+            case 1 -> I;
+            default -> 0;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test16" (%0 : BoxingConversionTest, %1 : int, %2 : java.lang.Integer)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = var.load %3;
+                %6 : int = java.switch.expression %5
+                    ^constantCaseLabel(%7 : int)boolean -> {
+                        %8 : int = constant @"1";
+                        %9 : boolean = eq %7 %8;
+                        yield %9;
+                    }
+                    ()int -> {
+                        %10 : int = constant @"1";
+                        yield %10;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()int -> {
+                        %11 : java.lang.Integer = var.load %4;
+                        %12 : int = invoke %11 @"java.lang.Integer::intValue()int";
+                        yield %12;
+                    };
+                %13 : Var<int> = var %6 @"x";
+                return;
+            };
+            """)
+    void test16(int i, Integer I) {
+        int x = switch (i) {
+            case 1 -> 1;
+            default -> I;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test17" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : java.lang.Integer = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.Integer -> {
+                        %8 : int = constant @"1";
+                        %9 : java.lang.Integer = invoke %8 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                        yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Integer -> {
+                        %10 : int = constant @"0";
+                        %11 : java.lang.Integer = invoke %10 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                        yield %11;
+                    };
+                %12 : Var<java.lang.Integer> = var %4 @"x";
+                return;
+            };
+            """)
+    void test17(int i) {
+        Integer x = switch (i) {
+            case 1 -> 1;
+            default -> 0;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test18" (%0 : BoxingConversionTest, %1 : int, %2 : java.lang.Integer)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = var.load %3;
+                %6 : int = java.switch.expression %5
+                    ^constantCaseLabel(%7 : int)boolean -> {
+                        %8 : int = constant @"1";
+                        %9 : boolean = eq %7 %8;
+                        yield %9;
+                    }
+                    ()int -> {
+                        %10 : java.lang.Integer = var.load %4;
+                        %11 : int = invoke %10 @"java.lang.Integer::intValue()int";
+                        java.yield %11;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()int -> {
+                        %12 : int = constant @"0";
+                        java.yield %12;
+                    };
+                %13 : Var<int> = var %6 @"x";
+                return;
+            };
+            """)
+    void test18(int i, Integer I) {
+        int x = switch (i) {
+            case 1 -> { yield I; }
+            default -> { yield 0; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test19" (%0 : BoxingConversionTest, %1 : int, %2 : java.lang.Integer)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = var.load %3;
+                %6 : int = java.switch.expression %5
+                    ^constantCaseLabel(%7 : int)boolean -> {
+                        %8 : int = constant @"1";
+                        %9 : boolean = eq %7 %8;
+                        yield %9;
+                    }
+                    ()int -> {
+                        %10 : int = constant @"1";
+                        java.yield %10;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()int -> {
+                        %11 : java.lang.Integer = var.load %4;
+                        %12 : int = invoke %11 @"java.lang.Integer::intValue()int";
+                        java.yield %12;
+                    };
+                %13 : Var<int> = var %6 @"x";
+                return;
+            };
+            """)
+    void test19(int i, Integer I) {
+        int x = switch (i) {
+            case 1 -> { yield 1; }
+            default -> { yield I; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test20" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : java.lang.Integer = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.Integer -> {
+                        %8 : int = constant @"1";
+                        %9 : java.lang.Integer = invoke %8 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                        java.yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Integer -> {
+                        %10 : int = constant @"0";
+                        %11 : java.lang.Integer = invoke %10 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                        java.yield %11;
+                    };
+                %12 : Var<java.lang.Integer> = var %4 @"x";
+                return;
+            };
+            """)
+    void test20(int i) {
+        Integer x = switch (i) {
+            case 1 -> { yield 1; }
+            default -> { yield 0; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test21" (%0 : BoxingConversionTest, %1 : int, %2 : java.lang.Integer)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<java.lang.Integer> = var %2 @"I";
+                %5 : int = var.load %3;
+                %6 : java.lang.Integer = var.load %4;
+                %7 : int = invoke %6 @"java.lang.Integer::intValue()int";
+                %8 : int = add %5 %7;
+                %9 : Var<int> = var %8 @"l";
+                return;
+            };
+            """)
+    void test21(int i, Integer I) {
+        int l = i + I;
+    }
+
+    void m(Integer I) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test22" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : java.lang.Integer = invoke %3 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                invoke %0 %4 @"BoxingConversionTest::m(java.lang.Integer)void";
+                return;
+            };
+            """)
+    void test22(int i) {
+        m(i);
+    }
+
+    void m(int i1, int i2, Integer... I) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test23" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                invoke %0 %3 %4 @"BoxingConversionTest::m(int, int, java.lang.Integer[])void";
+                return;
+            };
+            """)
+    void test23(int i) {
+        m(i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test24" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                invoke %0 %3 %4 %6 @"BoxingConversionTest::m(int, int, java.lang.Integer[])void";
+                return;
+            };
+            """)
+    void test24(int i) {
+        m(i, i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test25" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                %7 : int = var.load %2;
+                %8 : java.lang.Integer = invoke %7 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                invoke %0 %3 %4 %6 %8 @"BoxingConversionTest::m(int, int, java.lang.Integer[])void";
+                return;
+            };
+            """)
+    void test25(int i) {
+        m(i, i, i, i);
+    }
+
+    static class Box2 {
+        Box2(Integer I) { }
+        Box2(int i1, int i2, Integer... Is) { }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test26" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : java.lang.Integer = invoke %3 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                %5 : BoxingConversionTest$Box2 = new %4 @"func<BoxingConversionTest$Box2, java.lang.Integer>";
+                return;
+            };
+            """)
+    void test26(int i) {
+        new Box2(i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test27" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : BoxingConversionTest$Box2 = new %3 %4 @"func<BoxingConversionTest$Box2, int, int, java.lang.Integer[]>";
+                return;
+            };
+            """)
+    void test27(int i) {
+        new Box2(i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test28" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                %7 : BoxingConversionTest$Box2 = new %3 %4 %6 @"func<BoxingConversionTest$Box2, int, int, java.lang.Integer[]>";
+                return;
+            };
+            """)
+    void test28(int i) {
+        new Box2(i, i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test29" (%0 : BoxingConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : java.lang.Integer = invoke %5 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                %7 : int = var.load %2;
+                %8 : java.lang.Integer = invoke %7 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                %9 : BoxingConversionTest$Box2 = new %3 %4 %6 %8 @"func<BoxingConversionTest$Box2, int, int, java.lang.Integer[]>";
+                return;
+            };
+            """)
+    void test29(int i) {
+        new Box2(i, i, i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test30" (%0 : java.lang.Integer)void -> {
+                  %1 : Var<java.lang.Integer> = var %0 @"i";
+                  %2 : java.lang.Integer = var.load %1;
+                  %3 : int = invoke %2 @"java.lang.Integer::intValue()int";
+                  %4 : int = neg %3;
+                  %5 : Var<int> = var %4 @"j";
+                  return;
+            };
+            """)
+    static void test30(Integer i) {
+        int j = -i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test31" (%0 : int)void -> {
+                  %1 : Var<int> = var %0 @"i";
+                  %2 : int = var.load %1;
+                  %3 : int = neg %2;
+                  %4 : java.lang.Integer = invoke %3 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                  %5 : Var<java.lang.Integer> = var %4 @"j";
+                  return;
+            };
+            """)
+    static void test31(int i) {
+        Integer j = -i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test32" (%0 : boolean)void -> {
+                  %1 : Var<boolean> = var %0 @"i";
+                  %2 : boolean = var.load %1;
+                  %3 : boolean = not %2;
+                  %4 : java.lang.Boolean = invoke %3 @"java.lang.Boolean::valueOf(boolean)java.lang.Boolean";
+                  %5 : Var<java.lang.Boolean> = var %4 @"j";
+                  return;
+            };
+            """)
+    static void test32(boolean i) {
+        Boolean j = !i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test33" (%0 : java.lang.Boolean)void -> {
+                  %1 : Var<java.lang.Boolean> = var %0 @"i";
+                  %2 : java.lang.Boolean = var.load %1;
+                  %3 : boolean = invoke %2 @"java.lang.Boolean::booleanValue()boolean";
+                  %4 : boolean = not %3;
+                  %5 : Var<boolean> = var %4 @"j";
+                  return;
+            };
+            """)
+    static void test33(Boolean i) {
+        boolean j = !i;
+    }
+}
diff a/test/langtools/tools/javac/reflect/BreakContinueTest.java b/test/langtools/tools/javac/reflect/BreakContinueTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/BreakContinueTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with for loops.
+ * @build BreakContinueTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester BreakContinueTest
+ */
+
+
+public class BreakContinueTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : BreakContinueTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : int = var.load %3;
+                        %5 : int = constant @"10";
+                        %6 : boolean = lt %4 %5;
+                        yield %6;
+                    }
+                    ^update(%7 : Var<int>)void -> {
+                        %8 : int = var.load %7;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %7 %10;
+                        yield;
+                    }
+                    ^body(%11 : Var<int>)void -> {
+                        java.if
+                            ()boolean -> {
+                                %12 : boolean = constant @"true";
+                                yield %12;
+                            }
+                            ^then()void -> {
+                                java.continue;
+                            }
+                            ^else()void -> {
+                                yield;
+                            };
+                        java.if
+                            ()boolean -> {
+                                %13 : boolean = constant @"true";
+                                yield %13;
+                            }
+                            ^then()void -> {
+                                java.break;
+                            }
+                            ^else()void -> {
+                                yield;
+                            };
+                        java.for
+                            ^init()Var<int> -> {
+                                %14 : int = constant @"0";
+                                %15 : Var<int> = var %14 @"j";
+                                yield %15;
+                            }
+                            ^cond(%16 : Var<int>)boolean -> {
+                                %17 : int = var.load %16;
+                                %18 : int = constant @"10";
+                                %19 : boolean = lt %17 %18;
+                                yield %19;
+                            }
+                            ^update(%20 : Var<int>)void -> {
+                                %21 : int = var.load %20;
+                                %22 : int = constant @"1";
+                                %23 : int = add %21 %22;
+                                var.store %20 %23;
+                                yield;
+                            }
+                            ^body(%24 : Var<int>)void -> {
+                                java.if
+                                    ()boolean -> {
+                                        %25 : boolean = constant @"true";
+                                        yield %25;
+                                    }
+                                    ^then()void -> {
+                                        java.continue;
+                                    }
+                                    ^else()void -> {
+                                        yield;
+                                    };
+                                java.if
+                                    ()boolean -> {
+                                        %26 : boolean = constant @"true";
+                                        yield %26;
+                                    }
+                                    ^then()void -> {
+                                        java.break;
+                                    }
+                                    ^else()void -> {
+                                        yield;
+                                    };
+                                java.continue;
+                            };
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test1() {
+        for (int i = 0; i < 10; i++) {
+            if (true) {
+                continue;
+            }
+            if (true) {
+                break;
+            }
+            for (int j = 0; j < 10; j++) {
+                if (true) {
+                    continue;
+                }
+                if (true) {
+                    break;
+                }
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : BreakContinueTest)void -> {
+                java.labeled ()void -> {
+                    %1 : java.lang.String = constant @"outer";
+                    java.for
+                        ^init()Var<int> -> {
+                            %2 : int = constant @"0";
+                            %3 : Var<int> = var %2 @"i";
+                            yield %3;
+                        }
+                        ^cond(%4 : Var<int>)boolean -> {
+                            %5 : int = var.load %4;
+                            %6 : int = constant @"10";
+                            %7 : boolean = lt %5 %6;
+                            yield %7;
+                        }
+                        ^update(%8 : Var<int>)void -> {
+                            %9 : int = var.load %8;
+                            %10 : int = constant @"1";
+                            %11 : int = add %9 %10;
+                            var.store %8 %11;
+                            yield;
+                        }
+                        ^body(%12 : Var<int>)void -> {
+                            java.if
+                                ()boolean -> {
+                                    %13 : boolean = constant @"true";
+                                    yield %13;
+                                }
+                                ^then()void -> {
+                                    java.continue %1;
+                                }
+                                ^else()void -> {
+                                    yield;
+                                };
+                            java.if
+                                ()boolean -> {
+                                    %14 : boolean = constant @"true";
+                                    yield %14;
+                                }
+                                ^then()void -> {
+                                    java.break %1;
+                                }
+                                ^else()void -> {
+                                    yield;
+                                };
+                            java.labeled ()void -> {
+                                %15 : java.lang.String = constant @"inner";
+                                java.for
+                                    ^init()Var<int> -> {
+                                        %16 : int = constant @"0";
+                                        %17 : Var<int> = var %16 @"j";
+                                        yield %17;
+                                    }
+                                    ^cond(%18 : Var<int>)boolean -> {
+                                        %19 : int = var.load %18;
+                                        %20 : int = constant @"10";
+                                        %21 : boolean = lt %19 %20;
+                                        yield %21;
+                                    }
+                                    ^update(%22 : Var<int>)void -> {
+                                        %23 : int = var.load %22;
+                                        %24 : int = constant @"1";
+                                        %25 : int = add %23 %24;
+                                        var.store %22 %25;
+                                        yield;
+                                    }
+                                    ^body(%26 : Var<int>)void -> {
+                                        java.if
+                                            ()boolean -> {
+                                                %27 : boolean = constant @"true";
+                                                yield %27;
+                                            }
+                                            ^then()void -> {
+                                                java.continue;
+                                            }
+                                            ^else()void -> {
+                                                yield;
+                                            };
+                                        java.if
+                                            ()boolean -> {
+                                                %28 : boolean = constant @"true";
+                                                yield %28;
+                                            }
+                                            ^then()void -> {
+                                                java.break;
+                                            }
+                                            ^else()void -> {
+                                                yield;
+                                            };
+                                        java.if
+                                            ()boolean -> {
+                                                %29 : boolean = constant @"true";
+                                                yield %29;
+                                            }
+                                            ^then()void -> {
+                                                java.continue %1;
+                                            }
+                                            ^else()void -> {
+                                                yield;
+                                            };
+                                        java.if
+                                            ()boolean -> {
+                                                %30 : boolean = constant @"true";
+                                                yield %30;
+                                            }
+                                            ^then()void -> {
+                                                java.break %1;
+                                            }
+                                            ^else()void -> {
+                                                yield;
+                                            };
+                                        java.continue;
+                                    };
+                                yield;
+                            };
+                            java.continue;
+                        };
+                    yield;
+                };
+                return;
+            };
+            """)
+    void test2() {
+        outer:
+        for (int i = 0; i < 10; i++) {
+            if (true) {
+                continue outer;
+            }
+            if (true) {
+                break outer;
+            }
+            inner:
+            for (int j = 0; j < 10; j++) {
+                if (true) {
+                    continue;
+                }
+                if (true) {
+                    break;
+                }
+                if (true) {
+                    continue outer;
+                }
+                if (true) {
+                    break outer;
+                }
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : BreakContinueTest)void -> {
+                java.labeled ()void -> {
+                    %1 : java.lang.String = constant @"b1";
+                    java.block ()void -> {
+                        java.labeled ()void -> {
+                            %2 : java.lang.String = constant @"b2";
+                            java.block ()void -> {
+                                java.if
+                                    ()boolean -> {
+                                        %3 : boolean = constant @"true";
+                                        yield %3;
+                                    }
+                                    ^then()void -> {
+                                        java.break %1;
+                                    }
+                                    ^else()void -> {
+                                        yield;
+                                    };
+                                java.if
+                                    ()boolean -> {
+                                        %4 : boolean = constant @"true";
+                                        yield %4;
+                                    }
+                                    ^then()void -> {
+                                        java.break %2;
+                                    }
+                                    ^else()void -> {
+                                        yield;
+                                    };
+                                yield;
+                            };
+                            yield;
+                        };
+                        yield;
+                    };
+                    yield;
+                };
+                return;
+            };
+            """)
+    void test3() {
+        b1:
+        {
+            b2:
+            {
+                if (true) {
+                    break b1;
+                }
+                if (true) {
+                    break b2;
+                }
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : BreakContinueTest)void -> {
+                java.labeled ()void -> {
+                    %1 : java.lang.String = constant @"b";
+                    java.break %1;
+                };
+                %2 : int = constant @"0";
+                %3 : Var<int> = var %2 @"i";
+                java.labeled ()void -> {
+                    %4 : java.lang.String = constant @"b";
+                    %5 : int = var.load %3;
+                    %6 : int = constant @"1";
+                    %7 : int = add %5 %6;
+                    var.store %3 %7;
+                    yield;
+                };
+                java.labeled ()void -> {
+                    %8 : java.lang.String = constant @"a";
+                    java.labeled ()void -> {
+                        %9 : java.lang.String = constant @"b";
+                        java.block ()void -> {
+                            yield;
+                        };
+                        yield;
+                    };
+                    yield;
+                };
+                return;
+            };
+            """)
+    void test4() {
+        b:
+        break b;
+
+        int i = 0;
+        b:
+        i++;
+
+        a: b: {
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/CastInstanceOfTest.java b/test/langtools/tools/javac/reflect/CastInstanceOfTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/CastInstanceOfTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.Collection;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with cast expressions.
+ * @build CastInstanceOfTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester CastInstanceOfTest
+ */
+
+public class CastInstanceOfTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : CastInstanceOfTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Object = var.load %2;
+                %4 : java.lang.String = cast %3 @"java.lang.String";
+                %5 : Var<java.lang.String> = var %4 @"s";
+                %6 : java.lang.String = var.load %5;
+                %7 : Var<java.lang.String> = var %6 @"ss";
+                return;
+            };
+            """)
+    void test1(Object o) {
+        String s = (String) o;
+        String ss = (String) s;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : CastInstanceOfTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Object = var.load %2;
+                %4 : java.util.List<java.lang.String> = cast %3 @"java.util.List";
+                %5 : Var<java.util.List<java.lang.String>> = var %4 @"l";
+                %6 : java.util.List<java.lang.String> = var.load %5;
+                %7 : Var<java.util.Collection<java.lang.String>> = var %6 @"c1";
+                %8 : java.util.List<java.lang.String> = var.load %5;
+                %9 : Var<java.util.Collection<java.lang.String>> = var %8 @"c2";
+                return;
+            };
+            """)
+    void test2(Object o) {
+        List<String> l = (List<String>) o;
+        Collection<String> c1 = (List<String>) l;
+        Collection<String> c2 = (Collection) l;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : CastInstanceOfTest, %1 : java.util.List<java.lang.String>)void -> {
+                %2 : Var<java.util.List<java.lang.String>> = var %1 @"l";
+                %3 : java.util.List<java.lang.String> = var.load %2;
+                %4 : Var<java.util.List> = var %3 @"raw";
+                %5 : java.util.List = var.load %4;
+                %6 : Var<java.util.List<java.lang.Number>> = var %5 @"ln";
+                return;
+            };
+            """)
+    void test3(List<String> l) {
+        List raw = l;
+        List<Number> ln = raw;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : CastInstanceOfTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = conv %3;
+                %5 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test4(int i) {
+        long l = (int) i;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : CastInstanceOfTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Object = var.load %2;
+                %4 : boolean = instanceof %3 @"java.lang.String";
+                %5 : Var<boolean> = var %4 @"b";
+                return;
+            };
+            """)
+    void test5(Object o) {
+        boolean b = o instanceof String;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : CastInstanceOfTest, %1 : java.util.List<java.lang.Object>)void -> {
+                %2 : Var<java.util.List<java.lang.Object>> = var %1 @"l";
+                %3 : java.util.List<java.lang.Object> = var.load %2;
+                %4 : int = constant @"0";
+                %5 : java.lang.Object = invoke %3 %4 @"java.util.List::get(int)java.lang.Object";
+                %6 : boolean = instanceof %5 @"java.lang.String";
+                %7 : Var<boolean> = var %6 @"b";
+                return;
+            };
+            """)
+    void test6(List<Object> l) {
+        boolean b = l.get(0) instanceof String;
+    }
+}
diff a/test/langtools/tools/javac/reflect/CodeModelSameInstanceTest.java b/test/langtools/tools/javac/reflect/CodeModelSameInstanceTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/CodeModelSameInstanceTest.java
@@ -0,0 +1,32 @@
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.lang.runtime.CodeReflection;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.IntStream;
+
+/*
+ * @test
+ * @summary test that invoking Method::getCodeModel returns the same instance.
+ * @run testng CodeModelSameInstanceTest
+ */
+public class CodeModelSameInstanceTest {
+
+    @CodeReflection
+    static int add(int a, int b) {
+        return a + b;
+    }
+
+    @Test
+    void test() {
+        Optional<Method> om = Arrays.stream(this.getClass().getDeclaredMethods()).filter(m -> m.getName().equals("add"))
+                .findFirst();
+        Method m = om.get();
+        Object[] codeModels = IntStream.range(0, 1024).mapToObj(_ -> m.getCodeModel()).toArray();
+        for (int i = 1; i < codeModels.length; i++) {
+            Assert.assertSame(codeModels[i], codeModels[i-1]);
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/CodeReflectionTester.java b/test/langtools/tools/javac/reflect/CodeReflectionTester.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/CodeReflectionTester.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.code.*;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.writer.OpWriter;
+import java.lang.runtime.CodeReflection;
+
+import static java.lang.reflect.code.op.CoreOp._return;
+import static java.lang.reflect.code.op.CoreOp.func;
+import static java.lang.reflect.code.type.FunctionType.VOID;
+
+public class CodeReflectionTester {
+
+    public static void main(String[] args) throws ReflectiveOperationException {
+        if (args.length != 1) {
+            System.err.println("Usage: CodeReflectionTester <classname>");
+            System.exit(1);
+        }
+        Class<?> clazz = Class.forName(args[0]);
+        for (Method m : clazz.getDeclaredMethods()) {
+            check(m);
+        }
+        for (Field f : clazz.getDeclaredFields()) {
+            check(f);
+        }
+    }
+
+    static void check(Method method) throws ReflectiveOperationException {
+        if (!method.isAnnotationPresent(CodeReflection.class)) return;
+        Field field = method.getDeclaringClass().getDeclaredField(method.getName() + "$op");
+        String found = canonicalizeModel(method, (String) field.get(null));
+        IR ir = method.getAnnotation(IR.class);
+        if (ir == null) {
+            throw new AssertionError("No @IR annotation found on reflective method");
+        }
+        String expected = canonicalizeModel(method, ir.value());
+        if (!found.equals(expected)) {
+            throw new AssertionError(String.format("Bad IR\nFound:\n%s\n\nExpected:\n%s", found, expected));
+        }
+    }
+
+    static void check(Field field) throws ReflectiveOperationException {
+        IR ir = field.getAnnotation(IR.class);
+        if (ir == null) return;
+        if (field.getType().equals(Quoted.class)) {
+            // transitional
+            Quoted quoted = (Quoted) field.get(null);
+            String found = canonicalizeModel(field, getModelOfQuotedOp(quoted));
+            String expected = canonicalizeModel(field, ir.value());
+            if (!found.equals(expected)) {
+                throw new AssertionError(String.format("Bad IR\nFound:\n%s\n\nExpected:\n%s", found, expected));
+            }
+        } else if (Quotable.class.isAssignableFrom(field.getType())) {
+            Quotable quotable = (Quotable) field.get(null);
+            String found = canonicalizeModel(field, getModelOfQuotedOp(quotable.quoted()));
+            String expected = canonicalizeModel(field, ir.value());
+            if (!found.equals(expected)) {
+                throw new AssertionError(String.format("Bad IR\nFound:\n%s\n\nExpected:\n%s", found, expected));
+            }
+        } else {
+            throw new AssertionError("Field annotated with @IR should be of a quotable type (Quoted/Quotable)");
+        }
+    }
+
+    // serializes dropping location information, parses, and then serializes, dropping location information
+    static String canonicalizeModel(Member m, Op o) {
+        return canonicalizeModel(m, serialize(o));
+    }
+
+    // parses, and then serializes, dropping location information
+    static String canonicalizeModel(Member m, String d) {
+        Op o;
+        try {
+            o = OpParser.fromString(ExtendedOp.FACTORY, d).get(0);
+        } catch (Exception e) {
+            throw new IllegalStateException(m.toString(), e);
+        }
+        return serialize(o);
+    }
+
+    // serializes, dropping location information
+    static String serialize(Op o) {
+        StringWriter w = new StringWriter();
+        OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
+        return w.toString();
+    }
+
+    static Op getModelOfQuotedOp(Quoted quoted) {
+        return func("f", VOID).body(fblock -> {
+            CopyContext cc = fblock.context();
+            for (Value cv : quoted.capturedValues().keySet()) {
+                Block.Parameter p = fblock.parameter(cv.type());
+                cc.mapValue(cv, p);
+            }
+
+            Op qOp = quoted.op();
+            // Associate the quoted ops ancestor body's entry block
+            // with the function's entry block, thereby ensuring that
+            // captured values mapped to the function's parameters
+            // are reachable
+            cc.mapBlock(qOp.ancestorBody().entryBlock(), fblock);
+            fblock.op(qOp);
+
+            fblock.op(_return());
+        });
+    }
+}
diff a/test/langtools/tools/javac/reflect/ConditionalAndOrTest.java b/test/langtools/tools/javac/reflect/ConditionalAndOrTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ConditionalAndOrTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with conditional and/or expressions.
+ * @build ConditionalAndOrTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ConditionalAndOrTest
+ */
+
+public class ConditionalAndOrTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : ConditionalAndOrTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : boolean = java.cand
+                    ()boolean -> {
+                        %4 : int = var.load %2;
+                        %5 : int = constant @"1";
+                        %6 : boolean = gt %4 %5;
+                        yield %6;
+                    }
+                    ()boolean -> {
+                        %7 : int = var.load %2;
+                        %8 : int = constant @"10";
+                        %9 : boolean = lt %7 %8;
+                        yield %9;
+                    };
+                %10 : Var<boolean> = var %3 @"b";
+                return;
+            };
+            """)
+    void test1(int i) {
+        boolean b = i > 1 && i < 10;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : ConditionalAndOrTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : boolean = java.cor
+                    ()boolean -> {
+                        %4 : int = var.load %2;
+                        %5 : int = constant @"1";
+                        %6 : boolean = gt %4 %5;
+                        yield %6;
+                    }
+                    ()boolean -> {
+                        %7 : int = var.load %2;
+                        %8 : int = constant @"10";
+                        %9 : boolean = lt %7 %8;
+                        yield %9;
+                    };
+                %10 : Var<boolean> = var %3 @"b";
+                return;
+            };
+            """)
+    void test2(int i) {
+        boolean b = i > 1 || i < 10;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : ConditionalAndOrTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : boolean = java.cor
+                    ()boolean -> {
+                        %4 : boolean = java.cand
+                            ()boolean -> {
+                                %5 : int = var.load %2;
+                                %6 : int = constant @"1";
+                                %7 : boolean = gt %5 %6;
+                                yield %7;
+                            }
+                            ()boolean -> {
+                                %8 : int = var.load %2;
+                                %9 : int = constant @"10";
+                                %10 : boolean = lt %8 %9;
+                                yield %10;
+                            };
+                        yield %4;
+                    }
+                    ()boolean -> {
+                        %11 : int = var.load %2;
+                        %12 : int = constant @"100";
+                        %13 : boolean = eq %11 %12;
+                        yield %13;
+                    };
+                %14 : Var<boolean> = var %3 @"b";
+                return;
+            };
+            """)
+    void test3(int i) {
+        boolean b = i > 1 && i < 10 || i == 100;
+    }
+}
diff a/test/langtools/tools/javac/reflect/ConditionalExpressionTest.java b/test/langtools/tools/javac/reflect/ConditionalExpressionTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ConditionalExpressionTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+import java.util.function.Supplier;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with conditional expressions.
+ * @build ConditionalExpressionTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ConditionalExpressionTest
+ */
+
+public class ConditionalExpressionTest {
+
+    @IR("""
+            func @"test1" (%0 : ConditionalExpressionTest, %1 : boolean, %2 : int, %3 : int)void -> {
+                %4 : Var<boolean> = var %1 @"b";
+                %5 : Var<int> = var %2 @"x";
+                %6 : Var<int> = var %3 @"y";
+                %7 : int = java.cexpression
+                    ^cond()boolean -> {
+                        %8 : boolean = var.load %4;
+                        yield %8;
+                    }
+                    ^truepart()int -> {
+                        %9 : int = var.load %5;
+                        yield %9;
+                    }
+                    ^falsepart()int -> {
+                        %10 : int = var.load %6;
+                        yield %10;
+                    };
+                %11 : Var<int> = var %7 @"z";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test1(boolean b, int x, int y) {
+        int z = b ? x : y;
+    }
+
+    @IR("""
+            func @"test2" (%0 : ConditionalExpressionTest, %1 : boolean, %2 : int, %3 : double)void -> {
+                %4 : Var<boolean> = var %1 @"b";
+                %5 : Var<int> = var %2 @"x";
+                %6 : Var<double> = var %3 @"y";
+                %7 : double = java.cexpression
+                    ^cond()boolean -> {
+                        %8 : boolean = var.load %4;
+                        %9 : boolean = not %8;
+                        yield %9;
+                    }
+                    ^truepart()double -> {
+                        %10 : int = var.load %5;
+                        %11 : double = conv %10;
+                        yield %11;
+                    }
+                    ^falsepart()double -> {
+                        %12 : double = var.load %6;
+                        yield %12;
+                    };
+                %13 : Var<double> = var %7 @"z";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test2(boolean b, int x, double y) {
+        double z = !b ? x : y;
+    }
+
+    @IR("""
+            func @"test3" (%0 : ConditionalExpressionTest, %1 : boolean, %2 : int, %3 : double)void -> {
+                %4 : Var<boolean> = var %1 @"b";
+                %5 : Var<int> = var %2 @"x";
+                %6 : Var<double> = var %3 @"y";
+                %7 : java.util.function.Supplier<java.lang.Double> = java.cexpression
+                    ^cond()boolean -> {
+                        %8 : boolean = var.load %4;
+                        yield %8;
+                    }
+                    ^truepart()java.util.function.Supplier<java.lang.Double> -> {
+                        %9 : java.util.function.Supplier<java.lang.Double> = lambda ()java.lang.Double -> {
+                            %10 : int = var.load %5;
+                            %11 : double = conv %10;
+                            %12 : java.lang.Double = invoke %11 @"java.lang.Double::valueOf(double)java.lang.Double";
+                            return %12;
+                        };
+                        yield %9;
+                    }
+                    ^falsepart()java.util.function.Supplier<java.lang.Double> -> {
+                        %13 : java.util.function.Supplier<java.lang.Double> = lambda ()java.lang.Double -> {
+                            %14 : double = var.load %6;
+                            %15 : java.lang.Double = invoke %14 @"java.lang.Double::valueOf(double)java.lang.Double";
+                            return %15;
+                        };
+                        yield %13;
+                    };
+                %16 : Var<java.util.function.Supplier<java.lang.Double>> = var %7 @"z";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test3(boolean b, int x, double y) {
+        Supplier<Double> z = b ? () -> (double) x : () -> y;
+    }
+
+    @IR("""
+            func @"test4" (%0 : ConditionalExpressionTest, %1 : boolean, %2 : boolean, %3 : int, %4 : double, %5 : double)void -> {
+                %6 : Var<boolean> = var %1 @"b1";
+                %7 : Var<boolean> = var %2 @"b2";
+                %8 : Var<int> = var %3 @"x";
+                %9 : Var<double> = var %4 @"y";
+                %10 : Var<double> = var %5 @"z";
+                %11 : double = java.cexpression
+                    ^cond()boolean -> {
+                        %12 : boolean = var.load %6;
+                        yield %12;
+                    }
+                    ^truepart()double -> {
+                        %13 : double = java.cexpression
+                            ^cond()boolean -> {
+                                %14 : boolean = var.load %7;
+                                yield %14;
+                            }
+                            ^truepart()double -> {
+                                %15 : int = var.load %8;
+                                %16 : double = conv %15;
+                                yield %16;
+                            }
+                            ^falsepart()double -> {
+                                %17 : double = var.load %9;
+                                yield %17;
+                            };
+                        yield %13;
+                    }
+                    ^falsepart()double -> {
+                        %18 : double = var.load %10;
+                        yield %18;
+                    };
+                %19 : Var<double> = var %11 @"r";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test4(boolean b1, boolean b2, int x, double y, double z) {
+        double r = b1 ? (b2 ? x : y) : z;
+    }
+
+}
diff a/test/langtools/tools/javac/reflect/ConstantsTest.java b/test/langtools/tools/javac/reflect/ConstantsTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ConstantsTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with constant values.
+ * @build ConstantsTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ConstantsTest
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Function;
+
+public class ConstantsTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.String = constant @"";
+                %2 : Var<java.lang.String> = var %1 @"s";
+                return;
+            };
+            """)
+    void test1() {
+        String s = "";
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.String = constant @"Hello World";
+                %2 : Var<java.lang.String> = var %1 @"s";
+                return;
+            };
+            """)
+    void test2() {
+        String s = "Hello World";
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : Var<java.lang.String> = var %1 @"s";
+                return;
+            };
+            """)
+    void test3() {
+        String s = null;
+    }
+
+    @IR("""
+            func @"test4" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"java.util.function.Function";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test4() {
+        Class<?> s = Function.class;
+    }
+
+    @IR("""
+            func @"test5" (%0 : ConstantsTest)void -> {
+                %1 : int = constant @"42";
+                %2 : byte = conv %1;
+                %3 : Var<byte> = var %2 @"v";
+                %4 : int = constant @"-42";
+                %5 : byte = conv %4;
+                var.store %3 %5;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test5() {
+        byte v = 42;
+        v = -42;
+    }
+
+    @IR("""
+            func @"test6" (%0 : ConstantsTest)void -> {
+                %1 : int = constant @"42";
+                %2 : short = conv %1;
+                %3 : Var<short> = var %2 @"v";
+                %4 : int = constant @"-42";
+                %5 : short = conv %4;
+                var.store %3 %5;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test6() {
+        short v = 42;
+        v = -42;
+    }
+
+    @IR("""
+            func @"test7" (%0 : ConstantsTest)void -> {
+                %1 : int = constant @"42";
+                %2 : Var<int> = var %1 @"v";
+                %3 : int = constant @"-42";
+                var.store %2 %3;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test7() {
+        int v = 42;
+        v = -42;
+    }
+
+    @IR("""
+            func @"test8" (%0 : ConstantsTest)void -> {
+                %1 : long = constant @"42";
+                %2 : Var<long> = var %1 @"v";
+                %3 : long = constant @"-42";
+                var.store %2 %3;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test8() {
+        long v = 42L;
+        v = -42L;
+    }
+
+    @IR("""
+            func @"test9" (%0 : ConstantsTest)void -> {
+                %1 : float = constant @"42.0";
+                %2 : Var<float> = var %1 @"v";
+                %3 : float = constant @"42.0";
+                %4 : float = neg %3;
+                var.store %2 %4;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test9() {
+        float v = 42.0f;
+        v = -42.0f;
+    }
+
+    @IR("""
+            func @"test10" (%0 : ConstantsTest)void -> {
+                %1 : double = constant @"42.0";
+                %2 : Var<double> = var %1 @"v";
+                %3 : double = constant @"42.0";
+                %4 : double = neg %3;
+                var.store %2 %4;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test10() {
+        double v = 42.0;
+        v = -42.0;
+    }
+
+    @IR("""
+            func @"test11" (%0 : ConstantsTest)void -> {
+                %1 : char = constant @"a";
+                %2 : Var<char> = var %1 @"v";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test11() {
+        char v = 'a';
+    }
+
+    @IR("""
+            func @"test12" (%0 : ConstantsTest)void -> {
+                %1 : boolean = constant @"true";
+                %2 : Var<boolean> = var %1 @"b";
+                %3 : boolean = constant @"false";
+                var.store %2 %3;
+                return;
+            };
+            """)
+    @CodeReflection
+    void test12() {
+        boolean b = true;
+        b = false;
+    }
+
+    @IR("""
+            func @"test13" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"float";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test13() {
+        Class<?> s = float.class;
+    }
+
+    @IR("""
+            func @"test14" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"java.lang.String[]";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test14() {
+        Class<?> s = String[].class;
+    }
+
+    @IR("""
+            func @"test15" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"java.lang.String[][]";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test15() {
+        Class<?> s = String[][].class;
+    }
+
+    @IR("""
+            func @"test16" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"java.lang.String[][][]";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test16() {
+        Class<?> s = String[][][].class;
+    }
+
+    @IR("""
+            func @"test17" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"boolean[]";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test17() {
+        Class<?> s = boolean[].class;
+    }
+
+    @IR("""
+            func @"test18" (%0 : ConstantsTest)void -> {
+                %1 : java.lang.Class = constant @"boolean[][][]";
+                %2 : Var<java.lang.Class<+<java.lang.Object>>> = var %1 @"s";
+                return;
+            };
+            """)
+    @CodeReflection
+    void test18() {
+        Class<?> s = boolean[][][].class;
+    }
+}
diff a/test/langtools/tools/javac/reflect/DenotableTypesTest.java b/test/langtools/tools/javac/reflect/DenotableTypesTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/DenotableTypesTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for non-denotable types in IR type descriptors
+ * @build DenotableTypesTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester DenotableTypesTest
+ */
+
+public class DenotableTypesTest {
+    static <X extends Number & Runnable> X m1(X x) { return null; }
+    @CodeReflection
+    @IR("""
+            func @"test1" ()void -> {
+                  %0 : java.lang.Number = constant @null;
+                  %1 : java.lang.Number = invoke %0 @"DenotableTypesTest::m1(java.lang.Number)java.lang.Number";
+                  return;
+            };
+            """)
+    static void test1() {
+        m1(null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" ()void -> {
+                  %0 : int = constant @"1";
+                  %1 : java.lang.Integer = invoke %0 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                  %2 : double = constant @"3.0";
+                  %3 : java.lang.Double = invoke %2 @"java.lang.Double::valueOf(double)java.lang.Double";
+                  %4 : java.util.List<+<java.lang.Number>> = invoke %1 %3 @"java.util.List::of(java.lang.Object, java.lang.Object)java.util.List";
+                  return;
+            };
+            """)
+    static void test2() {
+        List.of(1, 3d); // infinite type! (List<Object & Serializable & Comparable<...>>)
+    }
+
+    static <X extends Throwable> X m2(X x) throws X { return null; }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" ()void -> {
+                %0 : java.lang.RuntimeException = constant @null;
+                %1 : java.lang.RuntimeException = invoke %0 @"DenotableTypesTest::m2(java.lang.Throwable)java.lang.Throwable";
+                return;
+            };
+            """)
+    static void test3() { // @@@ cast?
+        m2(null);
+    }
+
+    interface A { }
+    interface B { }
+    static class C implements A, B { }
+    static class D implements A, B { }
+
+    static <Z> Z pick(Z z1, Z z2) { return null; }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" ()void -> {
+                  %0 : java.lang.Object = constant @null;
+                  %1 : DenotableTypesTest$C = cast %0 @"DenotableTypesTest$C";
+                  %2 : java.lang.Object = constant @null;
+                  %3 : DenotableTypesTest$D = cast %2 @"DenotableTypesTest$D";
+                  %4 : DenotableTypesTest$A = invoke %1 %3 @"DenotableTypesTest::pick(java.lang.Object, java.lang.Object)java.lang.Object";
+                  return;
+            };
+            """)
+    static void test4() { // @@@ cast?
+        pick((C)null, (D)null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" ()void -> {
+                  %0 : java.util.List<+<java.lang.Number>> = constant @null;
+                  %1 : Var<java.util.List<+<java.lang.Number>>> = var %0 @"l";
+                  %2 : java.util.List<+<java.lang.Number>> = var.load %1;
+                  %3 : int = constant @"0";
+                  %4 : java.lang.Number = invoke %2 %3 @"java.util.List::get(int)java.lang.Object";
+                  return;
+            };
+            """)
+    static void test5() { // @@@ cast?
+        List<? extends Number> l = null;
+        l.get(0);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" ()void -> {
+                  %0 : java.util.List<-<java.lang.Number>> = constant @null;
+                  %1 : Var<java.util.List<-<java.lang.Number>>> = var %0 @"l";
+                  %2 : java.util.List<-<java.lang.Number>> = var.load %1;
+                  %3 : int = constant @"0";
+                  %4 : java.lang.Object = invoke %2 %3 @"java.util.List::get(int)java.lang.Object";
+                  return;
+            };
+            """)
+    static void test6() {
+        List<? super Number> l = null;
+        l.get(0);
+    }
+
+    static void consume(Runnable r) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" ()void -> {
+                  %0 : #DenotableTypesTest::test7()void::X<java.lang.Object> = constant @null;
+                  %1 : Var<#DenotableTypesTest::test7()void::X<java.lang.Object>> = var %0 @"x";
+                  %2 : #DenotableTypesTest::test7()void::X<java.lang.Object> = var.load %1;
+                  %3 : java.lang.Runnable = cast %2 @"java.lang.Runnable";
+                  invoke %3 @"DenotableTypesTest::consume(java.lang.Runnable)void";
+                  return;
+            };
+            """)
+    static <X extends Object & Runnable> void test7() {
+        X x = null;
+        consume(x);
+    }
+
+    interface Adder<X> {
+        void add(Adder<X> adder);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : java.util.List<+<DenotableTypesTest$Adder<java.lang.Integer>>>)void -> {
+                  %1 : Var<java.util.List<+<DenotableTypesTest$Adder<java.lang.Integer>>>> = var %0 @"list";
+                  %2 : java.util.List<+<DenotableTypesTest$Adder<java.lang.Integer>>> = var.load %1;
+                  %3 : int = constant @"0";
+                  %4 : DenotableTypesTest$Adder<java.lang.Integer> = invoke %2 %3 @"java.util.List::get(int)java.lang.Object";
+                  %5 : java.util.List<+<DenotableTypesTest$Adder<java.lang.Integer>>> = var.load %1;
+                  %6 : int = constant @"1";
+                  %7 : DenotableTypesTest$Adder<java.lang.Integer> = invoke %5 %6 @"java.util.List::get(int)java.lang.Object";
+                  invoke %4 %7 @"DenotableTypesTest$Adder::add(DenotableTypesTest$Adder)void";
+                  return;
+            };
+            """)
+    static void test8(List<? extends Adder<Integer>> list) {
+        list.get(0).add(list.get(1));
+    }
+
+    static class Box<X> {
+        X x;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : java.util.List<+<DenotableTypesTest$Box<java.lang.Integer>>>)void -> {
+                  %1 : Var<java.util.List<+<DenotableTypesTest$Box<java.lang.Integer>>>> = var %0 @"list";
+                  %2 : java.util.List<+<DenotableTypesTest$Box<java.lang.Integer>>> = var.load %1;
+                  %3 : int = constant @"0";
+                  %4 : DenotableTypesTest$Box<java.lang.Integer> = invoke %2 %3 @"java.util.List::get(int)java.lang.Object";
+                  %5 : java.lang.Integer = field.load %4 @"DenotableTypesTest$Box::x()java.lang.Object";
+                  %6 : Var<java.lang.Integer> = var %5 @"i";
+                  return;
+            };
+            """)
+    static void test9(List<? extends Box<Integer>> list) {
+        Integer i = list.get(0).x;
+    }
+
+    interface E {
+        void m();
+    }
+
+    static class XA extends Exception implements E {
+        public void m() { }
+    }
+
+    static class XB extends Exception implements E {
+        public void m() { }
+    }
+
+    static void g() throws XA, XB { }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" ()void -> {
+                  java.try
+                      ()void -> {
+                          invoke @"DenotableTypesTest::g()void";
+                          yield;
+                      }
+                      (%0 : java.lang.Exception)void -> {
+                          %1 : Var<java.lang.Exception> = var %0 @"x";
+                          %2 : java.lang.Exception = var.load %1;
+                          %3 : DenotableTypesTest$E = cast %2 @"DenotableTypesTest$E";
+                          invoke %3 @"DenotableTypesTest$E::m()void";
+                          yield;
+                      };
+                  return;
+            };
+            """)
+    static void test10() {
+        try {
+            g();
+        } catch (XA | XB x) {
+            x.m();
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/EnumAccessTest.java b/test/langtools/tools/javac/reflect/EnumAccessTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/EnumAccessTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.constant.DirectMethodHandleDesc;
+import java.lang.runtime.CodeReflection;
+
+import static java.lang.constant.DirectMethodHandleDesc.Kind;
+import static java.lang.constant.DirectMethodHandleDesc.Kind.VIRTUAL;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with enum access.
+ * @build EnumAccessTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester EnumAccessTest
+ */
+
+public class EnumAccessTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : EnumAccessTest)java.lang.constant.DirectMethodHandleDesc$Kind -> {
+                %1 : java.lang.constant.DirectMethodHandleDesc$Kind = field.load @"java.lang.constant.DirectMethodHandleDesc$Kind::VIRTUAL()java.lang.constant.DirectMethodHandleDesc$Kind";
+                return %1;
+            };
+            """)
+    DirectMethodHandleDesc.Kind test1() {
+        return DirectMethodHandleDesc.Kind.VIRTUAL;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : EnumAccessTest)java.lang.constant.DirectMethodHandleDesc$Kind -> {
+                %1 : java.lang.constant.DirectMethodHandleDesc$Kind = field.load @"java.lang.constant.DirectMethodHandleDesc$Kind::VIRTUAL()java.lang.constant.DirectMethodHandleDesc$Kind";
+                return %1;
+            };
+            """)
+    DirectMethodHandleDesc.Kind test2() {
+        return Kind.VIRTUAL;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : EnumAccessTest)java.lang.constant.DirectMethodHandleDesc$Kind -> {
+                %1 : java.lang.constant.DirectMethodHandleDesc$Kind = field.load @"java.lang.constant.DirectMethodHandleDesc$Kind::VIRTUAL()java.lang.constant.DirectMethodHandleDesc$Kind";
+                return %1;
+            };
+            """)
+    DirectMethodHandleDesc.Kind test3() {
+        return VIRTUAL;
+    }
+}
diff a/test/langtools/tools/javac/reflect/FieldAccessTest.java b/test/langtools/tools/javac/reflect/FieldAccessTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/FieldAccessTest.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.PrintStream;
+import java.lang.runtime.CodeReflection;
+import java.util.Spliterator;
+import java.util.Spliterator.OfInt;
+
+import static java.lang.System.out;
+import static java.util.Spliterator.OfInt.*;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with field access.
+ * @build FieldAccessTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester FieldAccessTest
+ */
+
+public class FieldAccessTest {
+    static int s_f;
+    int f;
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : FieldAccessTest)void -> {
+                %1 : int = constant @"1";
+                field.store %1 @"FieldAccessTest::s_f()int";
+                %2 : int = constant @"1";
+                field.store %0 %2 @"FieldAccessTest::f()int";
+                return;
+            };
+            """)
+    void test1() {
+        s_f = 1;
+        f = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test1_1" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : int = field.load @"FieldAccessTest::s_f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %6 @"FieldAccessTest::s_f()int";
+                return;
+            };
+            """)
+    void test1_1() {
+        f += 1;
+        s_f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : FieldAccessTest)void -> {
+                %1 : int = constant @"1";
+                field.store %0 %1 @"FieldAccessTest::f()int";
+                field.store %1 @"FieldAccessTest::s_f()int";
+                return;
+            };
+            """)
+    void test2() {
+        s_f = f = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2_1" (%0 : FieldAccessTest)void -> {
+                %1 : int = constant @"1";
+                field.store %0 %1 @"FieldAccessTest::f()int";
+                return;
+            };
+            """)
+    void test2_1() {
+        this.f = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2_2" (%0 : FieldAccessTest)int -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                return %1;
+            };
+            """)
+    int test2_2() {
+        return this.f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : FieldAccessTest)int -> {
+                %1 : int = field.load @"FieldAccessTest::s_f()int";
+                %2 : int = field.load %0 @"FieldAccessTest::f()int";
+                %3 : int = add %1 %2;
+                return %3;
+            };
+            """)
+    int test3() {
+        return s_f + f;
+    }
+
+    static class A {
+        B b;
+    }
+
+    static class B {
+        C c;
+    }
+
+    static class C {
+        int f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : FieldAccessTest, %1 : FieldAccessTest$A)void -> {
+                %2 : Var<FieldAccessTest$A> = var %1 @"a";
+                %3 : FieldAccessTest$A = var.load %2;
+                %4 : FieldAccessTest$B = field.load %3 @"FieldAccessTest$A::b()FieldAccessTest$B";
+                %5 : FieldAccessTest$C = field.load %4 @"FieldAccessTest$B::c()FieldAccessTest$C";
+                %6 : int = constant @"1";
+                field.store %5 %6 @"FieldAccessTest$C::f()int";
+                return;
+            };
+            """)
+    void test4(A a) {
+        a.b.c.f = 1;
+    }
+
+    static class X {
+        int f;
+        static int s_f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : FieldAccessTest)int -> {
+                %1 : int = field.load @"FieldAccessTest$X::s_f()int";
+                return %1;
+            };
+            """)
+    int test5() {
+        return X.s_f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : FieldAccessTest)void -> {
+                %1 : int = constant @"1";
+                field.store %1 @"FieldAccessTest$X::s_f()int";
+                return;
+            };
+            """)
+    void test6() {
+        X.s_f = 1;
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : int = field.load @"FieldAccessTest::s_f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %6 @"FieldAccessTest::s_f()int";
+                return;
+            };
+            """)
+    void test7() {
+        f += 1;
+        s_f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : int = field.load @"FieldAccessTest::s_f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %6 @"FieldAccessTest::s_f()int";
+                return;
+            };
+            """)
+    void test8() {
+        this.f += 1;
+        this.s_f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load @"FieldAccessTest$X::s_f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %3 @"FieldAccessTest$X::s_f()int";
+                return;
+            };
+            """)
+    void test9() {
+        X.s_f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                field.store %3 @"FieldAccessTest::s_f()int";
+                return;
+            };
+            """)
+    void test10() {
+        s_f = f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : FieldAccessTest, %1 : FieldAccessTest$A)void -> {
+                %2 : Var<FieldAccessTest$A> = var %1 @"a";
+                %3 : FieldAccessTest$A = var.load %2;
+                %4 : FieldAccessTest$B = field.load %3 @"FieldAccessTest$A::b()FieldAccessTest$B";
+                %5 : FieldAccessTest$C = field.load %4 @"FieldAccessTest$B::c()FieldAccessTest$C";
+                %6 : int = field.load %5 @"FieldAccessTest$C::f()int";
+                %7 : int = constant @"1";
+                %8 : int = add %6 %7;
+                field.store %5 %8 @"FieldAccessTest$C::f()int";
+                return;
+            };
+            """)
+    void test11(A a) {
+        a.b.c.f += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : Var<int> = var %1 @"x";
+                %5 : int = field.load %0 @"FieldAccessTest::f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %0 %7 @"FieldAccessTest::f()int";
+                %8 : Var<int> = var %5 @"y";
+                return;
+            };
+            """)
+    void test12() {
+        int x = f++;
+        int y = f--;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test13" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : Var<int> = var %1 @"x";
+                %5 : int = field.load %0 @"FieldAccessTest::f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %0 %7 @"FieldAccessTest::f()int";
+                %8 : Var<int> = var %5 @"y";
+                return;
+            };
+            """)
+    void test13() {
+        int x = this.f++;
+        int y = this.f--;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test14" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load @"FieldAccessTest::s_f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %3 @"FieldAccessTest::s_f()int";
+                %4 : Var<int> = var %1 @"x";
+                %5 : int = field.load @"FieldAccessTest::s_f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %7 @"FieldAccessTest::s_f()int";
+                %8 : Var<int> = var %5 @"y";
+                return;
+            };
+            """)
+    void test14() {
+        int x = s_f++;
+        int y = s_f--;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test15" (%0 : FieldAccessTest, %1 : FieldAccessTest$X)void -> {
+                %2 : Var<FieldAccessTest$X> = var %1 @"h";
+                %3 : FieldAccessTest$X = var.load %2;
+                %4 : int = field.load %3 @"FieldAccessTest$X::f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %3 %6 @"FieldAccessTest$X::f()int";
+                %7 : Var<int> = var %4 @"x";
+                %8 : FieldAccessTest$X = var.load %2;
+                %9 : int = field.load %8 @"FieldAccessTest$X::f()int";
+                %10 : int = constant @"1";
+                %11 : int = sub %9 %10;
+                field.store %8 %11 @"FieldAccessTest$X::f()int";
+                %12 : Var<int> = var %9 @"y";
+                return;
+            };
+            """)
+    void test15(X h) {
+        int x = h.f++;
+        int y = h.f--;
+    }
+
+
+
+
+    @CodeReflection
+    @IR("""
+            func @"test16" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : Var<int> = var %3 @"x";
+                %5 : int = field.load %0 @"FieldAccessTest::f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %0 %7 @"FieldAccessTest::f()int";
+                %8 : Var<int> = var %7 @"y";
+                return;
+            };
+            """)
+    void test16() {
+        int x = ++f;
+        int y = --f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test17" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load %0 @"FieldAccessTest::f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %0 %3 @"FieldAccessTest::f()int";
+                %4 : Var<int> = var %3 @"x";
+                %5 : int = field.load %0 @"FieldAccessTest::f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %0 %7 @"FieldAccessTest::f()int";
+                %8 : Var<int> = var %7 @"y";
+                return;
+            };
+            """)
+    void test17() {
+        int x = ++this.f;
+        int y = --this.f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test18" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load @"FieldAccessTest::s_f()int";
+                %2 : int = constant @"1";
+                %3 : int = add %1 %2;
+                field.store %3 @"FieldAccessTest::s_f()int";
+                %4 : Var<int> = var %3 @"x";
+                %5 : int = field.load @"FieldAccessTest::s_f()int";
+                %6 : int = constant @"1";
+                %7 : int = sub %5 %6;
+                field.store %7 @"FieldAccessTest::s_f()int";
+                %8 : Var<int> = var %7 @"y";
+                return;
+            };
+            """)
+    void test18() {
+        int x = ++s_f;
+        int y = --s_f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test19" (%0 : FieldAccessTest, %1 : FieldAccessTest$X)void -> {
+                %2 : Var<FieldAccessTest$X> = var %1 @"h";
+                %3 : FieldAccessTest$X = var.load %2;
+                %4 : int = field.load %3 @"FieldAccessTest$X::f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %3 %6 @"FieldAccessTest$X::f()int";
+                %7 : Var<int> = var %6 @"x";
+                %8 : FieldAccessTest$X = var.load %2;
+                %9 : int = field.load %8 @"FieldAccessTest$X::f()int";
+                %10 : int = constant @"1";
+                %11 : int = sub %9 %10;
+                field.store %8 %11 @"FieldAccessTest$X::f()int";
+                %12 : Var<int> = var %11 @"y";
+                return;
+            };
+            """)
+    void test19(X h) {
+        int x = ++h.f;
+        int y = --h.f;
+    }
+
+    static class Y extends X {
+        int yf;
+        static int s_yf;
+
+        @CodeReflection
+        @IR("""
+                func @"test" (%0 : FieldAccessTest$Y)void -> {
+                    %1 : int = field.load %0 @"FieldAccessTest$Y::f()int";
+                    %2 : Var<int> = var %1 @"x";
+                    %3 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                    var.store %2 %3;
+                    return;
+                };
+                """)
+        void test() {
+            int x = f;
+            x = s_f;
+        }
+
+        @CodeReflection
+        @IR("""
+                func @"test2" (%0 : FieldAccessTest$Y)void -> {
+                    %1 : int = constant @"1";
+                    field.store %0 %1 @"FieldAccessTest$Y::f()int";
+                    %2 : int = constant @"1";
+                    field.store %2 @"FieldAccessTest$Y::s_f()int";
+                    return;
+                };
+                """)
+        void test2() {
+            f = 1;
+            s_f = 1;
+        }
+
+        @CodeReflection
+        @IR("""
+                func @"test3" (%0 : FieldAccessTest$Y)void -> {
+                    %1 : int = field.load %0 @"FieldAccessTest$Y::f()int";
+                    %2 : int = constant @"1";
+                    %3 : int = add %1 %2;
+                    field.store %0 %3 @"FieldAccessTest$Y::f()int";
+                    %4 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                    %5 : int = constant @"1";
+                    %6 : int = add %4 %5;
+                    field.store %6 @"FieldAccessTest$Y::s_f()int";
+                    return;
+                };
+                """)
+        void test3() {
+            f++;
+            s_f++;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test20" (%0 : FieldAccessTest, %1 : FieldAccessTest$Y)void -> {
+                %2 : Var<FieldAccessTest$Y> = var %1 @"y";
+                %3 : FieldAccessTest$Y = var.load %2;
+                %4 : int = field.load %3 @"FieldAccessTest$Y::f()int";
+                %5 : Var<int> = var %4 @"x";
+                %6 : FieldAccessTest$Y = var.load %2;
+                %7 : int = field.load %6 @"FieldAccessTest$Y::yf()int";
+                var.store %5 %7;
+                %8 : FieldAccessTest$Y = var.load %2;
+                %9 : int = field.load @"FieldAccessTest$Y::s_yf()int";
+                var.store %5 %9;
+                %10 : int = field.load @"FieldAccessTest$Y::s_yf()int";
+                var.store %5 %10;
+                %11 : FieldAccessTest$Y = var.load %2;
+                %12 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                var.store %5 %12;
+                %13 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                var.store %5 %13;
+                return;
+            };
+            """)
+    void test20(Y y) {
+        int x = y.f;
+        x = y.yf;
+        x = y.s_yf;
+        x = Y.s_yf;
+        x = y.s_f;
+        x = Y.s_f;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test21" (%0 : FieldAccessTest, %1 : FieldAccessTest$Y)void -> {
+                %2 : Var<FieldAccessTest$Y> = var %1 @"y";
+                %3 : FieldAccessTest$Y = var.load %2;
+                %4 : int = constant @"1";
+                field.store %3 %4 @"FieldAccessTest$Y::f()int";
+                %5 : FieldAccessTest$Y = var.load %2;
+                %6 : int = constant @"1";
+                field.store %5 %6 @"FieldAccessTest$Y::yf()int";
+                %7 : FieldAccessTest$Y = var.load %2;
+                %8 : int = constant @"1";
+                field.store %8 @"FieldAccessTest$Y::s_yf()int";
+                %9 : int = constant @"1";
+                field.store %9 @"FieldAccessTest$Y::s_yf()int";
+                %10 : FieldAccessTest$Y = var.load %2;
+                %11 : int = constant @"1";
+                field.store %11 @"FieldAccessTest$Y::s_f()int";
+                %12 : int = constant @"1";
+                field.store %12 @"FieldAccessTest$Y::s_f()int";
+                return;
+            };
+            """)
+    void test21(Y y) {
+        y.f = 1;
+        y.yf = 1;
+        y.s_yf = 1;
+        Y.s_yf = 1;
+        y.s_f = 1;
+        Y.s_f = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+          func @"test22" (%0 : FieldAccessTest, %1 : FieldAccessTest$Y)void -> {
+                %2 : Var<FieldAccessTest$Y> = var %1 @"y";
+                %3 : FieldAccessTest$Y = var.load %2;
+                %4 : int = field.load %3 @"FieldAccessTest$Y::f()int";
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                field.store %3 %6 @"FieldAccessTest$Y::f()int";
+                %7 : FieldAccessTest$Y = var.load %2;
+                %8 : int = field.load %7 @"FieldAccessTest$Y::yf()int";
+                %9 : int = constant @"1";
+                %10 : int = add %8 %9;
+                field.store %7 %10 @"FieldAccessTest$Y::yf()int";
+                %11 : FieldAccessTest$Y = var.load %2;
+                %12 : int = field.load @"FieldAccessTest$Y::s_yf()int";
+                %13 : int = constant @"1";
+                %14 : int = add %12 %13;
+                field.store %14 @"FieldAccessTest$Y::s_yf()int";
+                %15 : int = field.load @"FieldAccessTest$Y::s_yf()int";
+                %16 : int = constant @"1";
+                %17 : int = add %15 %16;
+                field.store %17 @"FieldAccessTest$Y::s_yf()int";
+                %18 : FieldAccessTest$Y = var.load %2;
+                %19 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                %20 : int = constant @"1";
+                %21 : int = add %19 %20;
+                field.store %21 @"FieldAccessTest$Y::s_f()int";
+                %22 : int = field.load @"FieldAccessTest$Y::s_f()int";
+                %23 : int = constant @"1";
+                %24 : int = add %22 %23;
+                field.store %24 @"FieldAccessTest$Y::s_f()int";
+                return;
+            };
+            """)
+    void test22(Y y) {
+        y.f++;
+        y.yf++;
+        y.s_yf++;
+        Y.s_yf++;
+        y.s_f++;
+        Y.s_f++;
+    }
+
+    // @@@ Should propagate as constant value?
+    @CodeReflection
+    @IR("""
+            func @"test23" (%0 : FieldAccessTest)void -> {
+                %1 : int = field.load @"java.util.Spliterator$OfInt::CONCURRENT()int";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = field.load @"java.util.Spliterator$OfInt::CONCURRENT()int";
+                var.store %2 %3;
+                %4 : int = field.load @"java.util.Spliterator$OfInt::CONCURRENT()int";
+                var.store %2 %4;
+                return;
+            };
+            """)
+    void test23() {
+        int x = Spliterator.OfInt.CONCURRENT;
+        x = OfInt.CONCURRENT;
+        x = CONCURRENT;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test24" (%0 : FieldAccessTest)void -> {
+                %1 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                %2 : Var<java.io.PrintStream> = var %1 @"ps";
+                return;
+            };
+            """)
+    void test24() {
+        PrintStream ps = out;
+    }
+
+    static class Box<T> {
+        public T v;
+
+        public Box(T v) {
+            this.v = v;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test25" ()void -> {
+                    %0 : java.lang.String = constant @"abc";
+                    %1 : FieldAccessTest$Box<java.lang.String> = new %0 @"func<FieldAccessTest$Box, java.lang.Object>";
+                    %2 : Var<FieldAccessTest$Box<java.lang.String>> = var %1 @"b";
+                    %3 : FieldAccessTest$Box<java.lang.String> = var.load %2;
+                    %4 : java.lang.String = field.load %3 @"FieldAccessTest$Box::v()java.lang.Object";
+                    %5 : Var<java.lang.String> = var %4 @"s";
+                    return;
+            };
+            """)
+    static void test25() {
+        Box<String> b = new Box<>("abc");
+        String s = b.v;
+    }
+
+    //@@@ unqualified access to field of generic type needs to be tested
+    // waiting for a new way of modeling types, so that type variables are captured in the IR
+}
diff a/test/langtools/tools/javac/reflect/ForLoopTest.java b/test/langtools/tools/javac/reflect/ForLoopTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ForLoopTest.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with for loops.
+ * @build ForLoopTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ForLoopTest
+ */
+
+public class ForLoopTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : ForLoopTest, %1 : java.util.List<java.util.List<java.lang.String>>)void -> {
+              %2 : Var<java.util.List<java.util.List<java.lang.String>>> = var %1 @"ll";
+              java.enhancedFor
+                  ^expr()java.util.List<java.util.List<java.lang.String>> -> {
+                      %3 : java.util.List<java.util.List<java.lang.String>> = var.load %2;
+                      yield %3;
+                  }
+                  ^def(%4 : java.util.List<java.lang.String>)Var<java.util.List<java.lang.String>> -> {
+                      %5 : Var<java.util.List<java.lang.String>> = var %4 @"l";
+                      yield %5;
+                  }
+                  ^body(%6 : Var<java.util.List<java.lang.String>>)void -> {
+                      java.enhancedFor
+                          ^expr()java.util.List<java.lang.String> -> {
+                              %7 : java.util.List<java.lang.String> = var.load %6;
+                              yield %7;
+                          }
+                          ^def(%8 : java.lang.String)Var<java.lang.String> -> {
+                              %9 : Var<java.lang.String> = var %8 @"s";
+                              yield %9;
+                          }
+                          ^body(%10 : Var<java.lang.String>)void -> {
+                              %11 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                              %12 : java.lang.String = var.load %10;
+                              invoke %11 %12 @"java.io.PrintStream::println(java.lang.String)void";
+                              java.continue;
+                          };
+                      java.continue;
+                  };
+              return;
+            };
+            """)
+    void test1(List<List<String>> ll) {
+        for (List<String> l : ll) {
+            for (String s : l) {
+                System.out.println(s);
+            }
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : ForLoopTest, %1 : java.util.List<java.lang.String>)void -> {
+                %2 : Var<java.util.List<java.lang.String>> = var %1 @"l";
+                java.enhancedFor
+                    ^expr()java.util.List<java.lang.String> -> {
+                        %3 : java.util.List<java.lang.String> = var.load %2;
+                        %4 : java.util.stream.Stream<java.lang.String> = invoke %3 @"java.util.List::stream()java.util.stream.Stream";
+                        %5 : java.util.function.Predicate<java.lang.String> = lambda (%6 : java.lang.String)boolean -> {
+                            %7 : Var<java.lang.String> = var %6 @"s";
+                            %8 : java.lang.String = var.load %7;
+                            %9 : int = invoke %8 @"java.lang.String::length()int";
+                            %10 : int = constant @"10";
+                            %11 : boolean = lt %9 %10;
+                            return %11;
+                        };
+                        %12 : java.util.stream.Stream<java.lang.String> = invoke %4 %5 @"java.util.stream.Stream::filter(java.util.function.Predicate)java.util.stream.Stream";
+                        %13 : java.util.List<java.lang.String> = invoke %12 @"java.util.stream.Stream::toList()java.util.List";
+                        yield %13;
+                    }
+                    ^def(%14 : java.lang.String)Var<java.lang.String> -> {
+                        %15 : Var<java.lang.String> = var %14 @"s";
+                        yield %15;
+                    }
+                    ^body(%16 : Var<java.lang.String>)void -> {
+                        %17 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %18 : java.lang.String = var.load %16;
+                        invoke %17 %18 @"java.io.PrintStream::println(java.lang.String)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test2(List<String> l) {
+        for (String s : l.stream().filter(s -> s.length() < 10).toList()) {
+            System.out.println(s);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2_1" (%0 : ForLoopTest, %1 : java.util.List<java.lang.String>)void -> {
+              %2 : Var<java.util.List<java.lang.String>> = var %1 @"l";
+              java.enhancedFor
+                  ^expr()java.util.List<java.lang.String> -> {
+                      %3 : java.util.List<java.lang.String> = var.load %2;
+                      yield %3;
+                  }
+                  ^def(%4 : java.lang.String)Var<java.lang.String> -> {
+                      %5 : Var<java.lang.String> = var %4 @"s";
+                      yield %5;
+                  }
+                  ^body(%6 : Var<java.lang.String>)void -> {
+                      java.continue;
+                  };
+              return;
+            };
+            """)
+    void test2_1(List<String> l) {
+        for (String s : l);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2_2" (%0 : ForLoopTest, %1 : java.util.List<java.lang.String>)java.lang.String -> {
+              %2 : Var<java.util.List<java.lang.String>> = var %1 @"l";
+              java.enhancedFor
+                  ^expr()java.util.List<java.lang.String> -> {
+                      %3 : java.util.List<java.lang.String> = var.load %2;
+                      yield %3;
+                  }
+                  ^def(%4 : java.lang.String)Var<java.lang.String> -> {
+                      %5 : Var<java.lang.String> = var %4 @"s";
+                      yield %5;
+                  }
+                  ^body(%6 : Var<java.lang.String>)void -> {
+                      %7 : java.lang.String = var.load %6;
+                      return %7;
+                  };
+              %8 : java.lang.String = constant @"";
+              return %8;
+            };
+            """)
+    String test2_2(List<String> l) {
+        for (String s : l) {
+            return s;
+        }
+        return "";
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : int = var.load %3;
+                        %5 : int = constant @"10";
+                        %6 : boolean = lt %4 %5;
+                        yield %6;
+                    }
+                    ^update(%7 : Var<int>)void -> {
+                        %8 : int = var.load %7;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %7 %10;
+                        yield;
+                    }
+                    ^body(%11 : Var<int>)void -> {
+                        %12 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %13 : int = var.load %11;
+                        invoke %12 %13 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test3() {
+        for (int i = 0; i < 10; i++) {
+            System.out.println(i);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3_1" (%0 : ForLoopTest)int -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : int = var.load %3;
+                        %5 : int = constant @"10";
+                        %6 : boolean = lt %4 %5;
+                        yield %6;
+                    }
+                    ^update(%7 : Var<int>)void -> {
+                        %8 : int = var.load %7;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %7 %10;
+                        yield;
+                    }
+                    ^body(%11 : Var<int>)void -> {
+                        %12 : int = var.load %11;
+                        return %12;
+                    };
+                %13 : int = constant @"-1";
+                return %13;
+            };
+            """)
+    int test3_1() {
+        for (int i = 0; i < 10; i++) {
+            return i;
+        }
+        return -1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : int = var.load %3;
+                        %5 : int = constant @"10";
+                        %6 : boolean = lt %4 %5;
+                        yield %6;
+                    }
+                    ^update(%7 : Var<int>)void -> {
+                        %8 : int = var.load %7;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %7 %10;
+                        yield;
+                    }
+                    ^body(%11 : Var<int>)void -> {
+                        %12 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %13 : int = var.load %11;
+                        invoke %12 %13 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test4() {
+        for (int i = 0; i < 10; i = i + 1)
+            System.out.println(i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : int = var.load %3;
+                        %5 : int = constant @"10";
+                        %6 : boolean = lt %4 %5;
+                        yield %6;
+                    }
+                    ^update(%7 : Var<int>)void -> {
+                        %8 : int = var.load %7;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %7 %10;
+                        yield;
+                    }
+                    ^body(%11 : Var<int>)void -> {
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test5() {
+        for (int i = 0; i < 10; i = i + 1);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : ForLoopTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.for
+                    ^init()void -> {
+                        yield;
+                    }
+                    ^cond()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"10";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^update()void -> {
+                        %6 : int = var.load %2;
+                        %7 : int = constant @"1";
+                        %8 : int = add %6 %7;
+                        var.store %2 %8;
+                        yield;
+                    }
+                    ^body()void -> {
+                        %9 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %10 : int = var.load %2;
+                        invoke %9 %10 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test6() {
+        int i = 0;
+        for (; i < 10; i = i + 1) {
+            System.out.println(i);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : ForLoopTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.for
+                    ^init()void -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : int = add %3 %4;
+                        var.store %2 %5;
+                        yield;
+                    }
+                    ^cond()boolean -> {
+                        %6 : int = var.load %2;
+                        %7 : int = constant @"10";
+                        %8 : boolean = lt %6 %7;
+                        yield %8;
+                    }
+                    ^update()void -> {
+                        %9 : int = var.load %2;
+                        %10 : int = constant @"1";
+                        %11 : int = add %9 %10;
+                        var.store %2 %11;
+                        yield;
+                    }
+                    ^body()void -> {
+                        %12 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %13 : int = var.load %2;
+                        invoke %12 %13 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test7() {
+        int i = 0;
+        for (i = i + 1; i < 10; i = i + 1) {
+            System.out.println(i);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : boolean = constant @"true";
+                        yield %4;
+                    }
+                    ^update(%5 : Var<int>)void -> {
+                        %6 : int = var.load %5;
+                        %7 : int = constant @"1";
+                        %8 : int = add %6 %7;
+                        var.store %5 %8;
+                        yield;
+                    }
+                    ^body(%9 : Var<int>)void -> {
+                        %10 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %11 : int = var.load %9;
+                        invoke %10 %11 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test8() {
+        for (int i = 0; ; i = i + 1) {
+            System.out.println(i);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Var<int> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        yield %2;
+                    }
+                    ^cond(%3 : Var<int>)boolean -> {
+                        %4 : boolean = constant @"true";
+                        yield %4;
+                    }
+                    ^update(%5 : Var<int>)void -> {
+                        yield;
+                    }
+                    ^body(%6 : Var<int>)void -> {
+                        %7 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %8 : int = var.load %6;
+                        invoke %7 %8 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test9() {
+        for (int i = 0; ; ) {
+            System.out.println(i);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()void -> {
+                        yield;
+                    }
+                    ^cond()boolean -> {
+                        %1 : boolean = constant @"true";
+                        yield %1;
+                    }
+                    ^update()void -> {
+                        yield;
+                    }
+                    ^body()void -> {
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test10() {
+        for (; ; ) {
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : ForLoopTest)void -> {
+                java.for
+                    ^init()Tuple<Var<int>, Var<int>> -> {
+                        %1 : int = constant @"0";
+                        %2 : Var<int> = var %1 @"i";
+                        %3 : int = constant @"0";
+                        %4 : Var<int> = var %3 @"j";
+                        %5 : Tuple<Var<int>, Var<int>> = tuple %2 %4;
+                        yield %5;
+                    }
+                    ^cond(%6 : Var<int>, %7 : Var<int>)boolean -> {
+                        %8 : boolean = java.cand
+                            ()boolean -> {
+                                %9 : int = var.load %6;
+                                %10 : int = constant @"10";
+                                %11 : boolean = lt %9 %10;
+                                yield %11;
+                            }
+                            ()boolean -> {
+                                %12 : int = var.load %7;
+                                %13 : int = constant @"20";
+                                %14 : boolean = lt %12 %13;
+                                yield %14;
+                            };
+                        yield %8;
+                    }
+                    ^update(%15 : Var<int>, %16 : Var<int>)void -> {
+                        %17 : int = var.load %15;
+                        %18 : int = constant @"1";
+                        %19 : int = add %17 %18;
+                        var.store %15 %19;
+                        %20 : int = var.load %16;
+                        %21 : int = constant @"2";
+                        %22 : int = add %20 %21;
+                        var.store %16 %22;
+                        yield;
+                    }
+                    ^body(%23 : Var<int>, %24 : Var<int>)void -> {
+                        %25 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %26 : int = var.load %23;
+                        invoke %25 %26 @"java.io.PrintStream::println(int)void";
+                        %27 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %28 : int = var.load %24;
+                        invoke %27 %28 @"java.io.PrintStream::println(int)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test11() {
+        for (int i = 0, j = 0; i < 10 && j < 20; i = i + 1, j = j + 2) {
+            System.out.println(i);
+            System.out.println(j);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0 : ForLoopTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"r";
+                java.for
+                    ^init()Var<int> -> {
+                        %3 : int = constant @"0";
+                        %4 : Var<int> = var %3 @"i";
+                        yield %4;
+                    }
+                    ^cond(%5 : Var<int>)boolean -> {
+                        %6 : int = var.load %5;
+                        %7 : int = constant @"10";
+                        %8 : boolean = lt %6 %7;
+                        yield %8;
+                    }
+                    ^update(%9 : Var<int>)void -> {
+                        %10 : int = var.load %9;
+                        %11 : int = constant @"1";
+                        %12 : int = add %10 %11;
+                        var.store %9 %12;
+                        yield;
+                    }
+                    ^body(%13 : Var<int>)void -> {
+                        java.if
+                            ()boolean -> {
+                                %14 : int = var.load %2;
+                                %15 : int = constant @"0";
+                                %16 : boolean = eq %14 %15;
+                                yield %16;
+                            }
+                            ^then()void -> {
+                                java.break;
+                            }
+                            ^else_if()boolean -> {
+                                %17 : int = var.load %2;
+                                %18 : int = constant @"1";
+                                %19 : boolean = eq %17 %18;
+                                yield %19;
+                            }
+                            ^then()void -> {
+                                java.continue;
+                            }
+                            ^else()void -> {
+                                yield;
+                            };
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test12(int r) {
+        for (int i = 0; i < 10; i++) {
+            if (r == 0) {
+                break;
+            } else if (r == 1) {
+                continue;
+            }
+        }
+    }
+
+}
diff a/test/langtools/tools/javac/reflect/IR.java b/test/langtools/tools/javac/reflect/IR.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/IR.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.annotation.*;
+
+@Target({ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IR {
+    String value();
+}
diff a/test/langtools/tools/javac/reflect/IfTest.java b/test/langtools/tools/javac/reflect/IfTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/IfTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with if statements.
+ * @build IfTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester IfTest
+ */
+
+public class IfTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : IfTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                java.if
+                    ()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^then()void -> {
+                        %6 : int = constant @"1";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^else()void -> {
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test1(int i) {
+        if (i < 1) {
+            i = 1;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : IfTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                java.if
+                    ()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^then()void -> {
+                        %6 : int = constant @"1";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^else()void -> {
+                        %7 : int = constant @"2";
+                        var.store %2 %7;
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test2(int i) {
+        if (i < 1) {
+            i = 1;
+        } else {
+            i = 2;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : IfTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                java.if
+                    ()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^then()void -> {
+                        %6 : int = constant @"1";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^else_if()boolean -> {
+                        %7 : int = var.load %2;
+                        %8 : int = constant @"2";
+                        %9 : boolean = lt %7 %8;
+                        yield %9;
+                    }
+                    ^then()void -> {
+                        %10 : int = constant @"2";
+                        var.store %2 %10;
+                        yield;
+                    }
+                    ^else()void -> {
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test3(int i) {
+        if (i < 1) {
+            i = 1;
+        } else if (i < 2) {
+            i = 2;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : IfTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                java.if
+                    ()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"1";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^then()void -> {
+                        %6 : int = constant @"1";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^else_if()boolean -> {
+                        %7 : int = var.load %2;
+                        %8 : int = constant @"2";
+                        %9 : boolean = lt %7 %8;
+                        yield %9;
+                    }
+                    ^then()void -> {
+                        %10 : int = constant @"2";
+                        var.store %2 %10;
+                        yield;
+                    }
+                    ^else()void -> {
+                        %11 : int = constant @"3";
+                        var.store %2 %11;
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test4(int i) {
+        if (i < 1) {
+            i = 1;
+        } else if (i < 2) {
+            i = 2;
+        } else {
+            i = 3;
+        }
+    }
+
+    @IR("""
+            func @"test5" (%0 : IfTest, %1 : int)int -> {
+              %2 : Var<int> = var %1 @"i";
+              java.if
+                  ()boolean -> {
+                      %3 : int = var.load %2;
+                      %4 : int = constant @"1";
+                      %5 : boolean = lt %3 %4;
+                      yield %5;
+                  }
+                  ^then()void -> {
+                      %6 : int = constant @"1";
+                      return %6;
+                  }
+                  ^else_if()boolean -> {
+                      %7 : int = var.load %2;
+                      %8 : int = constant @"2";
+                      %9 : boolean = lt %7 %8;
+                      yield %9;
+                  }
+                  ^then()void -> {
+                      %10 : int = constant @"2";
+                      return %10;
+                  }
+                  ^else()void -> {
+                      %11 : int = constant @"3";
+                      return %11;
+                  };
+              return;
+            };
+            """)
+    @CodeReflection
+    int test5(int i) {
+        if (i < 1) {
+            return 1;
+        } else if (i < 2) {
+            return 2;
+        } else {
+            return 3;
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/ImplicitConversionTest.java b/test/langtools/tools/javac/reflect/ImplicitConversionTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ImplicitConversionTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.LongSupplier;
+
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with implicit conversions.
+ * @enablePreview
+ * @build ImplicitConversionTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ImplicitConversionTest
+ */
+
+public class ImplicitConversionTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0: ImplicitConversionTest)void -> {
+                %1 : int = constant @"1";
+                %2 : long = conv %1;
+                %3 : Var<long> = var %2 @"x";
+                return;
+            };
+            """)
+    void test1() {
+        long x = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0: ImplicitConversionTest)void -> {
+                %1 : long = constant @"0";
+                %2 : Var<long> = var %1 @"x";
+                %3 : int = constant @"1";
+                %4 : long = conv %3;
+                var.store %2 %4;
+                return;
+            };
+            """)
+    void test2() {
+        long x;
+        x = 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0: ImplicitConversionTest)void -> {
+                %1 : long = constant @"0";
+                %2 : Var<long> = var %1 @"x";
+                %3 : long = var.load %2;
+                %4 : int = constant @"1";
+                %5 : long = conv %4;
+                %6 : long = add %3 %5;
+                var.store %2 %6;
+                return;
+            };
+            """)
+    void test3() {
+        long x = 0L;
+        x += 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0: ImplicitConversionTest, %1 : boolean)void -> {
+                %2 : Var<boolean> = var %1 @"cond";
+                %3 : long = constant @"0";
+                %4 : Var<long> = var %3 @"x";
+                %5 : long = java.cexpression
+                    ^cond()boolean -> {
+                        %6 : boolean = var.load %2;
+                        yield %6;
+                    }
+                    ^truepart()long -> {
+                        %7 : long = constant @"1";
+                        yield %7;
+                    }
+                    ^falsepart()long -> {
+                        %8 : int = constant @"2";
+                        %9 : long = conv %8;
+                        yield %9;
+                    };
+                var.store %4 %5;
+                return;
+            };
+            """)
+    void test4(boolean cond) {
+        long x;
+        x = cond ? 1L : 2;
+    }
+
+    @CodeReflection
+    @IR("""
+           func @"test5" (%0: ImplicitConversionTest, %1 : boolean)void -> {
+               %2 : Var<boolean> = var %1 @"cond";
+               %3 : long = constant @"0";
+               %4 : Var<long> = var %3 @"x";
+               %5 : long = java.cexpression
+                   ^cond()boolean -> {
+                       %6 : boolean = var.load %2;
+                       yield %6;
+                   }
+                   ^truepart()long -> {
+                       %7 : int = constant @"1";
+                       %8 : long = conv %7;
+                       yield %8;
+                   }
+                   ^falsepart()long -> {
+                       %9 : long = constant @"2";
+                       yield %9;
+                   };
+               var.store %4 %5;
+               return;
+           };
+           """)
+    void test5(boolean cond) {
+        long x;
+        x = cond ? 1 : 2L;
+    }
+
+    @CodeReflection
+    @IR("""
+           func @"test6" (%0: ImplicitConversionTest, %1 : boolean)void -> {
+               %2 : Var<boolean> = var %1 @"cond";
+               %3 : long = constant @"0";
+               %4 : Var<long> = var %3 @"x";
+               %5 : int = java.cexpression
+                   ^cond()boolean -> {
+                       %6 : boolean = var.load %2;
+                       yield %6;
+                   }
+                   ^truepart()int -> {
+                       %7 : int = constant @"1";
+                       yield %7;
+                   }
+                   ^falsepart()int -> {
+                       %8 : int = constant @"2";
+                       yield %8;
+                   };
+               %9 : long = conv %5;
+               var.store %4 %9;
+               return;
+           };
+           """)
+    void test6(boolean cond) {
+        long x;
+        x = cond ? 1 : 2;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0: ImplicitConversionTest)long -> {
+                %1 : int = constant @"1";
+                %2 : long = conv %1;
+                return %2;
+            };
+            """)
+    long test7() {
+        return 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0: ImplicitConversionTest)void -> {
+                %1 : java.util.function.LongSupplier = lambda ()long -> {
+                    %2 : int = constant @"1";
+                    %3 : long = conv %2;
+                    return %3;
+                };
+                %4 : Var<java.util.function.LongSupplier> = var %1 @"s";
+                return;
+            };
+            """)
+    void test8() {
+        LongSupplier s = () -> { return 1; };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0: ImplicitConversionTest)void -> {
+                %1 : java.util.function.LongSupplier = lambda ()long -> {
+                    %2 : int = constant @"1";
+                    %3 : long = conv %2;
+                    return %3;
+                };
+                %4 : Var<java.util.function.LongSupplier> = var %1 @"s";
+                return;
+            };
+            """)
+    void test9() {
+        LongSupplier s = () -> 1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : long = constant @"1";
+                        yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %9 : int = constant @"0";
+                        %10 : long = conv %9;
+                        yield %10;
+                    };
+                %11 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test10(int i) {
+        long l = switch (i) {
+            case 1 -> 1L;
+            default -> 0;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : int = constant @"1";
+                        %9 : long = conv %8;
+                        yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %10 : long = constant @"0";
+                        yield %10;
+                    };
+                %11 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test11(int i) {
+        long l = switch (i) {
+            case 1 -> 1;
+            default -> 0L;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : int = constant @"1";
+                        %9 : long = conv %8;
+                        yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %10 : int = constant @"0";
+                        %11 : long = conv %10;
+                        yield %11;
+                    };
+                %12 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test12(int i) {
+        long l = switch (i) {
+            case 1 -> 1;
+            default -> 0;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test13" (%0 : ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : long = constant @"1";
+                        java.yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %9 : int = constant @"0";
+                        %10 : long = conv %9;
+                        java.yield %10;
+                    };
+                %11 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test13(int i) {
+        long l = switch (i) {
+            case 1 -> { yield 1L; }
+            default -> { yield 0; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test14" (%0 : ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : int = constant @"1";
+                        %9 : long = conv %8;
+                        java.yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %10 : long = constant @"0";
+                        java.yield %10;
+                    };
+                %11 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test14(int i) {
+        long l = switch (i) {
+            case 1 -> { yield 1; }
+            default -> { yield 0L; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test15" (%0 : ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()long -> {
+                        %8 : int = constant @"1";
+                        %9 : long = conv %8;
+                        java.yield %9;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()long -> {
+                        %10 : int = constant @"0";
+                        %11 : long = conv %10;
+                        java.yield %11;
+                    };
+                %12 : Var<long> = var %4 @"l";
+                return;
+            };
+            """)
+    void test15(int i) {
+        long l = switch (i) {
+            case 1 -> { yield 1; }
+            default -> { yield 0; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test16" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = conv %3;
+                %5 : long = constant @"2";
+                %6 : long = add %4 %5;
+                %7 : Var<long> = var %6 @"l";
+                return;
+            };
+            """)
+    void test16(int i) {
+        long l = i + 2L;
+    }
+
+    void m(long l) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test17" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = conv %3;
+                invoke %0 %4 @"ImplicitConversionTest::m(long)void";
+                return;
+            };
+            """)
+    void test17(int i) {
+        m(i);
+    }
+
+    void m(int i1, int i2, long... l) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test18" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                invoke %0 %3 %4 @"ImplicitConversionTest::m(int, int, long[])void";
+                return;
+            };
+            """)
+    void test18(int i) {
+        m(i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+           func @"test19" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : long = conv %5;
+                invoke %0 %3 %4 %6 @"ImplicitConversionTest::m(int, int, long[])void";
+                return;
+           };
+           """)
+    void test19(int i) {
+        m(i, i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test20" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : long = conv %5;
+                %7 : int = var.load %2;
+                %8 : long = conv %7;
+                invoke %0 %3 %4 %6 %8 @"ImplicitConversionTest::m(int, int, long[])void";
+                return;
+            };
+            """)
+    void test20(int i) {
+        m(i, i, i, i);
+    }
+
+    static class Box {
+        Box(long l) { }
+        Box(int i1, int i2, long... longs) { }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test21" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : long = conv %3;
+                %5 : ImplicitConversionTest$Box = new %4 @"func<ImplicitConversionTest$Box, long>";
+                return;
+            };
+            """)
+    void test21(int i) {
+        new Box(i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test22" (%0: ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : ImplicitConversionTest$Box = new %3 %4 @"func<ImplicitConversionTest$Box, int, int, long[]>";
+                return;
+            };
+            """)
+    void test22(int i) {
+        new Box(i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+           func @"test23" (%0 : ImplicitConversionTest, %1 : int)void -> {
+               %2 : Var<int> = var %1 @"i";
+               %3 : int = var.load %2;
+               %4 : int = var.load %2;
+               %5 : int = var.load %2;
+               %6 : long = conv %5;
+               %7 : ImplicitConversionTest$Box = new %3 %4 %6 @"func<ImplicitConversionTest$Box, int, int, long[]>";
+               return;
+           };
+           """)
+    void test23(int i) {
+        new Box(i, i, i);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test24" (%0 : ImplicitConversionTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = var.load %2;
+                %6 : long = conv %5;
+                %7 : int = var.load %2;
+                %8 : long = conv %7;
+                %9 : ImplicitConversionTest$Box = new %3 %4 %6 %8 @"func<ImplicitConversionTest$Box, int, int, long[]>";
+                return;
+            };
+            """)
+    void test24(int i) {
+        new Box(i, i, i, i);
+    }
+}
diff a/test/langtools/tools/javac/reflect/IntersectionTypeTest.java b/test/langtools/tools/javac/reflect/IntersectionTypeTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/IntersectionTypeTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with intersection type conversions.
+ * @build IntersectionTypeTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester IntersectionTypeTest
+ */
+
+class IntersectionTypeTest {
+    interface A {
+        Object f_A = 5;
+        void m_A();
+    }
+
+    interface B {
+        Object f_B = 5;
+        void m_B();
+    }
+
+    interface C {
+        Object f_C = 5;
+        void m_C();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : #IntersectionTypeTest::test1(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>)void -> {
+                  %1 : Var<#IntersectionTypeTest::test1(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>> = var %0 @"x";
+                  %2 : #IntersectionTypeTest::test1(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  invoke %2 @"IntersectionTypeTest$A::m_A()void";
+                  %3 : #IntersectionTypeTest::test1(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %4 : IntersectionTypeTest$B = cast %3 @"IntersectionTypeTest$B";
+                  invoke %4 @"IntersectionTypeTest$B::m_B()void";
+                  %5 : #IntersectionTypeTest::test1(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %6 : IntersectionTypeTest$C = cast %5 @"IntersectionTypeTest$C";
+                  invoke %6 @"IntersectionTypeTest$C::m_C()void";
+                  return;
+            };
+            """)
+    static <X extends A & B & C> void test1(X x) {
+        x.m_A();
+        x.m_B();
+        x.m_C();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : #IntersectionTypeTest::test2(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>)void -> {
+                  %1 : Var<#IntersectionTypeTest::test2(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>> = var %0 @"x";
+                  %2 : #IntersectionTypeTest::test2(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %3 : java.lang.Object = field.load @"IntersectionTypeTest$A::f_A()java.lang.Object";
+                  %4 : Var<java.lang.Object> = var %3 @"oA";
+                  %5 : #IntersectionTypeTest::test2(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %6 : java.lang.Object = field.load @"IntersectionTypeTest$B::f_B()java.lang.Object";
+                  %7 : Var<java.lang.Object> = var %6 @"oB";
+                  %8 : #IntersectionTypeTest::test2(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %9 : java.lang.Object = field.load @"IntersectionTypeTest$C::f_C()java.lang.Object";
+                  %10 : Var<java.lang.Object> = var %9 @"oC";
+                  return;
+            };
+            """)
+    static <X extends A & B & C> void test2(X x) {
+        Object oA = x.f_A;
+        Object oB = x.f_B;
+        Object oC = x.f_C;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : #IntersectionTypeTest::test3(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>)void -> {
+                  %1 : Var<#IntersectionTypeTest::test3(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>> = var %0 @"x";
+                  %2 : #IntersectionTypeTest::test3(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %3 : Var<IntersectionTypeTest$A> = var %2 @"rec$";
+                  %4 : java.lang.Runnable = lambda ()void -> {
+                      %5 : IntersectionTypeTest$A = var.load %3;
+                      invoke %5 @"IntersectionTypeTest$A::m_A()void";
+                      return;
+                  };
+                  %6 : Var<java.lang.Runnable> = var %4 @"rA";
+                  %7 : #IntersectionTypeTest::test3(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %8 : IntersectionTypeTest$B = cast %7 @"IntersectionTypeTest$B";
+                  %9 : Var<IntersectionTypeTest$B> = var %8 @"rec$";
+                  %10 : java.lang.Runnable = lambda ()void -> {
+                      %11 : IntersectionTypeTest$B = var.load %9;
+                      invoke %11 @"IntersectionTypeTest$B::m_B()void";
+                      return;
+                  };
+                  %12 : Var<java.lang.Runnable> = var %10 @"rB";
+                  %13 : #IntersectionTypeTest::test3(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                  %14 : IntersectionTypeTest$C = cast %13 @"IntersectionTypeTest$C";
+                  %15 : Var<IntersectionTypeTest$C> = var %14 @"rec$";
+                  %16 : java.lang.Runnable = lambda ()void -> {
+                      %17 : IntersectionTypeTest$C = var.load %15;
+                      invoke %17 @"IntersectionTypeTest$C::m_C()void";
+                      return;
+                  };
+                  %18 : Var<java.lang.Runnable> = var %16 @"rC";
+                  return;
+            };
+            """)
+    static <X extends A & B & C> void test3(X x) {
+        Runnable rA = x::m_A;
+        Runnable rB = x::m_B;
+        Runnable rC = x::m_C;
+    }
+
+    static void g_A(A a) { }
+    static void g_B(B a) { }
+    static void g_C(C a) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : #IntersectionTypeTest::test4(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>)void -> {
+                %1 : Var<#IntersectionTypeTest::test4(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A>> = var %0 @"x";
+                %2 : #IntersectionTypeTest::test4(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                invoke %2 @"IntersectionTypeTest::g_A(IntersectionTypeTest$A)void";
+                %3 : #IntersectionTypeTest::test4(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                %4 : IntersectionTypeTest$B = cast %3 @"IntersectionTypeTest$B";
+                invoke %4 @"IntersectionTypeTest::g_B(IntersectionTypeTest$B)void";
+                %5 : #IntersectionTypeTest::test4(IntersectionTypeTest$A)void::X<IntersectionTypeTest$A> = var.load %1;
+                %6 : IntersectionTypeTest$C = cast %5 @"IntersectionTypeTest$C";
+                invoke %6 @"IntersectionTypeTest::g_C(IntersectionTypeTest$C)void";
+                return;
+            };
+            """)
+    static <X extends A & B & C> void test4(X x) {
+        g_A(x);
+        g_B(x);
+        g_C(x);
+    }
+
+    static <X extends A & B & C> X makeIntersection(X x1, X x2) {
+        return null;
+    }
+
+    class E1 implements A, B, C {
+        @Override
+        public void m_A() { }
+        @Override
+        public void m_B() { }
+        @Override
+        public void m_C() { }
+    }
+
+    class E2 implements A, B, C {
+        @Override
+        public void m_A() { }
+        @Override
+        public void m_B() { }
+        @Override
+        public void m_C() { }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : IntersectionTypeTest$E1, %1 : IntersectionTypeTest$E2)void -> {
+                %2 : Var<IntersectionTypeTest$E1> = var %0 @"e1";
+                %3 : Var<IntersectionTypeTest$E2> = var %1 @"e2";
+                %4 : IntersectionTypeTest$E1 = var.load %2;
+                %5 : IntersectionTypeTest$E2 = var.load %3;
+                %6 : IntersectionTypeTest$A = invoke %4 %5 @"IntersectionTypeTest::makeIntersection(IntersectionTypeTest$A, IntersectionTypeTest$A)IntersectionTypeTest$A";
+                %7 : Var<IntersectionTypeTest$A> = var %6 @"x";
+                %8 : IntersectionTypeTest$A = var.load %7;
+                invoke %8 @"IntersectionTypeTest$A::m_A()void";
+                %9 : IntersectionTypeTest$A = var.load %7;
+                %10 : IntersectionTypeTest$B = cast %9 @"IntersectionTypeTest$B";
+                invoke %10 @"IntersectionTypeTest$B::m_B()void";
+                %11 : IntersectionTypeTest$A = var.load %7;
+                %12 : IntersectionTypeTest$C = cast %11 @"IntersectionTypeTest$C";
+                invoke %12 @"IntersectionTypeTest$C::m_C()void";
+                return;
+            };
+            """)
+    static void test5(E1 e1, E2 e2) {
+        var x = makeIntersection(e1, e2);
+        x.m_A();
+        x.m_B();
+        x.m_C();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : IntersectionTypeTest$E1, %1 : IntersectionTypeTest$E2)void -> {
+                %2 : Var<IntersectionTypeTest$E1> = var %0 @"e1";
+                %3 : Var<IntersectionTypeTest$E2> = var %1 @"e2";
+                %4 : IntersectionTypeTest$E1 = var.load %2;
+                %5 : IntersectionTypeTest$E2 = var.load %3;
+                %6 : IntersectionTypeTest$A = invoke %4 %5 @"IntersectionTypeTest::makeIntersection(IntersectionTypeTest$A, IntersectionTypeTest$A)IntersectionTypeTest$A";
+                %7 : Var<IntersectionTypeTest$A> = var %6 @"x";
+                %8 : IntersectionTypeTest$A = var.load %7;
+                %9 : java.lang.Object = field.load @"IntersectionTypeTest$A::f_A()java.lang.Object";
+                %10 : Var<java.lang.Object> = var %9 @"oA";
+                %11 : IntersectionTypeTest$A = var.load %7;
+                %12 : java.lang.Object = field.load @"IntersectionTypeTest$B::f_B()java.lang.Object";
+                %13 : Var<java.lang.Object> = var %12 @"oB";
+                %14 : IntersectionTypeTest$A = var.load %7;
+                %15 : java.lang.Object = field.load @"IntersectionTypeTest$C::f_C()java.lang.Object";
+                %16 : Var<java.lang.Object> = var %15 @"oC";
+                return;
+            };
+            """)
+    static void test6(E1 e1, E2 e2) {
+        var x = makeIntersection(e1, e2);
+        Object oA = x.f_A;
+        Object oB = x.f_B;
+        Object oC = x.f_C;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : IntersectionTypeTest$E1, %1 : IntersectionTypeTest$E2)void -> {
+                %2 : Var<IntersectionTypeTest$E1> = var %0 @"e1";
+                %3 : Var<IntersectionTypeTest$E2> = var %1 @"e2";
+                %4 : IntersectionTypeTest$E1 = var.load %2;
+                %5 : IntersectionTypeTest$E2 = var.load %3;
+                %6 : IntersectionTypeTest$A = invoke %4 %5 @"IntersectionTypeTest::makeIntersection(IntersectionTypeTest$A, IntersectionTypeTest$A)IntersectionTypeTest$A";
+                %7 : Var<IntersectionTypeTest$A> = var %6 @"x";
+                %8 : IntersectionTypeTest$A = var.load %7;
+                %9 : Var<IntersectionTypeTest$A> = var %8 @"rec$";
+                %10 : java.lang.Runnable = lambda ()void -> {
+                    %11 : IntersectionTypeTest$A = var.load %9;
+                    invoke %11 @"IntersectionTypeTest$A::m_A()void";
+                    return;
+                };
+                %12 : Var<java.lang.Runnable> = var %10 @"rA";
+                %13 : IntersectionTypeTest$A = var.load %7;
+                %14 : IntersectionTypeTest$B = cast %13 @"IntersectionTypeTest$B";
+                %15 : Var<IntersectionTypeTest$B> = var %14 @"rec$";
+                %16 : java.lang.Runnable = lambda ()void -> {
+                    %17 : IntersectionTypeTest$B = var.load %15;
+                    invoke %17 @"IntersectionTypeTest$B::m_B()void";
+                    return;
+                };
+                %18 : Var<java.lang.Runnable> = var %16 @"rB";
+                %19 : IntersectionTypeTest$A = var.load %7;
+                %20 : IntersectionTypeTest$C = cast %19 @"IntersectionTypeTest$C";
+                %21 : Var<IntersectionTypeTest$C> = var %20 @"rec$";
+                %22 : java.lang.Runnable = lambda ()void -> {
+                    %23 : IntersectionTypeTest$C = var.load %21;
+                    invoke %23 @"IntersectionTypeTest$C::m_C()void";
+                    return;
+                };
+                %24 : Var<java.lang.Runnable> = var %22 @"rC";
+                return;
+            };
+            """)
+    static void test7(E1 e1, E2 e2) {
+        var x = makeIntersection(e1, e2);
+        Runnable rA = x::m_A;
+        Runnable rB = x::m_B;
+        Runnable rC = x::m_C;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : IntersectionTypeTest$E1, %1 : IntersectionTypeTest$E2)void -> {
+                %2 : Var<IntersectionTypeTest$E1> = var %0 @"e1";
+                %3 : Var<IntersectionTypeTest$E2> = var %1 @"e2";
+                %4 : IntersectionTypeTest$E1 = var.load %2;
+                %5 : IntersectionTypeTest$E2 = var.load %3;
+                %6 : IntersectionTypeTest$A = invoke %4 %5 @"IntersectionTypeTest::makeIntersection(IntersectionTypeTest$A, IntersectionTypeTest$A)IntersectionTypeTest$A";
+                %7 : Var<IntersectionTypeTest$A> = var %6 @"x";
+                %8 : IntersectionTypeTest$A = var.load %7;
+                invoke %8 @"IntersectionTypeTest::g_A(IntersectionTypeTest$A)void";
+                %9 : IntersectionTypeTest$A = var.load %7;
+                %10 : IntersectionTypeTest$B = cast %9 @"IntersectionTypeTest$B";
+                invoke %10 @"IntersectionTypeTest::g_B(IntersectionTypeTest$B)void";
+                %11 : IntersectionTypeTest$A = var.load %7;
+                %12 : IntersectionTypeTest$C = cast %11 @"IntersectionTypeTest$C";
+                invoke %12 @"IntersectionTypeTest::g_C(IntersectionTypeTest$C)void";
+                return;
+            };
+            """)
+    static void test8(E1 e1, E2 e2) {
+        var x = makeIntersection(e1, e2);
+        g_A(x);
+        g_B(x);
+        g_C(x);
+    }
+}
diff a/test/langtools/tools/javac/reflect/LambdaTest.java b/test/langtools/tools/javac/reflect/LambdaTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/LambdaTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with lambda expressions.
+ * @build LambdaTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester LambdaTest
+ */
+
+public class LambdaTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : LambdaTest)void -> {
+                %1 : java.util.function.Consumer<java.lang.String> = lambda (%2 : java.lang.String)void -> {
+                    %3 : Var<java.lang.String> = var %2 @"s";
+                    %4 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                    %5 : java.lang.String = var.load %3;
+                    invoke %4 %5 @"java.io.PrintStream::println(java.lang.String)void";
+                    return;
+                };
+                %6 : Var<java.util.function.Consumer<java.lang.String>> = var %1 @"c";
+                %7 : java.util.function.Consumer<java.lang.String> = var.load %6;
+                %8 : java.lang.String = constant @"Hello World";
+                invoke %7 %8 @"java.util.function.Consumer::accept(java.lang.Object)void";
+                return;
+            };
+            """)
+    void test1() {
+        Consumer<String> c = s -> {
+            System.out.println(s);
+        };
+        c.accept("Hello World");
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : LambdaTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.String> = lambda ()java.lang.String -> {
+                    %2 : java.lang.String = constant @"Hello World";
+                    return %2;
+                };
+                %3 : Var<java.util.function.Supplier<java.lang.String>> = var %1 @"c";
+                %4 : java.util.function.Supplier<java.lang.String> = var.load %3;
+                %5 : java.lang.String = invoke %4 @"java.util.function.Supplier::get()java.lang.Object";
+                %6 : Var<java.lang.String> = var %5 @"s";
+                return;
+            };
+            """)
+    void test2() {
+        Supplier<String> c = () -> {
+            return "Hello World";
+        };
+        String s = c.get();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : LambdaTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.String> = lambda ()java.lang.String -> {
+                    %2 : java.lang.String = constant @"Hello World";
+                    return %2;
+                };
+                %3 : Var<java.util.function.Supplier<java.lang.String>> = var %1 @"c";
+                return;
+            };
+            """)
+    void test3() {
+        Supplier<String> c = () -> "Hello World";
+    }
+
+    String s_f;
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : LambdaTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.String> = lambda ()java.lang.String -> {
+                    %2 : java.lang.String = field.load %0 @"LambdaTest::s_f()java.lang.String";
+                    return %2;
+                };
+                %3 : Var<java.util.function.Supplier<java.lang.String>> = var %1 @"c";
+                return;
+            };
+            """)
+    void test4() {
+        Supplier<String> c = () -> {
+            return s_f;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : LambdaTest, %1 : int, %2 : int)void -> {
+                %3 : Var<int> = var %1 @"i";
+                %4 : Var<int> = var %2 @"j";
+                %5 : int = constant @"3";
+                %6 : Var<int> = var %5 @"k";
+                %7 : java.util.function.Supplier<java.lang.Integer> = lambda ()java.lang.Integer -> {
+                    %8 : int = constant @"4";
+                    %9 : Var<int> = var %8 @"l";
+                    %10 : java.util.function.Supplier<java.lang.Integer> = lambda ()java.lang.Integer -> {
+                        %11 : int = var.load %4;
+                        %12 : int = var.load %6;
+                        %13 : int = add %11 %12;
+                        %14 : int = var.load %9;
+                        %15 : int = add %13 %14;
+                        %16 : Var<int> = var %15 @"r";
+                        %17 : int = var.load %16;
+                        %18 : java.lang.Integer = invoke %17 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                        return %18;
+                    };
+                    %19 : Var<java.util.function.Supplier<java.lang.Integer>> = var %10 @"sInner";
+                    %20 : int = var.load %3;
+                    %21 : java.util.function.Supplier<java.lang.Integer> = var.load %19;
+                    %22 : java.lang.Integer = invoke %21 @"java.util.function.Supplier::get()java.lang.Object";
+                    %23 : int = invoke %22 @"java.lang.Integer::intValue()int";
+                    %24 : int = add %20 %23;
+                    %25 : Var<int> = var %24 @"r";
+                    %26 : int = var.load %25;
+                    %27 : java.lang.Integer = invoke %26 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                    return %27;
+                };
+                %28 : Var<java.util.function.Supplier<java.lang.Integer>> = var %7 @"sOuter";
+                return;
+            };
+            """)
+    void test5(int i, int j) {
+        int k = 3;
+        Supplier<Integer> sOuter = () -> {
+            int l = 4;
+            Supplier<Integer> sInner = () -> {
+                int r = j + k + l;
+                return r;
+            };
+
+            int r = i + sInner.get();
+            return r;
+        };
+    }
+
+    int f;
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : LambdaTest)void -> {
+                %1 : java.util.function.Supplier<java.lang.Integer> = lambda ()java.lang.Integer -> {
+                    %2 : int = field.load %0 @"LambdaTest::f()int";
+                    %3 : java.lang.Integer = invoke %2 @"java.lang.Integer::valueOf(int)java.lang.Integer";
+                    return %3;
+                };
+                %4 : Var<java.util.function.Supplier<java.lang.Integer>> = var %1 @"s";
+                return;
+            };
+            """)
+    void test6() {
+        Supplier<Integer> s = () -> f;
+    }
+}
diff a/test/langtools/tools/javac/reflect/LocalVarTest.java b/test/langtools/tools/javac/reflect/LocalVarTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/LocalVarTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with local variables.
+ * @build LocalVarTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester LocalVarTest
+ */
+
+public class LocalVarTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"1";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = constant @"2";
+                %4 : Var<int> = var %3 @"y";
+                %5 : int = var.load %2;
+                %6 : int = var.load %4;
+                %7 : int = add %5 %6;
+                return %7;
+            };
+            """)
+    int test1() {
+        int x = 1;
+        int y = 2;
+        return x + y;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : LocalVarTest, %1 : int, %2 : int)int -> {
+                %3 : Var<int> = var %1 @"x";
+                %4 : Var<int> = var %2 @"y";
+                %5 : int = var.load %3;
+                %6 : int = var.load %4;
+                %7 : int = add %5 %6;
+                return %7;
+            };
+            """)
+    int test2(int x, int y) {
+        return x + y;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = constant @"0";
+                %4 : Var<int> = var %3 @"y";
+                %5 : int = constant @"1";
+                var.store %2 %5;
+                %6 : int = constant @"2";
+                var.store %4 %6;
+                %7 : int = var.load %2;
+                %8 : int = var.load %4;
+                %9 : int = add %7 %8;
+                return %9;
+            };
+            """)
+    int test3() {
+        int x;
+        int y;
+        x = 1;
+        y = 2;
+        return x + y;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"1";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = var.load %2;
+                %4 : int = constant @"1";
+                %5 : int = add %3 %4;
+                %6 : Var<int> = var %5 @"y";
+                %7 : int = var.load %6;
+                return %7;
+            };
+            """)
+    int test4() {
+        int x = 1;
+        int y = x + 1;
+        return y;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"1";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = var.load %2;
+                %4 : Var<int> = var %3 @"y";
+                %5 : int = var.load %4;
+                return %5;
+            };
+            """)
+    int test5() {
+        int x = 1;
+        int y = x;
+        return y;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"1";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = constant @"1";
+                %4 : Var<int> = var %3 @"y";
+                %5 : int = constant @"1";
+                %6 : Var<int> = var %5 @"z";
+                %7 : int = var.load %2;
+                var.store %4 %7;
+                var.store %6 %7;
+                %8 : int = var.load %6;
+                return %8;
+            };
+            """)
+    int test6() {
+        int x = 1;
+        int y = 1;
+        int z = 1;
+        z = y = x;
+        return z;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : LocalVarTest)int -> {
+                %1 : int = constant @"1";
+                %2 : Var<int> = var %1 @"x";
+                %3 : int = var.load %2;
+                %4 : int = constant @"2";
+                %5 : int = add %3 %4;
+                var.store %2 %5;
+                %6 : Var<int> = var %5 @"y";
+                %7 : int = var.load %6;
+                %8 : int = constant @"3";
+                %9 : int = add %7 %8;
+                var.store %6 %9;
+                %10 : int = var.load %2;
+                %11 : int = constant @"4";
+                %12 : int = add %10 %11;
+                var.store %2 %12;
+                %13 : int = add %9 %12;
+                return %13;
+            };
+            """)
+    int test7() {
+        int x = 1;
+        int y = x += 2;
+        return (y += 3) + (x += 4);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : LocalVarTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = constant @"1";
+                %5 : int = add %3 %4;
+                var.store %2 %5;
+                %6 : Var<int> = var %3 @"x";
+                %7 : int = var.load %2;
+                %8 : int = constant @"1";
+                %9 : int = sub %7 %8;
+                var.store %2 %9;
+                %10 : Var<int> = var %7 @"y";
+                return;
+            };
+            """)
+    void test8(int i) {
+        int x = i++;
+        int y = i--;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : LocalVarTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = constant @"1";
+                %5 : int = add %3 %4;
+                var.store %2 %5;
+                %6 : Var<int> = var %5 @"x";
+                %7 : int = var.load %2;
+                %8 : int = constant @"1";
+                %9 : int = sub %7 %8;
+                var.store %2 %9;
+                %10 : Var<int> = var %9 @"y";
+                return;
+            };
+            """)
+    void test9(int i) {
+        int x = ++i;
+        int y = --i;
+    }
+}
diff a/test/langtools/tools/javac/reflect/MethodCallTest.java b/test/langtools/tools/javac/reflect/MethodCallTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/MethodCallTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with method calls.
+ * @build MethodCallTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester MethodCallTest
+ */
+
+public class MethodCallTest {
+
+    void m() {
+    }
+
+    int m_int() {
+        return 0;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : MethodCallTest)void -> {
+                invoke %0 @"MethodCallTest::m()void";
+                return;
+            };
+            """)
+    void test1() {
+        m();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : MethodCallTest)void -> {
+                invoke %0 @"MethodCallTest::m()void";
+                return;
+            };
+            """)
+    void test2() {
+        this.m();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : MethodCallTest)int -> {
+                %1 : int = invoke %0 @"MethodCallTest::m_int()int";
+                return %1;
+            };
+            """)
+    int test3() {
+        return m_int();
+    }
+
+
+    static void ms() {
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : MethodCallTest)void -> {
+                invoke @"MethodCallTest::ms()void";
+                return;
+            };
+            """)
+    void test4() {
+        ms();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4_1" (%0 : MethodCallTest)void -> {
+                invoke @"MethodCallTest::ms()void";
+                return;
+            };
+            """)
+    void test4_1() {
+        MethodCallTest.ms();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4_2" (%0 : MethodCallTest)java.util.List<java.lang.String> -> {
+                %1 : java.util.List<java.lang.String> = invoke @"java.util.List::of()java.util.List";
+                return %1;
+            };
+            """)
+    List<String> test4_2() {
+        return List.of();
+    }
+
+    String m(int i, String s, List<Number> l) {
+        return s;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : MethodCallTest, %1 : java.util.List<java.lang.Number>)void -> {
+                %2 : Var<java.util.List<java.lang.Number>> = var %1 @"l";
+                %3 : int = constant @"1";
+                %4 : java.lang.String = constant @"1";
+                %5 : java.util.List<java.lang.Number> = var.load %2;
+                %6 : java.lang.String = invoke %0 %3 %4 %5 @"MethodCallTest::m(int, java.lang.String, java.util.List)java.lang.String";
+                %7 : Var<java.lang.String> = var %6 @"s";
+                return;
+            };
+            """)
+    void test5(List<Number> l) {
+        String s = m(1, "1", l);
+    }
+
+
+    static class A {
+        B b;
+
+        B m() {
+            return null;
+        }
+    }
+
+    static class B {
+        C m() {
+            return null;
+        }
+    }
+
+    static class C {
+        int m() {
+            return 0;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : MethodCallTest, %1 : MethodCallTest$A)void -> {
+                %2 : Var<MethodCallTest$A> = var %1 @"a";
+                %3 : MethodCallTest$A = var.load %2;
+                %4 : MethodCallTest$B = invoke %3 @"MethodCallTest$A::m()MethodCallTest$B";
+                %5 : MethodCallTest$C = invoke %4 @"MethodCallTest$B::m()MethodCallTest$C";
+                %6 : int = invoke %5 @"MethodCallTest$C::m()int";
+                return;
+            };
+            """)
+    void test6(A a) {
+        a.m().m().m();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : MethodCallTest, %1 : MethodCallTest$A)void -> {
+                %2 : Var<MethodCallTest$A> = var %1 @"a";
+                %3 : MethodCallTest$A = var.load %2;
+                %4 : MethodCallTest$B = field.load %3 @"MethodCallTest$A::b()MethodCallTest$B";
+                %5 : MethodCallTest$C = invoke %4 @"MethodCallTest$B::m()MethodCallTest$C";
+                return;
+            };
+            """)
+    void test7(A a) {
+        a.b.m();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : MethodCallTest, %1 : java.lang.String)void -> {
+                %2 : Var<java.lang.String> = var %1 @"s";
+                %3 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                %4 : java.lang.String = var.load %2;
+                invoke %3 %4 @"java.io.PrintStream::println(java.lang.String)void";
+                return;
+            };
+            """)
+    void test8(String s) {
+        System.out.println(s);
+    }
+
+    static class X {
+        int x;
+        void x() {}
+
+        static void sx() {}
+    }
+
+    static class Y extends X {
+        void y() {}
+        static void sy() {}
+
+        @CodeReflection
+        @IR("""
+                func @"test" (%0 : MethodCallTest$Y)void -> {
+                    invoke %0 @"MethodCallTest$Y::x()void";
+                    invoke %0 @"MethodCallTest$Y::y()void";
+                    invoke @"MethodCallTest$Y::sx()void";
+                    invoke @"MethodCallTest$Y::sy()void";
+                    invoke @"MethodCallTest$Y::sx()void";
+                    invoke @"MethodCallTest$Y::sy()void";
+                    return;
+                };
+                """)
+        void test() {
+            x();
+            y();
+
+            sx();
+            sy();
+
+            Y.sx();
+            Y.sy();
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : MethodCallTest$Y)void -> {
+                %1 : Var<MethodCallTest$Y> = var %0 @"y";
+                %2 : MethodCallTest$Y = var.load %1;
+                invoke %2 @"MethodCallTest$Y::x()void";
+                %3 : MethodCallTest$Y = var.load %1;
+                invoke %3 @"MethodCallTest$Y::y()void";
+                %4 : MethodCallTest$Y = var.load %1;
+                invoke @"MethodCallTest$Y::sx()void";
+                %5 : MethodCallTest$Y = var.load %1;
+                invoke @"MethodCallTest$Y::sy()void";
+                invoke @"MethodCallTest$Y::sx()void";
+                invoke @"MethodCallTest$Y::sy()void";
+                return;
+            };
+            """)
+    static void test9(Y y) {
+        y.x();
+        y.y();
+
+        y.sx();
+        y.sy();
+
+        Y.sx();
+        Y.sy();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : java.util.ArrayList<java.lang.String>)void -> {
+                %1 : Var<java.util.ArrayList<java.lang.String>> = var %0 @"al";
+                %2 : java.util.ArrayList<java.lang.String> = var.load %1;
+                %3 : int = constant @"0";
+                %4 : java.lang.String = invoke %2 %3 @"java.util.ArrayList::get(int)java.lang.Object";
+                %5 : Var<java.lang.String> = var %4 @"s";
+                %6 : java.util.ArrayList<java.lang.String> = var.load %1;
+                %7 : Var<java.util.List<java.lang.String>> = var %6 @"l";
+                %8 : java.util.List<java.lang.String> = var.load %7;
+                %9 : int = constant @"0";
+                %10 : java.lang.String = invoke %8 %9 @"java.util.List::get(int)java.lang.Object";
+                var.store %5 %10;
+                return;
+            };
+            """)
+    static void test10(ArrayList<String> al) {
+        String s = al.get(0);
+        List<String> l = al;
+        s = l.get(0);
+    }
+}
diff a/test/langtools/tools/javac/reflect/MethodReferenceTest.java b/test/langtools/tools/javac/reflect/MethodReferenceTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/MethodReferenceTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with method reference expressions.
+ * @build MethodReferenceTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester MethodReferenceTest
+ */
+
+public class MethodReferenceTest {
+
+    static void m_s(String s) {}
+
+    void m(String s) {}
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.Consumer<java.lang.String> = lambda (%2 : java.lang.String)void -> {
+                    %3 : Var<java.lang.String> = var %2 @"x$0";
+                    %4 : java.lang.String = var.load %3;
+                    invoke %4 @"MethodReferenceTest::m_s(java.lang.String)void";
+                    return;
+                };
+                %5 : Var<java.util.function.Consumer<java.lang.String>> = var %1 @"c";
+                return;
+            };
+            """)
+    void test1() {
+        Consumer<String> c = MethodReferenceTest::m_s;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.BiConsumer<MethodReferenceTest, java.lang.String> = lambda (%2 : MethodReferenceTest, %3 : java.lang.String)void -> {
+                    %4 : Var<MethodReferenceTest> = var %2 @"rec$";
+                    %5 : Var<java.lang.String> = var %3 @"x$0";
+                    %6 : MethodReferenceTest = var.load %4;
+                    %7 : java.lang.String = var.load %5;
+                    invoke %6 %7 @"MethodReferenceTest::m(java.lang.String)void";
+                    return;
+                };
+                %8 : Var<java.util.function.BiConsumer<MethodReferenceTest, java.lang.String>> = var %1 @"bc";
+                return;
+            };
+            """)
+    void test2() {
+        BiConsumer<MethodReferenceTest, String> bc = MethodReferenceTest::m;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.Consumer<java.lang.String> = lambda (%2 : java.lang.String)void -> {
+                    %3 : Var<java.lang.String> = var %2 @"x$0";
+                    %4 : java.lang.String = var.load %3;
+                    invoke %0 %4 @"MethodReferenceTest::m(java.lang.String)void";
+                    return;
+                };
+                %5 : Var<java.util.function.Consumer<java.lang.String>> = var %1 @"c";
+                return;
+            };
+            """)
+    void test3() {
+        Consumer<String> c = this::m;
+    }
+
+    class A<T> {
+        T m(T t) { return t; }
+    }
+
+    <T> A<T> a(T t) { return null; }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : MethodReferenceTest)void -> {
+                %1 : java.lang.String = constant @"s";
+                %2 : MethodReferenceTest$A<java.lang.String> = invoke %0 %1 @"MethodReferenceTest::a(java.lang.Object)MethodReferenceTest$A";
+                %3 : Var<MethodReferenceTest$A<java.lang.String>> = var %2 @"rec$";
+                %4 : java.util.function.Function<java.lang.String, java.lang.String> = lambda (%5 : java.lang.String)java.lang.String -> {
+                    %6 : Var<java.lang.String> = var %5 @"x$0";
+                    %7 : MethodReferenceTest$A<java.lang.String> = var.load %3;
+                    %8 : java.lang.String = var.load %6;
+                    %9 : java.lang.String = invoke %7 %8 @"MethodReferenceTest$A::m(java.lang.Object)java.lang.Object";
+                    return %9;
+                };
+                %10 : Var<java.util.function.Function<java.lang.String, java.lang.String>> = var %4 @"f";
+                return;
+            };
+            """)
+    void test4() {
+        Function<String, String> f = a("s")::m;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : MethodReferenceTest)void -> {
+                %1 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                %2 : Var<java.io.PrintStream> = var %1 @"rec$";
+                %3 : java.util.function.Consumer<java.lang.String> = lambda (%4 : java.lang.String)void -> {
+                    %5 : Var<java.lang.String> = var %4 @"x$0";
+                    %6 : java.io.PrintStream = var.load %2;
+                    %7 : java.lang.String = var.load %5;
+                    invoke %6 %7 @"java.io.PrintStream::println(java.lang.String)void";
+                    return;
+                };
+                %8 : Var<java.util.function.Consumer<java.lang.String>> = var %3 @"c3";
+                return;
+            };
+            """)
+    void test5() {
+        Consumer<String> c3 = System.out::println;
+    }
+
+    static class X {
+        X(int i) {}
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.Function<java.lang.Integer, MethodReferenceTest$X> = lambda (%2 : java.lang.Integer)MethodReferenceTest$X -> {
+                    %3 : Var<java.lang.Integer> = var %2 @"x$0";
+                    %4 : java.lang.Integer = var.load %3;
+                    %5 : int = invoke %4 @"java.lang.Integer::intValue()int";
+                    %6 : MethodReferenceTest$X = new %5 @"func<MethodReferenceTest$X, int>";
+                    return %6;
+                };
+                %7 : Var<java.util.function.Function<java.lang.Integer, MethodReferenceTest$X>> = var %1 @"xNew";
+                return;
+            };
+            """)
+    void test6() {
+        Function<Integer, X> xNew = X::new;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.Supplier<MethodReferenceTest$A<java.lang.String>> = lambda ()MethodReferenceTest$A<java.lang.String> -> {
+                    %2 : MethodReferenceTest$A<java.lang.String> = new %0 @"func<MethodReferenceTest$A>";
+                    return %2;
+                };
+                %3 : Var<java.util.function.Supplier<MethodReferenceTest$A<java.lang.String>>> = var %1 @"aNew";
+                return;
+            };
+            """)
+    void test7() {
+        Supplier<A<String>> aNew = A::new;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : MethodReferenceTest)void -> {
+                %1 : java.util.function.IntFunction<MethodReferenceTest$A<java.lang.String>[]> = lambda (%2 : int)MethodReferenceTest$A<java.lang.String>[] -> {
+                    %3 : Var<int> = var %2 @"x$0";
+                    %4 : int = var.load %3;
+                    %5 : MethodReferenceTest$A[] = new %4 @"func<MethodReferenceTest$A[], int>";
+                    return %5;
+                };
+                %6 : Var<java.util.function.IntFunction<MethodReferenceTest$A<java.lang.String>[]>> = var %1 @"aNewArray";
+                return;
+            };
+            """)
+    void test8() {
+        IntFunction<A<String>[]> aNewArray = A[]::new;
+    }
+
+}
diff a/test/langtools/tools/javac/reflect/NewArrayTest.java b/test/langtools/tools/javac/reflect/NewArrayTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/NewArrayTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with constant values.
+ * @build NewArrayTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester NewArrayTest
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Function;
+
+public class NewArrayTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"10";
+                %2 : int[] = new %1 @"func<int[], int>";
+                %3 : Var<int[]> = var %2 @"a";
+                return;
+            };
+            """)
+    void test1() {
+        int[] a = new int[10];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : NewArrayTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"l";
+                %3 : int = var.load %2;
+                %4 : int = constant @"10";
+                %5 : int = add %3 %4;
+                %6 : int[] = new %5 @"func<int[], int>";
+                %7 : Var<int[]> = var %6 @"a";
+                return;
+            };
+            """)
+    void test2(int l) {
+        int[] a = new int[l + 10];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"10";
+                %2 : java.lang.String[] = new %1 @"func<java.lang.String[], int>";
+                %3 : Var<java.lang.String[]> = var %2 @"a";
+                return;
+            };
+            """)
+    void test3() {
+        String[] a = new String[10];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"10";
+                %2 : java.lang.String[][] = new %1 @"func<java.lang.String[][], int>";
+                %3 : Var<java.lang.String[][]> = var %2 @"a";
+                return;
+            };
+            """)
+    void test4() {
+        String[][] a = new String[10][];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"10";
+                %2 : int = constant @"10";
+                %3 : java.lang.String[][] = new %1 %2 @"func<java.lang.String[][], int, int>";
+                %4 : Var<java.lang.String[][]> = var %3 @"a";
+                return;
+            };
+            """)
+    void test5() {
+        String[][] a = new String[10][10];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"3";
+                %2 : java.lang.String[][] = new %1 @"func<java.lang.String[][], int>";
+                %3 : int = constant @"2";
+                %4 : java.lang.String[] = new %3 @"func<java.lang.String[], int>";
+                %5 : java.lang.String = constant @"one";
+                %6 : int = constant @"0";
+                array.store %4 %6 %5;
+                %7 : java.lang.String = constant @"two";
+                %8 : int = constant @"1";
+                array.store %4 %8 %7;
+                %9 : int = constant @"0";
+                array.store %2 %9 %4;
+                %10 : int = constant @"1";
+                %11 : java.lang.String[] = new %10 @"func<java.lang.String[], int>";
+                %12 : java.lang.String = constant @"three";
+                %13 : int = constant @"0";
+                array.store %11 %13 %12;
+                %14 : int = constant @"1";
+                array.store %2 %14 %11;
+                %15 : java.lang.String[] = constant @null;
+                %16 : int = constant @"2";
+                array.store %2 %16 %15;
+                %17 : Var<java.lang.String[][]> = var %2 @"a";
+                return;
+            };
+            """)
+    void test6() {
+        String[][] a = { { "one", "two" }, { "three" }, null };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : NewArrayTest)void -> {
+                %1 : int = constant @"3";
+                %2 : java.lang.String[][] = new %1 @"func<java.lang.String[][], int>";
+                %3 : int = constant @"2";
+                %4 : java.lang.String[] = new %3 @"func<java.lang.String[], int>";
+                %5 : java.lang.String = constant @"one";
+                %6 : int = constant @"0";
+                array.store %4 %6 %5;
+                %7 : java.lang.String = constant @"two";
+                %8 : int = constant @"1";
+                array.store %4 %8 %7;
+                %9 : int = constant @"0";
+                array.store %2 %9 %4;
+                %10 : int = constant @"1";
+                %11 : java.lang.String[] = new %10 @"func<java.lang.String[], int>";
+                %12 : java.lang.String = constant @"three";
+                %13 : int = constant @"0";
+                array.store %11 %13 %12;
+                %14 : int = constant @"1";
+                array.store %2 %14 %11;
+                %15 : java.lang.String[] = constant @null;
+                %16 : int = constant @"2";
+                array.store %2 %16 %15;
+                %17 : Var<java.lang.String[][]> = var %2 @"a";
+                return;
+            };
+            """)
+    void test7() {
+        String[][] a = new String[][] { { "one", "two" }, { "three" }, null };
+    }
+}
diff a/test/langtools/tools/javac/reflect/NewTest.java b/test/langtools/tools/javac/reflect/NewTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/NewTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.math.BigDecimal;
+import java.util.List;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with new expressions.
+ * @build NewTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester NewTest
+ */
+
+public class NewTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test0" (%0 : NewTest)void -> {
+                %1 : java.lang.String = constant @"1";
+                %2 : java.math.BigDecimal = new %1 @"func<java.math.BigDecimal, java.lang.String>";
+                %3 : Var<java.math.BigDecimal> = var %2 @"a";
+                return;
+            };
+            """)
+    void test0() {
+        BigDecimal a = new BigDecimal("1");
+    }
+
+    static class A {
+        A() {}
+
+        A(int i, int j) {}
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : NewTest)void -> {
+                %1 : NewTest$A = new @"func<NewTest$A>";
+                %2 : Var<NewTest$A> = var %1 @"a";
+                return;
+            };
+            """)
+    void test1() {
+        A a = new A();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : NewTest)void -> {
+                %1 : int = constant @"1";
+                %2 : int = constant @"2";
+                %3 : NewTest$A = new %1 %2 @"func<NewTest$A, int, int>";
+                %4 : Var<NewTest$A> = var %3 @"a";
+                return;
+            };
+            """)
+    void test2() {
+        A a = new A(1, 2);
+    }
+
+    class B {
+        B() {}
+
+        B(int i, int j) {}
+
+        class C {
+        }
+    }
+
+    B f;
+
+    B b() { return f; }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : NewTest)void -> {
+                %1 : NewTest$B = new %0 @"func<NewTest$B>";
+                %2 : Var<NewTest$B> = var %1 @"b";
+                return;
+            };
+            """)
+    void test3() {
+        B b = new B();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : NewTest)void -> {
+                %1 : int = constant @"1";
+                %2 : int = constant @"2";
+                %3 : NewTest$B = new %0 %1 %2 @"func<NewTest$B, int, int>";
+                %4 : Var<NewTest$B> = var %3 @"b";
+                return;
+            };
+            """)
+    void test4() {
+        B b = new B(1, 2);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : NewTest)void -> {
+                %1 : NewTest$B = new %0 @"func<NewTest$B>";
+                %2 : Var<NewTest$B> = var %1 @"b";
+                return;
+            };
+            """)
+    void test5() {
+        B b = this.new B();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : NewTest)void -> {
+                %1 : NewTest$B = field.load %0 @"NewTest::f()NewTest$B";
+                %2 : NewTest$B$C = new %1 @"func<NewTest$B$C>";
+                %3 : Var<NewTest$B$C> = var %2 @"c";
+                return;
+            };
+            """)
+    void test6() {
+        B.C c = f.new C();
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : NewTest)void -> {
+                %1 : NewTest$B = invoke %0 @"NewTest::b()NewTest$B";
+                %2 : NewTest$B$C = new %1 @"func<NewTest$B$C>";
+                %3 : Var<NewTest$B$C> = var %2 @"c";
+                return;
+            };
+            """)
+    void test7() {
+        B.C c = b().new C();
+    }
+
+    static class AG<T> {
+        AG(List<T> l) {}
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : NewTest, %1 : java.util.List<java.lang.String>)void -> {
+                %2 : Var<java.util.List<java.lang.String>> = var %1 @"l";
+                %3 : java.util.List<java.lang.String> = var.load %2;
+                %4 : NewTest$AG<java.lang.String> = new %3 @"func<NewTest$AG, java.util.List>";
+                %5 : Var<NewTest$AG<java.lang.String>> = var %4 @"a";
+                return;
+            };
+            """)
+    void test8(List<String> l) {
+        AG<String> a = new AG<>(l);
+    }
+
+    class BG<T> {
+        BG(List<T> l) {}
+
+        class CG<U> {
+            CG(List<U> l) {}
+        }
+    }
+
+    // @@@ This produces incorrect type descriptors for generic inner classes
+    // the type argument for type BG is not preserved
+//    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : NewTest, %1 : java.util.List<java.lang.String>, %2 : java.util.List<java.lang.Number>)void -> {
+                %3 : Var<java.util.List<java.lang.String>> = var %1 @"l1";
+                %4 : Var<java.util.List<java.lang.Number>> = var %2 @"l2";
+                %5 : java.util.List<java.lang.String> = var.load %3;
+                %6 : NewTest$BG<java.lang.String> = new %0 %5 @"func<NewTest$BG, java.util.List>";
+                %7 : java.util.List<java.lang.Number> = var.load %4;
+                %8 : NewTest$BG$CG<java.lang.Number> = new %6 %7 @"func<NewTest$BG$CG, java.util.List>";
+                %9 : Var<NewTest$BG$CG<java.lang.Number>> = var %8 @"numberCG";
+                return;
+            };
+            """)
+    void test9(List<String> l1, List<Number> l2) {
+        BG<String>.CG<Number> numberCG = new BG<String>(l1).new CG<Number>(l2);
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : NewTest)void -> {
+                %1 : int = constant @"10";
+                %2 : int[] = new %1 @"func<int[], int>";
+                %3 : Var<int[]> = var %2 @"i";
+                return;
+            };
+            """)
+    void test10() {
+        int[] i = new int[10];
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : NewTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"i";
+                %3 : int = var.load %2;
+                %4 : int = var.load %2;
+                %5 : int = constant @"1";
+                %6 : int = add %4 %5;
+                %7 : int = var.load %2;
+                %8 : int = constant @"2";
+                %9 : int = add %7 %8;
+                %10 : java.lang.String[][][] = new %3 %6 %9 @"func<java.lang.String[][][], int, int, int>";
+                %11 : Var<java.lang.String[][][]> = var %10 @"s";
+                return;
+            };
+            """)
+    void test11(int i) {
+        String[][][] s = new String[i][i + 1][i + 2];
+    }
+}
diff a/test/langtools/tools/javac/reflect/NullTest.java b/test/langtools/tools/javac/reflect/NullTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/NullTest.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Supplier;
+
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with null constants.
+ * @enablePreview
+ * @build NullTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester NullTest
+ */
+
+public class NullTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : Var<java.lang.String> = var %1 @"s";
+                return;
+            };
+            """)
+    void test1() {
+        String s = null;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : NullTest)void -> {
+                %1 : java.lang.Object = constant @null;
+                %2 : java.lang.String = cast %1 @"java.lang.String";
+                %3 : Var<java.lang.String> = var %2 @"s";
+                return;
+            };
+            """)
+    void test2() {
+        String s = (String)null;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : NullTest, %1 : boolean)java.lang.String -> {
+                %2 : Var<boolean> = var %1 @"cond";
+                %3 : java.lang.String = java.cexpression
+                    ^cond()boolean -> {
+                        %4 : boolean = var.load %2;
+                        yield %4;
+                    }
+                    ^truepart()java.lang.String -> {
+                        %5 : java.lang.String = constant @null;
+                        yield %5;
+                    }
+                    ^falsepart()java.lang.String -> {
+                        %6 : java.lang.String = constant @"";
+                        yield %6;
+                    };
+                return %3;
+            };
+            """)
+    String test3(boolean cond) {
+        return cond ? null : "";
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : NullTest, %1 : boolean)java.lang.String -> {
+                %2 : Var<boolean> = var %1 @"cond";
+                %3 : java.lang.String = java.cexpression
+                    ^cond()boolean -> {
+                        %4 : boolean = var.load %2;
+                        yield %4;
+                    }
+                    ^truepart()java.lang.String -> {
+                        %5 : java.lang.String = constant @"";
+                        yield %5;
+                    }
+                    ^falsepart()java.lang.String -> {
+                        %6 : java.lang.String = constant @null;
+                        yield %6;
+                    };
+                return %3;
+            };
+            """)
+    String test4(boolean cond) {
+        return cond ? "" : null;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : NullTest, %1 : boolean)java.lang.String -> {
+                %2 : Var<boolean> = var %1 @"cond";
+                %3 : java.lang.String = java.cexpression
+                    ^cond()boolean -> {
+                        %4 : boolean = var.load %2;
+                        yield %4;
+                    }
+                    ^truepart()java.lang.String -> {
+                        %5 : java.lang.String = constant @null;
+                        yield %5;
+                    }
+                    ^falsepart()java.lang.String -> {
+                        %6 : java.lang.String = constant @null;
+                        yield %6;
+                    };
+                return %3;
+            };
+            """)
+    String test5(boolean cond) {
+        return cond ? null : null;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : NullTest, %1 : boolean)java.lang.String -> {
+                %2 : Var<boolean> = var %1 @"cond";
+                %3 : java.lang.Object = java.cexpression
+                    ^cond()boolean -> {
+                        %4 : boolean = var.load %2;
+                        yield %4;
+                    }
+                    ^truepart()java.lang.Object -> {
+                        %5 : java.lang.Object = constant @null;
+                        yield %5;
+                    }
+                    ^falsepart()java.lang.Object -> {
+                        %6 : java.lang.Object = constant @null;
+                        yield %6;
+                    };
+                %7 : java.lang.String = cast %3 @"java.lang.String";
+                return %7;
+            };
+            """)
+    String test6(boolean cond) {
+        return (String)(cond ? null : null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @"";
+                        yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @null;
+                        yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test7(int cond) {
+        return switch(cond) {
+            case 1  -> "";
+            default -> null;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test8" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @null;
+                        yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @"";
+                        yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test8(int cond) {
+        return switch(cond) {
+            case 1  -> null;
+            default -> "";
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test9" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @null;
+                        yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @null;
+                        yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test9(int cond) {
+        return switch(cond) {
+            case 1  -> null;
+            default -> null;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test10" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.Object = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.Object -> {
+                        %8 : java.lang.Object = constant @null;
+                        yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %9 : java.lang.Object = constant @null;
+                        yield %9;
+                    };
+                %10 : java.lang.String = cast %4 @"java.lang.String";
+                return %10;
+            };
+            """)
+    String test10(int cond) {
+        return (String)switch(cond) {
+            case 1  -> null;
+            default -> null;
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test11" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @"";
+                        java.yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @null;
+                        java.yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test11(int cond) {
+        return switch(cond) {
+            case 1  -> { yield ""; }
+            default -> { yield null; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test12" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @null;
+                        java.yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @"";
+                        java.yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test12(int cond) {
+        return switch(cond) {
+            case 1  -> { yield null; }
+            default -> { yield ""; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test13" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.String = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.String -> {
+                        %8 : java.lang.String = constant @null;
+                        java.yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.String -> {
+                        %9 : java.lang.String = constant @null;
+                        java.yield %9;
+                    };
+                return %4;
+            };
+            """)
+    String test13(int cond) {
+        return switch(cond) {
+            case 1  -> { yield null; }
+            default -> { yield null; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test14" (%0 : NullTest, %1 : int)java.lang.String -> {
+                %2 : Var<int> = var %1 @"cond";
+                %3 : int = var.load %2;
+                %4 : java.lang.Object = java.switch.expression %3
+                    ^constantCaseLabel(%5 : int)boolean -> {
+                        %6 : int = constant @"1";
+                        %7 : boolean = eq %5 %6;
+                        yield %7;
+                    }
+                    ()java.lang.Object -> {
+                        %8 : java.lang.Object = constant @null;
+                        java.yield %8;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %9 : java.lang.Object = constant @null;
+                        java.yield %9;
+                    };
+                %10 : java.lang.String = cast %4 @"java.lang.String";
+                return %10;
+            };
+            """)
+    String test14(int cond) {
+        return (String)switch(cond) {
+            case 1  -> { yield null; }
+            default -> { yield null; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test15" (%0 : NullTest)java.util.function.Supplier<java.lang.String> -> {
+                %1 : java.util.function.Supplier<java.lang.String> = lambda ()java.lang.String -> {
+                    %2 : java.lang.String = constant @null;
+                    return %2;
+                };
+                return %1;
+            };
+            """)
+    Supplier<String> test15() {
+        return () -> null;
+    }
+
+    static void m(String s, String... ss) { }
+
+    @CodeReflection
+    @IR("""
+            func @"test16" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                invoke %1 @"NullTest::m(java.lang.String, java.lang.String[])void";
+                return;
+            };
+            """)
+    void test16() {
+        m(null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test17" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : java.lang.String[] = constant @null;
+                invoke %1 %2 @"NullTest::m(java.lang.String, java.lang.String[])void";
+                return;
+            };
+            """)
+    void test17() {
+        m(null, null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test18" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : java.lang.String = constant @null;
+                %3 : java.lang.String = constant @null;
+                invoke %1 %2 %3 @"NullTest::m(java.lang.String, java.lang.String[])void";
+                return;
+            };
+            """)
+    void test18() {
+        m(null, null, null);
+    }
+
+    static class Box {
+        Box(String s, String... ss) { }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test19" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : NullTest$Box = new %1 @"func<NullTest$Box, java.lang.String, java.lang.String[]>";
+                return;
+            };
+            """)
+    void test19() {
+        new Box(null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test20" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : java.lang.String[] = constant @null;
+                %3 : NullTest$Box = new %1 %2 @"func<NullTest$Box, java.lang.String, java.lang.String[]>";
+                return;
+            };
+            """)
+    void test20() {
+        new Box(null, null);
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test21" (%0 : NullTest)void -> {
+                %1 : java.lang.String = constant @null;
+                %2 : java.lang.String = constant @null;
+                %3 : java.lang.String = constant @null;
+                %4 : NullTest$Box = new %1 %2 %3 @"func<NullTest$Box, java.lang.String, java.lang.String[]>";
+                return;
+            };
+            """)
+    void test21() {
+        new Box(null, null, null);
+    }
+}
diff a/test/langtools/tools/javac/reflect/PatternsTest.java b/test/langtools/tools/javac/reflect/PatternsTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/PatternsTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with patterns.
+ * @enablePreview
+ * @build PatternsTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester PatternsTest
+ */
+
+public class PatternsTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : PatternsTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Object = var.load %2;
+                %4 : java.lang.String = constant @null;
+                %5 : Var<java.lang.String> = var %4 @"s";
+                %6 : boolean = pattern.match %3
+                    ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                        %7 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                        yield %7;
+                    }
+                    ^match(%8 : java.lang.String)void -> {
+                        var.store %5 %8;
+                        yield;
+                    };
+                %9 : Var<boolean> = var %6 @"x";
+                return;
+            };
+            """)
+    void test1(Object o) {
+        boolean x = o instanceof String s;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : PatternsTest, %1 : java.lang.Object)java.lang.String -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.String = constant @null;
+                %4 : Var<java.lang.String> = var %3 @"s";
+                java.if
+                    ()boolean -> {
+                        %5 : java.lang.Object = var.load %2;
+                        %6 : boolean = pattern.match %5
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                %7 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                yield %7;
+                            }
+                            ^match(%8 : java.lang.String)void -> {
+                                var.store %4 %8;
+                                yield;
+                            };
+                        yield %6;
+                    }
+                    ^then()void -> {
+                        %9 : java.lang.String = var.load %4;
+                        return %9;
+                    }
+                    ^else()void -> {
+                        %10 : java.lang.String = constant @"";
+                        return %10;
+                    };
+                return;
+            };
+            """)
+    String test2(Object o) {
+        if (o instanceof String s) {
+            return s;
+        } else {
+            return "";
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : PatternsTest, %1 : java.lang.Object)java.lang.String -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.String = constant @null;
+                %4 : Var<java.lang.String> = var %3 @"s";
+                java.if
+                    ()boolean -> {
+                        %5 : java.lang.Object = var.load %2;
+                        %6 : boolean = pattern.match %5
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                %7 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                yield %7;
+                            }
+                            ^match(%8 : java.lang.String)void -> {
+                                var.store %4 %8;
+                                yield;
+                            };
+                        %9 : boolean = not %6;
+                        yield %9;
+                    }
+                    ^then()void -> {
+                        %10 : java.lang.String = constant @"";
+                        return %10;
+                    }
+                    ^else()void -> {
+                        yield;
+                    };
+                %11 : java.lang.String = var.load %4;
+                return %11;
+            };
+            """)
+    String test3(Object o) {
+        if (!(o instanceof String s)) {
+            return "";
+        }
+        return s;
+    }
+
+    interface Point {
+    }
+
+    record ConcretePoint(int x, int y) implements Point {
+    }
+
+    enum Color {RED, GREEN, BLUE}
+
+    record ColoredPoint(ConcretePoint p, Color c) implements Point {
+    }
+
+    record Rectangle(Point upperLeft, Point lowerRight) {
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : PatternsTest, %1 : PatternsTest$Rectangle)void -> {
+                %2 : Var<PatternsTest$Rectangle> = var %1 @"r";
+                %3 : PatternsTest$ConcretePoint = constant @null;
+                %4 : Var<PatternsTest$ConcretePoint> = var %3 @"p";
+                %5 : PatternsTest$Color = constant @null;
+                %6 : Var<PatternsTest$Color> = var %5 @"c";
+                %7 : PatternsTest$ColoredPoint = constant @null;
+                %8 : Var<PatternsTest$ColoredPoint> = var %7 @"lr";
+                java.if
+                    ()boolean -> {
+                        %9 : PatternsTest$Rectangle = var.load %2;
+                        %10 : boolean = pattern.match %9
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Record<PatternsTest$Rectangle> -> {
+                                %11 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<PatternsTest$ConcretePoint> = pattern.binding @"p";
+                                %12 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<PatternsTest$Color> = pattern.binding @"c";
+                                %13 : java.lang.reflect.code.ExtendedOp$Pattern$Record<PatternsTest$ColoredPoint> = pattern.record %11 %12 @"(PatternsTest$ConcretePoint p, PatternsTest$Color c)PatternsTest$ColoredPoint";
+                                %14 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<PatternsTest$ColoredPoint> = pattern.binding @"lr";
+                                %15 : java.lang.reflect.code.ExtendedOp$Pattern$Record<PatternsTest$Rectangle> = pattern.record %13 %14 @"(PatternsTest$Point upperLeft, PatternsTest$Point lowerRight)PatternsTest$Rectangle";
+                                yield %15;
+                            }
+                            ^match(%16 : PatternsTest$ConcretePoint, %17 : PatternsTest$Color, %18 : PatternsTest$ColoredPoint)void -> {
+                                var.store %4 %16;
+                                var.store %6 %17;
+                                var.store %8 %18;
+                                yield;
+                            };
+                        yield %10;
+                    }
+                    ^then()void -> {
+                        %19 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %20 : PatternsTest$ConcretePoint = var.load %4;
+                        invoke %19 %20 @"java.io.PrintStream::println(java.lang.Object)void";
+                        %21 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %22 : PatternsTest$Color = var.load %6;
+                        invoke %21 %22 @"java.io.PrintStream::println(java.lang.Object)void";
+                        %23 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %24 : PatternsTest$ColoredPoint = var.load %8;
+                        invoke %23 %24 @"java.io.PrintStream::println(java.lang.Object)void";
+                        yield;
+                    }
+                    ^else()void -> {
+                        %25 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %26 : java.lang.String = constant @"NO MATCH";
+                        invoke %25 %26 @"java.io.PrintStream::println(java.lang.String)void";
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test4(Rectangle r) {
+        if (r instanceof Rectangle(
+                ColoredPoint(ConcretePoint p, Color c),
+                ColoredPoint lr)){
+            System.out.println(p);
+            System.out.println(c);
+            System.out.println(lr);
+        }
+        else {
+            System.out.println("NO MATCH");
+        }
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : PatternsTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.String = constant @null;
+                %4 : Var<java.lang.String> = var %3 @"s";
+                java.while
+                    ^cond()boolean -> {
+                        %5 : java.lang.Object = var.load %2;
+                        %6 : boolean = pattern.match %5
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                %7 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                yield %7;
+                            }
+                            ^match(%8 : java.lang.String)void -> {
+                                var.store %4 %8;
+                                yield;
+                            };
+                        yield %6;
+                    }
+                    ^body()void -> {
+                        %9 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %10 : java.lang.String = var.load %4;
+                        invoke %9 %10 @"java.io.PrintStream::println(java.lang.String)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test5(Object o) {
+        while (o instanceof String s) {
+            System.out.println(s);
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : PatternsTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.String = constant @null;
+                %4 : Var<java.lang.String> = var %3 @"s";
+                java.do.while
+                    ^body()void -> {
+                        java.continue;
+                    }
+                    ^cond()boolean -> {
+                        %5 : java.lang.Object = var.load %2;
+                        %6 : boolean = pattern.match %5
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                %7 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                yield %7;
+                            }
+                            ^match(%8 : java.lang.String)void -> {
+                                var.store %4 %8;
+                                yield;
+                            };
+                        %9 : boolean = not %6;
+                        yield %9;
+                    };
+                %10 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                %11 : java.lang.String = var.load %4;
+                invoke %10 %11 @"java.io.PrintStream::println(java.lang.String)void";
+                return;
+            };
+            """)
+    void test6(Object o) {
+        do {
+        } while (!(o instanceof String s));
+        System.out.println(s);
+    }
+
+
+    @CodeReflection
+    @IR("""
+            func @"test7" (%0 : PatternsTest, %1 : java.lang.Object)void -> {
+                %2 : Var<java.lang.Object> = var %1 @"o";
+                %3 : java.lang.Number = constant @null;
+                %4 : Var<java.lang.Number> = var %3 @"n";
+                java.for
+                    ^init()Var<int> -> {
+                        %5 : int = constant @"0";
+                        %6 : Var<int> = var %5 @"i";
+                        yield %6;
+                    }
+                    ^cond(%7 : Var<int>)boolean -> {
+                        %8 : boolean = java.cand
+                            ()boolean -> {
+                                %9 : int = var.load %7;
+                                %10 : int = constant @"10";
+                                %11 : boolean = lt %9 %10;
+                                yield %11;
+                            }
+                            ()boolean -> {
+                                %12 : java.lang.Object = var.load %2;
+                                %13 : boolean = pattern.match %12
+                                    ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.Number> -> {
+                                        %14 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.Number> = pattern.binding @"n";
+                                        yield %14;
+                                    }
+                                    ^match(%15 : java.lang.Number)void -> {
+                                        var.store %4 %15;
+                                        yield;
+                                    };
+                                yield %13;
+                            };
+                        yield %8;
+                    }
+                    ^update(%16 : Var<int>)void -> {
+                        %17 : int = var.load %16;
+                        %18 : java.lang.Number = var.load %4;
+                        %19 : int = invoke %18 @"java.lang.Number::intValue()int";
+                        %20 : int = add %17 %19;
+                        var.store %16 %20;
+                        yield;
+                    }
+                    ^body(%21 : Var<int>)void -> {
+                        %22 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %23 : java.lang.Number = var.load %4;
+                        invoke %22 %23 @"java.io.PrintStream::println(java.lang.Object)void";
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test7(Object o) {
+        for (int i = 0;
+             i < 10 && o instanceof Number n; i += n.intValue()) {
+            System.out.println(n);
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/PrimitiveCastTest.java b/test/langtools/tools/javac/reflect/PrimitiveCastTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/PrimitiveCastTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with primitive casts.
+ * @build PrimitiveCastTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester PrimitiveCastTest
+ */
+
+import java.lang.runtime.CodeReflection;
+
+public class PrimitiveCastTest {
+
+    @CodeReflection
+    @IR("""
+            func @"testFromDouble" (%0 : PrimitiveCastTest, %1 : double)void -> {
+                %2 : Var<double> = var %1 @"v";
+                %3 : double = var.load %2;
+                %4 : Var<double> = var %3 @"d";
+                %5 : double = var.load %2;
+                %6 : float = conv %5;
+                %7 : Var<float> = var %6 @"f";
+                %8 : double = var.load %2;
+                %9 : long = conv %8;
+                %10 : Var<long> = var %9 @"l";
+                %11 : double = var.load %2;
+                %12 : int = conv %11;
+                %13 : Var<int> = var %12 @"i";
+                %14 : double = var.load %2;
+                %15 : short = conv %14;
+                %16 : Var<short> = var %15 @"s";
+                %17 : double = var.load %2;
+                %18 : char = conv %17;
+                %19 : Var<char> = var %18 @"c";
+                %20 : double = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromDouble(double v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromFloat" (%0 : PrimitiveCastTest, %1 : float)void -> {
+                %2 : Var<float> = var %1 @"v";
+                %3 : float = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : float = var.load %2;
+                %7 : Var<float> = var %6 @"f";
+                %8 : float = var.load %2;
+                %9 : long = conv %8;
+                %10 : Var<long> = var %9 @"l";
+                %11 : float = var.load %2;
+                %12 : int = conv %11;
+                %13 : Var<int> = var %12 @"i";
+                %14 : float = var.load %2;
+                %15 : short = conv %14;
+                %16 : Var<short> = var %15 @"s";
+                %17 : float = var.load %2;
+                %18 : char = conv %17;
+                %19 : Var<char> = var %18 @"c";
+                %20 : float = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromFloat(float v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromLong" (%0 : PrimitiveCastTest, %1 : long)void -> {
+                %2 : Var<long> = var %1 @"v";
+                %3 : long = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : long = var.load %2;
+                %7 : float = conv %6;
+                %8 : Var<float> = var %7 @"f";
+                %9 : long = var.load %2;
+                %10 : Var<long> = var %9 @"l";
+                %11 : long = var.load %2;
+                %12 : int = conv %11;
+                %13 : Var<int> = var %12 @"i";
+                %14 : long = var.load %2;
+                %15 : short = conv %14;
+                %16 : Var<short> = var %15 @"s";
+                %17 : long = var.load %2;
+                %18 : char = conv %17;
+                %19 : Var<char> = var %18 @"c";
+                %20 : long = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromLong(long v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromInt" (%0 : PrimitiveCastTest, %1 : int)void -> {
+                %2 : Var<int> = var %1 @"v";
+                %3 : int = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : int = var.load %2;
+                %7 : float = conv %6;
+                %8 : Var<float> = var %7 @"f";
+                %9 : int = var.load %2;
+                %10 : long = conv %9;
+                %11 : Var<long> = var %10 @"l";
+                %12 : int = var.load %2;
+                %13 : Var<int> = var %12 @"i";
+                %14 : int = var.load %2;
+                %15 : short = conv %14;
+                %16 : Var<short> = var %15 @"s";
+                %17 : int = var.load %2;
+                %18 : char = conv %17;
+                %19 : Var<char> = var %18 @"c";
+                %20 : int = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromInt(int v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromShort" (%0 : PrimitiveCastTest, %1 : short)void -> {
+                %2 : Var<short> = var %1 @"v";
+                %3 : short = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : short = var.load %2;
+                %7 : float = conv %6;
+                %8 : Var<float> = var %7 @"f";
+                %9 : short = var.load %2;
+                %10 : long = conv %9;
+                %11 : Var<long> = var %10 @"l";
+                %12 : short = var.load %2;
+                %13 : int = conv %12;
+                %14 : Var<int> = var %13 @"i";
+                %15 : short = var.load %2;
+                %16 : Var<short> = var %15 @"s";
+                %17 : short = var.load %2;
+                %18 : char = conv %17;
+                %19 : Var<char> = var %18 @"c";
+                %20 : short = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromShort(short v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromChar" (%0 : PrimitiveCastTest, %1 : char)void -> {
+                %2 : Var<char> = var %1 @"v";
+                %3 : char = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : char = var.load %2;
+                %7 : float = conv %6;
+                %8 : Var<float> = var %7 @"f";
+                %9 : char = var.load %2;
+                %10 : long = conv %9;
+                %11 : Var<long> = var %10 @"l";
+                %12 : char = var.load %2;
+                %13 : int = conv %12;
+                %14 : Var<int> = var %13 @"i";
+                %15 : char = var.load %2;
+                %16 : short = conv %15;
+                %17 : Var<short> = var %16 @"s";
+                %18 : char = var.load %2;
+                %19 : Var<char> = var %18 @"c";
+                %20 : char = var.load %2;
+                %21 : byte = conv %20;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromChar(char v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"testFromByte" (%0 : PrimitiveCastTest, %1 : byte)void -> {
+                %2 : Var<byte> = var %1 @"v";
+                %3 : byte = var.load %2;
+                %4 : double = conv %3;
+                %5 : Var<double> = var %4 @"d";
+                %6 : byte = var.load %2;
+                %7 : float = conv %6;
+                %8 : Var<float> = var %7 @"f";
+                %9 : byte = var.load %2;
+                %10 : long = conv %9;
+                %11 : Var<long> = var %10 @"l";
+                %12 : byte = var.load %2;
+                %13 : int = conv %12;
+                %14 : Var<int> = var %13 @"i";
+                %15 : byte = var.load %2;
+                %16 : short = conv %15;
+                %17 : Var<short> = var %16 @"s";
+                %18 : byte = var.load %2;
+                %19 : char = conv %18;
+                %20 : Var<char> = var %19 @"c";
+                %21 : byte = var.load %2;
+                %22 : Var<byte> = var %21 @"b";
+                return;
+            };
+            """)
+    @SuppressWarnings("cast")
+    void testFromByte(byte v) {
+        double d = (double) v;
+        float f = (float) v;
+        long l = (long) v;
+        int i = (int) v;
+        short s = (short) v;
+        char c = (char) v;
+        byte b = (byte) v;
+    }
+}
diff a/test/langtools/tools/javac/reflect/QuotableIntersectionTest.java b/test/langtools/tools/javac/reflect/QuotableIntersectionTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/QuotableIntersectionTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with quotable lambdas.
+ * @build QuotableIntersectionTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester QuotableIntersectionTest
+ */
+
+import java.lang.reflect.code.Quotable;
+import java.lang.runtime.CodeReflection;
+import java.util.function.IntBinaryOperator;
+import java.util.function.IntFunction;
+import java.util.function.IntSupplier;
+import java.util.function.IntUnaryOperator;
+
+public class QuotableIntersectionTest {
+    @IR("""
+            func @"f" ()void -> {
+                  %0 : java.lang.reflect.code.Quotable = lambda ()void -> {
+                      return;
+                  };
+                  return;
+            };
+            """)
+    static final Quotable QUOTED_NO_PARAM_VOID = (Quotable & Runnable) () -> {
+    };
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : java.lang.reflect.code.Quotable = lambda ()int -> {
+                    %2 : int = constant @"1";
+                    return %2;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_NO_PARAM_CONST = (Quotable & IntSupplier) () -> 1;
+
+    @IR("""
+            func @"f" ()void -> {
+                  %0 : java.lang.reflect.code.Quotable = lambda (%1 : int)int -> {
+                      %2 : Var<int> = var %1 @"x";
+                      %3 : int = var.load %2;
+                      return %3;
+                  };
+                  return;
+            };
+            """)
+    static final Quotable QUOTED_ID = (Quotable & IntUnaryOperator) x -> x;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : java.lang.reflect.code.Quotable = lambda (%1 : int, %2 : int)int -> {
+                    %3 : Var<int> = var %1 @"x";
+                    %4 : Var<int> = var %2 @"y";
+                    %5 : int = var.load %3;
+                    %6 : int = var.load %4;
+                    %7 : int = add %5 %6;
+                    return %7;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_PLUS = (Quotable & IntBinaryOperator) (x, y) -> x + y;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : java.lang.reflect.code.Quotable = lambda ()void -> {
+                    %1 : java.lang.AssertionError = new @"func<java.lang.AssertionError>";
+                    throw %1;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_THROW_NO_PARAM = (Quotable & Runnable) () -> {
+        throw new AssertionError();
+    };
+
+    @IR("""
+            func @"f" (%1 : Var<int>)void -> {
+                %2 : java.lang.reflect.code.Quotable = lambda (%4 : int)int -> {
+                    %5 : Var<int> = var %4 @"y";
+                    %6 : int = var.load %1;
+                    %7 : int = var.load %5;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_CAPTURE_PARAM = new Object() {
+        Quotable captureContext(int x) {
+            return (Quotable & IntUnaryOperator) y -> x + y;
+        }
+    }.captureContext(42);
+
+    static class Context {
+        int x, y;
+
+        Quotable capture() {
+            return (Quotable & IntUnaryOperator) z -> x + y + z;
+        }
+    }
+
+    @IR("""
+            func @"f" (%0 : QuotableIntersectionTest$Context)void -> {
+                %1 : java.lang.reflect.code.Quotable = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"z";
+                    %5 : int = field.load %0 @"QuotableIntersectionTest$Context::x()int";
+                    %6 : int = field.load %0 @"QuotableIntersectionTest$Context::y()int";
+                    %7 : int = add %5 %6;
+                    %8 : int = var.load %4;
+                    %9 : int = add %7 %8;
+                    return %9;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_CAPTURE_FIELD = new Context().capture();
+
+    @CodeReflection
+    @IR("""
+            func @"captureParam" (%0 : int)void -> {
+                %1 : Var<int> = var %0 @"x";
+                %2 : java.util.function.IntUnaryOperator = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"y";
+                    %5 : int = var.load %1;
+                    %6 : int = var.load %4;
+                    %7 : int = add %5 %6;
+                    return %7;
+                };
+                %8 : Var<java.util.function.IntUnaryOperator> = var %2 @"op";
+                return;
+            };
+            """)
+    static void captureParam(int x) {
+        IntUnaryOperator op = (IntUnaryOperator & Quotable) y -> x + y;
+    }
+
+    int x, y;
+
+    @CodeReflection
+    @IR("""
+            func @"captureField" (%0 : QuotableIntersectionTest)void -> {
+                %1 : java.util.function.IntUnaryOperator = lambda (%2 : int)int -> {
+                    %3 : Var<int> = var %2 @"z";
+                    %4 : int = field.load %0 @"QuotableIntersectionTest::x()int";
+                    %5 : int = field.load %0 @"QuotableIntersectionTest::y()int";
+                    %6 : int = add %4 %5;
+                    %7 : int = var.load %3;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                %9 : Var<java.util.function.IntUnaryOperator> = var %1 @"op";
+                return;
+            };
+            """)
+    void captureField() {
+        IntUnaryOperator op = (IntUnaryOperator & Quotable) z -> x + y + z;
+    }
+
+    static void m() {
+    }
+
+    @IR("""
+            func @"f" ()void -> {
+                  %0 : java.lang.reflect.code.Quotable = lambda ()void -> {
+                      invoke @"QuotableIntersectionTest::m()void";
+                      return;
+                  };
+                  return;
+            };
+            """)
+    static final Quotable QUOTED_NO_PARAM_VOID_REF = (Quotable & Runnable) QuotableIntersectionTest::m;
+
+    static int g(int i) {
+        return i;
+    }
+
+    @IR("""
+            func @"f" ()void -> {
+                  %0 : java.lang.reflect.code.Quotable = lambda (%1 : int)int -> {
+                      %2 : Var<int> = var %1 @"x$0";
+                      %3 : int = var.load %2;
+                      %4 : int = invoke %3 @"QuotableIntersectionTest::g(int)int";
+                      return %4;
+                  };
+                  return;
+            };
+            """)
+    static final Quotable QUOTED_INT_PARAM_INT_RET_REF = (Quotable & IntUnaryOperator) QuotableIntersectionTest::g;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : java.lang.reflect.code.Quotable = lambda (%1 : int)int[] -> {
+                    %2 : Var<int> = var %1 @"x$0";
+                    %3 : int = var.load %2;
+                    %4 : int[] = new %3 @"func<int[], int>";
+                    return %4;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_INT_PARAM_ARR_RET_REF = (Quotable & IntFunction<int[]>) int[]::new;
+
+    static class ContextRef {
+        int g(int i) {
+            return i;
+        }
+
+        Quotable capture() {
+            return (Quotable & IntUnaryOperator) this::g;
+        }
+    }
+
+    @IR("""
+            func @"f" (%0 : QuotableIntersectionTest$ContextRef)void -> {
+                %1 : java.lang.reflect.code.Quotable = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"x$0";
+                    %5 : int = var.load %4;
+                    %6 : int = invoke %0 %5 @"QuotableIntersectionTest$ContextRef::g(int)int";
+                    return %6;
+                };
+                return;
+            };
+            """)
+    static final Quotable QUOTED_CAPTURE_THIS_REF = new ContextRef().capture();
+}
diff a/test/langtools/tools/javac/reflect/QuotableSubtypeTest.java b/test/langtools/tools/javac/reflect/QuotableSubtypeTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/QuotableSubtypeTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with quotable lambdas.
+ * @build QuotableSubtypeTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester QuotableSubtypeTest
+ */
+
+import java.lang.reflect.code.Quotable;
+import java.lang.runtime.CodeReflection;
+import java.util.function.IntBinaryOperator;
+import java.util.function.IntFunction;
+import java.util.function.IntSupplier;
+import java.util.function.IntUnaryOperator;
+
+public class QuotableSubtypeTest {
+
+    interface QuotableRunnable extends Runnable, Quotable { }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableRunnable = lambda ()void -> {
+                    return;
+                };
+                return;
+            };
+            """)
+    static final QuotableRunnable QUOTED_NO_PARAM_VOID = () -> { };
+
+    interface QuotableIntSupplier extends IntSupplier, Quotable { }
+
+    @IR("""
+           func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableIntSupplier = lambda ()int -> {
+                    %2 : int = constant @"1";
+                    return %2;
+                };
+                return;
+           };
+            """)
+    static final QuotableIntSupplier QUOTED_NO_PARAM_CONST = () -> 1;
+
+    interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable { }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%2 : int)int -> {
+                    %3 : Var<int> = var %2 @"x";
+                    %4 : int = var.load %3;
+                    return %4;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntUnaryOperator QUOTED_ID = x -> x;
+
+    interface QuotableIntBinaryOperator extends IntBinaryOperator, Quotable { }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableIntBinaryOperator = lambda (%2 : int, %3 : int)int -> {
+                    %4 : Var<int> = var %2 @"x";
+                    %5 : Var<int> = var %3 @"y";
+                    %6 : int = var.load %4;
+                    %7 : int = var.load %5;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntBinaryOperator QUOTED_PLUS = (x, y) -> x + y;
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableRunnable = lambda ()void -> {
+                    %2 : java.lang.AssertionError = new @"func<java.lang.AssertionError>";
+                    throw %2;
+                };
+                return;
+            };
+            """)
+    static final QuotableRunnable QUOTED_THROW_NO_PARAM = () -> { throw new AssertionError(); };
+
+    @IR("""
+            func @"f" (%1 : Var<int>)void -> {
+                %0 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%4 : int)int -> {
+                    %5 : Var<int> = var %4 @"y";
+                    %6 : int = var.load %1;
+                    %7 : int = var.load %5;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntUnaryOperator QUOTED_CAPTURE_PARAM = new Object() {
+        QuotableIntUnaryOperator captureContext(int x) {
+            return y -> x + y;
+        }
+    }.captureContext(42);
+
+    static class Context {
+        int x, y;
+
+        QuotableIntUnaryOperator capture() {
+            return z -> x + y + z;
+        }
+    }
+
+    @IR("""
+            func @"f" (%0 : QuotableSubtypeTest$Context)void -> {
+                %1 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"z";
+                    %5 : int = field.load %0 @"QuotableSubtypeTest$Context::x()int";
+                    %6 : int = field.load %0 @"QuotableSubtypeTest$Context::y()int";
+                    %7 : int = add %5 %6;
+                    %8 : int = var.load %4;
+                    %9 : int = add %7 %8;
+                    return %9;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntUnaryOperator QUOTED_CAPTURE_FIELD = new Context().capture();
+
+    @CodeReflection
+    @IR("""
+            func @"captureParam" (%0 : int)void -> {
+                %1 : Var<int> = var %0 @"x";
+                %2 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"y";
+                    %5 : int = var.load %1;
+                    %6 : int = var.load %4;
+                    %7 : int = add %5 %6;
+                    return %7;
+                };
+                %8 : Var<QuotableSubtypeTest$QuotableIntUnaryOperator> = var %2 @"op";
+                return;
+            };
+            """)
+    static void captureParam(int x) {
+        QuotableIntUnaryOperator op = y -> x + y;
+    }
+
+    int x, y;
+
+    @CodeReflection
+    @IR("""
+            func @"captureField" (%0 : QuotableSubtypeTest)void -> {
+                %1 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%2 : int)int -> {
+                    %3 : Var<int> = var %2 @"z";
+                    %4 : int = field.load %0 @"QuotableSubtypeTest::x()int";
+                    %5 : int = field.load %0 @"QuotableSubtypeTest::y()int";
+                    %6 : int = add %4 %5;
+                    %7 : int = var.load %3;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                %9 : Var<QuotableSubtypeTest$QuotableIntUnaryOperator> = var %1 @"op";
+                return;
+            };
+            """)
+    void captureField() {
+        QuotableIntUnaryOperator op = z -> x + y + z;
+    }
+
+    static void m() { }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableRunnable = lambda ()void -> {
+                    invoke @"QuotableSubtypeTest::m()void";
+                    return;
+                };
+                return;
+            };
+            """)
+    static final QuotableRunnable QUOTED_NO_PARAM_VOID_REF = QuotableSubtypeTest::m;
+
+    static int g(int i) { return i; }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%2 : int)int -> {
+                    %3 : Var<int> = var %2 @"x$0";
+                    %4 : int = var.load %3;
+                    %5 : int = invoke %4 @"QuotableSubtypeTest::g(int)int";
+                    return %5;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntUnaryOperator QUOTED_INT_PARAM_INT_RET_REF = QuotableSubtypeTest::g;
+
+    interface QuotableIntFunction<A> extends Quotable, IntFunction<A> { }
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : QuotableSubtypeTest$QuotableIntFunction<int[]> = lambda (%2 : int)int[] -> {
+                    %3 : Var<int> = var %2 @"x$0";
+                    %4 : int = var.load %3;
+                    %5 : int[] = new %4 @"func<int[], int>";
+                    return %5;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntFunction<int[]> QUOTED_INT_PARAM_ARR_RET_REF = int[]::new;
+
+    static class ContextRef {
+        int g(int i) { return i; }
+
+        QuotableIntUnaryOperator capture() {
+            return this::g;
+        }
+    }
+
+    @IR("""
+            func @"f" (%0 : QuotableSubtypeTest$ContextRef)void -> {
+                %1 : QuotableSubtypeTest$QuotableIntUnaryOperator = lambda (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"x$0";
+                    %5 : int = var.load %4;
+                    %6 : int = invoke %0 %5 @"QuotableSubtypeTest$ContextRef::g(int)int";
+                    return %6;
+                };
+                return;
+            };
+            """)
+    static final QuotableIntUnaryOperator QUOTED_CAPTURE_THIS_REF = new ContextRef().capture();
+}
diff a/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java
@@ -0,0 +1,34 @@
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.code.Quotable;
+import java.util.function.IntUnaryOperator;
+import java.util.stream.IntStream;
+
+/*
+ * @test
+ * @summary test that invoking Quotable#quoted returns the same instance
+ * @run testng QuotedSameInstanceTest
+ */
+
+public class QuotedSameInstanceTest {
+
+    private static final Quotable q1 = (Quotable & Runnable) () -> {
+    };
+
+    @Test
+    void testWithOneThread() {
+        Assert.assertSame(q1.quoted(), q1.quoted());
+    }
+
+    interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable { }
+    private static final QuotableIntUnaryOperator q2 = x -> x;
+
+    @Test
+    void testWithMultiThreads() {
+        Object[] quotedObjects = IntStream.range(0, 1024).parallel().mapToObj(__ -> q2.quoted()).toArray();
+        for (int i = 1; i < quotedObjects.length; i++) {
+            Assert.assertSame(quotedObjects[i], quotedObjects[i - 1]);
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/QuotedTest.java b/test/langtools/tools/javac/reflect/QuotedTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/QuotedTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with quoted lambdas.
+ * @build QuotedTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester QuotedTest
+ */
+
+import java.lang.reflect.code.Quoted;
+import java.lang.runtime.CodeReflection;
+
+public class QuotedTest {
+    @IR("""
+            func @"f" ()void -> {
+                 %0 : func<void> = closure ()void -> {
+                    return;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_NO_PARAM_VOID = () -> {
+    };
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : func<int> = closure ()int -> {
+                    %2 : int = constant @"1";
+                    return %2;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_NO_PARAM_CONST = () -> 1;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : func<int, int> = closure (%2 : int)int -> {
+                    %3 : Var<int> = var %2 @"x";
+                    %4 : int = var.load %3;
+                    return %4;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_ID = (int x) -> x;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : func<int, int, int> = closure (%2 : int, %3 : int)int -> {
+                    %4 : Var<int> = var %2 @"x";
+                    %5 : Var<int> = var %3 @"y";
+                    %6 : int = var.load %4;
+                    %7 : int = var.load %5;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_PLUS = (int x, int y) -> x + y;
+
+    @IR("""
+            func @"f" ()void -> {
+                %0 : func<java.lang.Object> = closure ()java.lang.Object -> {
+                    %2 : java.lang.AssertionError = new @"func<java.lang.AssertionError>";
+                    throw %2;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_THROW_NO_PARAM = () -> {
+        throw new AssertionError();
+    };
+
+    // can we write out the root op then extract the closure ?
+
+    @IR("""
+            func @"f" (%1: Var<int>)void -> {
+                %0 : func<int, int> = closure (%4 : int)int -> {
+                    %5 : Var<int> = var %4 @"y";
+                    %6 : int = var.load %1;
+                    %7 : int = var.load %5;
+                    %8 : int = add %6 %7;
+                    return %8;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_CAPTURE_PARAM = new Object() {
+        Quoted captureContext(int x) {
+            return (int y) -> x + y;
+        }
+    }.captureContext(42);
+
+    static class Context {
+        int x, y;
+
+        Quoted capture() {
+            return (int z) -> x + y + z;
+        }
+    }
+
+    @IR("""
+            func @"f" (%0 : QuotedTest$Context)void -> {
+                %1 : func<int, int> = closure (%3 : int)int -> {
+                    %4 : Var<int> = var %3 @"z";
+                    %5 : int = field.load %0 @"QuotedTest$Context::x()int";
+                    %6 : int = field.load %0 @"QuotedTest$Context::y()int";
+                    %7 : int = add %5 %6;
+                    %8 : int = var.load %4;
+                    %9 : int = add %7 %8;
+                    return %9;
+                };
+                return;
+            };
+            """)
+    static final Quoted QUOTED_CAPTURE_FIELD = new Context().capture();
+
+    @CodeReflection
+    @IR("""
+            func @"captureParam" (%0 : int)void -> {
+                %1 : Var<int> = var %0 @"x";
+                %2 : java.lang.reflect.code.Quoted = quoted ()void -> {
+                    %3 : func<int, int> = closure (%4 : int)int -> {
+                        %5 : Var<int> = var %4 @"y";
+                        %6 : int = var.load %1;
+                        %7 : int = var.load %5;
+                        %8 : int = add %6 %7;
+                        return %8;
+                    };
+                    yield %3;
+                };
+                %9 : Var<java.lang.reflect.code.Quoted> = var %2 @"op";
+                return;
+            };
+            """)
+    static void captureParam(int x) {
+        Quoted op = (int y) -> x + y;
+    }
+
+    int x, y;
+
+    @CodeReflection
+    @IR("""
+            func @"captureField" (%0 : QuotedTest)void -> {
+                %1 : java.lang.reflect.code.Quoted = quoted ()void -> {
+                    %2 : func<int, int> = closure (%3 : int)int -> {
+                        %4 : Var<int> = var %3 @"z";
+                        %5 : int = field.load %0 @"QuotedTest::x()int";
+                        %6 : int = field.load %0 @"QuotedTest::y()int";
+                        %7 : int = add %5 %6;
+                        %8 : int = var.load %4;
+                        %9 : int = add %7 %8;
+                        return %9;
+                    };
+                    yield %2;
+                };
+                %10 : Var<java.lang.reflect.code.Quoted> = var %1 @"op";
+                return;
+            };
+            """)
+    void captureField() {
+        Quoted op = (int z) -> x + y + z;
+    }
+}
diff a/test/langtools/tools/javac/reflect/SwitchExpressionTest.java b/test/langtools/tools/javac/reflect/SwitchExpressionTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/SwitchExpressionTest.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with switch expressions.
+ * @enablePreview
+ * @build SwitchExpressionTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester SwitchExpressionTest
+ */
+
+public class SwitchExpressionTest {
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelRule" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : java.lang.String = constant @"FOO";
+                        %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %6;
+                    }
+                    ()java.lang.Object -> {
+                        %7 : java.lang.String = constant @"FOO";
+                        yield %7;
+                    }
+                    ^constantCaseLabel(%8 : java.lang.String)boolean -> {
+                        %9 : java.lang.String = constant @"BAR";
+                        %10 : boolean = invoke %8 %9 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %10;
+                    }
+                    ()java.lang.Object -> {
+                        %11 : java.lang.String = constant @"FOO";
+                        yield %11;
+                    }
+                    ^constantCaseLabel(%12 : java.lang.String)boolean -> {
+                        %13 : java.lang.String = constant @"BAZ";
+                        %14 : boolean = invoke %12 %13 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %14;
+                    }
+                    ()java.lang.Object -> {
+                        %15 : java.lang.String = constant @"FOO";
+                        yield %15;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %16 : java.lang.String = constant @"";
+                        yield %16;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelRule(String r) {
+        return switch (r) {
+            case "FOO" -> "FOO";
+            case "BAR" -> "FOO";
+            case "BAZ" -> "FOO";
+            default -> "";
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelsRule" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : boolean = java.cor
+                            ()boolean -> {
+                                %6 : java.lang.String = constant @"FOO";
+                                %7 : boolean = invoke %4 %6 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %7;
+                            }
+                            ()boolean -> {
+                                %8 : java.lang.String = constant @"BAR";
+                                %9 : boolean = invoke %4 %8 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %9;
+                            }
+                            ()boolean -> {
+                                %10 : java.lang.String = constant @"BAZ";
+                                %11 : boolean = invoke %4 %10 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %11;
+                            };
+                        yield %5;
+                    }
+                    ()java.lang.Object -> {
+                        %12 : java.lang.String = constant @"FOO";
+                        yield %12;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %13 : java.lang.String = constant @"";
+                        java.yield %13;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelsRule(String r) {
+        return switch (r) {
+            case "FOO", "BAR", "BAZ" -> "FOO";
+            default -> {
+                yield "";
+            }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelStatement" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : java.lang.String = constant @"FOO";
+                        %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %6;
+                    }
+                    ()java.lang.Object -> {
+                        %7 : java.lang.String = constant @"FOO";
+                        java.yield %7;
+                    }
+                    ^constantCaseLabel(%8 : java.lang.String)boolean -> {
+                        %9 : java.lang.String = constant @"BAR";
+                        %10 : boolean = invoke %8 %9 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %10;
+                    }
+                    ()java.lang.Object -> {
+                        %11 : java.lang.String = constant @"FOO";
+                        java.yield %11;
+                    }
+                    ^constantCaseLabel(%12 : java.lang.String)boolean -> {
+                        %13 : java.lang.String = constant @"BAZ";
+                        %14 : boolean = invoke %12 %13 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %14;
+                    }
+                    ()java.lang.Object -> {
+                        %15 : java.lang.String = constant @"FOO";
+                        java.yield %15;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %16 : java.lang.String = constant @"";
+                        java.yield %16;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelStatement(String r) {
+        return switch (r) {
+            case "FOO" : yield "FOO";
+            case "BAR" : yield "FOO";
+            case "BAZ" : yield "FOO";
+            default : yield "";
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelsStatement" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : boolean = java.cor
+                            ()boolean -> {
+                                %6 : java.lang.String = constant @"FOO";
+                                %7 : boolean = invoke %4 %6 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %7;
+                            }
+                            ()boolean -> {
+                                %8 : java.lang.String = constant @"BAR";
+                                %9 : boolean = invoke %4 %8 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %9;
+                            }
+                            ()boolean -> {
+                                %10 : java.lang.String = constant @"BAZ";
+                                %11 : boolean = invoke %4 %10 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                                yield %11;
+                            };
+                        yield %5;
+                    }
+                    ()java.lang.Object -> {
+                        %12 : java.lang.String = constant @"FOO";
+                        java.yield %12;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        java.block ()void -> {
+                            %13 : java.lang.String = constant @"";
+                            java.yield %13;
+                        };
+                        unreachable;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelsStatement(String r) {
+        return switch (r) {
+            case "FOO", "BAR", "BAZ" : yield "FOO";
+            default : { yield ""; }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelStatements" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : java.lang.String = constant @"FOO";
+                        %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %6;
+                    }
+                    ()java.lang.Object -> {
+                        java.block ()void -> {
+                            %7 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                            %8 : java.lang.String = constant @"FOO";
+                            invoke %7 %8 @"java.io.PrintStream::println(java.lang.String)void";
+                            yield;
+                        };
+                        java.block ()void -> {
+                            %9 : java.lang.String = constant @"FOO";
+                            java.yield %9;
+                        };
+                        unreachable;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %10 : java.lang.String = constant @"";
+                        java.yield %10;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelStatements(String r) {
+        return switch (r) {
+            case "FOO" : {
+                System.out.println("FOO");
+            }
+            {
+                yield "FOO";
+            }
+            default : yield "";
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"constantCaseLabelFallthrough" (%0 : java.lang.String)java.lang.Object -> {
+                %1 : Var<java.lang.String> = var %0 @"r";
+                %2 : java.lang.String = var.load %1;
+                %3 : java.lang.Object = java.switch.expression %2
+                    ^constantCaseLabel(%4 : java.lang.String)boolean -> {
+                        %5 : java.lang.String = constant @"FOO";
+                        %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
+                        yield %6;
+                    }
+                    ()java.lang.Object -> {
+                        java.block ()void -> {
+                            %7 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                            %8 : java.lang.String = constant @"FOO";
+                            invoke %7 %8 @"java.io.PrintStream::println(java.lang.String)void";
+                            yield;
+                        };
+                        java.switch.fallthrough;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %9 : java.lang.String = constant @"";
+                        java.yield %9;
+                    };
+                return %3;
+            };
+            """)
+    public static Object constantCaseLabelFallthrough(String r) {
+        return switch (r) {
+            case "FOO" : {
+                System.out.println("FOO");
+            }
+            default : yield "";
+        };
+    }
+
+    record A(Number n) {
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"patternCaseLabel" (%0 : java.lang.Object)java.lang.Object -> {
+                %1 : Var<java.lang.Object> = var %0 @"r";
+                %2 : java.lang.Object = var.load %1;
+                %3 : java.lang.Number = constant @null;
+                %4 : Var<java.lang.Number> = var %3 @"n";
+                %5 : java.lang.String = constant @null;
+                %6 : Var<java.lang.String> = var %5 @"s";
+                %7 : java.lang.Object = java.switch.expression %2
+                    ^patternCaseLabel(%8 : java.lang.Object)boolean -> {
+                        %9 : boolean = pattern.match %8
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Record<SwitchExpressionTest$A> -> {
+                                %10 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.Number> = pattern.binding @"n";
+                                %11 : java.lang.reflect.code.ExtendedOp$Pattern$Record<SwitchExpressionTest$A> = pattern.record %10 @"(java.lang.Number n)SwitchExpressionTest$A";
+                                yield %11;
+                            }
+                            ^match(%12 : java.lang.Number)void -> {
+                                var.store %4 %12;
+                                yield;
+                            };
+                        yield %9;
+                    }
+                    ()java.lang.Object -> {
+                        %13 : java.lang.Number = var.load %4;
+                        java.yield %13;
+                    }
+                    ^patternCaseLabel(%14 : java.lang.Object)boolean -> {
+                        %15 : boolean = pattern.match %14
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                %16 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                yield %16;
+                            }
+                            ^match(%17 : java.lang.String)void -> {
+                                var.store %6 %17;
+                                yield;
+                            };
+                        yield %15;
+                    }
+                    ()java.lang.Object -> {
+                        %18 : java.lang.String = var.load %6;
+                        java.yield %18;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %19 : java.lang.String = constant @"";
+                        java.yield %19;
+                    };
+                return %7;
+            };
+            """)
+    public static Object patternCaseLabel(Object r) {
+        return switch (r) {
+            case A(Number n) -> {
+                yield n;
+            }
+            case String s -> {
+                yield s;
+            }
+            default -> {
+                yield "";
+            }
+        };
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"patternCaseLabelGuard" (%0 : java.lang.Object)java.lang.Object -> {
+                %1 : Var<java.lang.Object> = var %0 @"r";
+                %2 : java.lang.Object = var.load %1;
+                %3 : java.lang.Number = constant @null;
+                %4 : Var<java.lang.Number> = var %3 @"n";
+                %5 : java.lang.String = constant @null;
+                %6 : Var<java.lang.String> = var %5 @"s";
+                %7 : java.lang.String = constant @null;
+                %8 : Var<java.lang.String> = var %7 @"s";
+                %9 : java.lang.Object = java.switch.expression %2
+                    ^patternCaseLabel(%10 : java.lang.Object)boolean -> {
+                        %11 : boolean = pattern.match %10
+                            ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Record<SwitchExpressionTest$A> -> {
+                                %12 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.Number> = pattern.binding @"n";
+                                %13 : java.lang.reflect.code.ExtendedOp$Pattern$Record<SwitchExpressionTest$A> = pattern.record %12 @"(java.lang.Number n)SwitchExpressionTest$A";
+                                yield %13;
+                            }
+                            ^match(%14 : java.lang.Number)void -> {
+                                var.store %4 %14;
+                                yield;
+                            };
+                        yield %11;
+                    }
+                    ()java.lang.Object -> {
+                        %15 : java.lang.Number = var.load %4;
+                        java.yield %15;
+                    }
+                    ^patternCaseLabel(%16 : java.lang.Object)boolean -> {
+                        %17 : boolean = java.cand
+                            ()boolean -> {
+                                %18 : boolean = pattern.match %16
+                                    ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                        %19 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                        yield %19;
+                                    }
+                                    ^match(%20 : java.lang.String)void -> {
+                                        var.store %6 %20;
+                                        yield;
+                                    };
+                                yield %18;
+                            }
+                            ()boolean -> {
+                                %21 : java.lang.String = var.load %6;
+                                %22 : int = invoke %21 @"java.lang.String::length()int";
+                                %23 : int = constant @"5";
+                                %24 : boolean = lt %22 %23;
+                                yield %24;
+                            };
+                        yield %17;
+                    }
+                    ()java.lang.Object -> {
+                        %25 : java.lang.String = var.load %6;
+                        java.yield %25;
+                    }
+                    ^patternCaseLabel(%26 : java.lang.Object)boolean -> {
+                        %27 : boolean = java.cand
+                            ()boolean -> {
+                                %28 : boolean = pattern.match %26
+                                    ^pattern()java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> -> {
+                                        %29 : java.lang.reflect.code.ExtendedOp$Pattern$Binding<java.lang.String> = pattern.binding @"s";
+                                        yield %29;
+                                    }
+                                    ^match(%30 : java.lang.String)void -> {
+                                        var.store %8 %30;
+                                        yield;
+                                    };
+                                yield %28;
+                            }
+                            ()boolean -> {
+                                %31 : java.lang.String = var.load %8;
+                                %32 : int = invoke %31 @"java.lang.String::length()int";
+                                %33 : int = constant @"10";
+                                %34 : boolean = lt %32 %33;
+                                yield %34;
+                            };
+                        yield %27;
+                    }
+                    ()java.lang.Object -> {
+                        %35 : java.lang.String = var.load %8;
+                        java.yield %35;
+                    }
+                    ^defaultCaseLabel()void -> {
+                        yield;
+                    }
+                    ()java.lang.Object -> {
+                        %36 : java.lang.String = constant @"";
+                        java.yield %36;
+                    };
+                return %9;
+            };
+            """)
+    public static Object patternCaseLabelGuard(Object r) {
+        return switch (r) {
+            case A(Number n) -> {
+                yield n;
+            }
+            case String s when s.length() < 5 -> {
+                yield s;
+            }
+            case String s when s.length() < 10 -> {
+                yield s;
+            }
+            default -> {
+                yield "";
+            }
+        };
+    }
+}
diff a/test/langtools/tools/javac/reflect/TestIRFromAnnotation.java b/test/langtools/tools/javac/reflect/TestIRFromAnnotation.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/TestIRFromAnnotation.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @enablePreview
+ * @library ../lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * @summary Smoke test for accessing IR from annotation processors
+ * @run main TestIRFromAnnotation
+ */
+
+import com.sun.source.util.JavacTask;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.op.CoreOp.FuncOp;
+import java.lang.reflect.code.op.ExtendedOp;
+import java.lang.reflect.code.parser.OpParser;
+import java.lang.reflect.code.writer.OpWriter;
+import java.nio.charset.Charset;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.*;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+public class TestIRFromAnnotation {
+
+    public static void main(String... args) throws Exception {
+        String testSrc = System.getProperty("test.src");
+        File baseDir = Path.of(testSrc).toFile();
+        new TestIRFromAnnotation().run(baseDir);
+    }
+
+    void run(File baseDir) throws Exception {
+        for (File file : getAllFiles(List.of(baseDir))) {
+            if (!file.exists() || !file.getName().endsWith(".java")) {
+                continue;
+            }
+            analyze(file);
+        }
+    }
+
+    void analyze(File source) {
+        try {
+            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+            JavaFileManager fileManager = compiler.getStandardFileManager(null, null, Charset.defaultCharset());
+            JavacTask task = (JavacTask)compiler.getTask(null, fileManager, null,
+                    List.of("-proc:only",
+                            "--enable-preview",
+                            "--source", Integer.toString(SourceVersion.latest().runtimeVersion().feature())),
+                    null, List.of(new SourceFile(source)));
+            task.setProcessors(List.of(new Processor()));
+            task.analyze();
+        } catch (Throwable ex) {
+            throw new AssertionError("Unexpected exception when analyzing: " + source, ex);
+        }
+    }
+
+    File[] getAllFiles(List<File> roots) throws IOException {
+        long now = System.currentTimeMillis();
+        ArrayList<File> buf = new ArrayList<>();
+        for (File file : roots) {
+            Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    buf.add(file.toFile());
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        }
+        long delta = System.currentTimeMillis() - now;
+        System.err.println("All files = " + buf.size() + " " + delta);
+        return buf.toArray(new File[buf.size()]);
+    }
+
+    static class SourceFile extends SimpleJavaFileObject {
+
+        private final File file;
+        protected SourceFile(File file) {
+            super(file.toURI(), Kind.SOURCE);
+            this.file = file;
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+            return Files.readString(file.toPath());
+        }
+    }
+
+    public static class Processor extends JavacTestingAbstractProcessor {
+
+        public boolean process(Set<? extends TypeElement> annotations,
+                               RoundEnvironment roundEnvironment) {
+            class Scan extends ElementScanner<Void,Void> {
+                @Override
+                public Void visitExecutable(ExecutableElement e, Void p) {
+                    IR ir = e.getAnnotation(IR.class);
+                    if (ir == null) {
+                        return null; // skip
+                    }
+                    Optional<Object> body = elements.getBody(e);
+                    if (!body.isPresent()) {
+                        throw new AssertionError(String.format("No body found in method %s annotated with @IR",
+                                toMethodString(e)));
+                    }
+                    String actualOp = canonicalizeModel((FuncOp)body.get());
+                    String expectedOp = canonicalizeModel(ir.value());
+                    if (!actualOp.equals(expectedOp)) {
+                        throw new AssertionError(String.format("Bad IR found in %s:\n%s\nExpected:\n%s",
+                                toMethodString(e), actualOp, expectedOp));
+                    }
+                    return null;
+                }
+            }
+            Scan scan = new Scan();
+            for (Element e : roundEnvironment.getRootElements()) {
+                scan.scan(e);
+            }
+            return true;
+        }
+    }
+
+    // serializes dropping location information, parses, and then serializes, dropping location information
+    static String canonicalizeModel(Op o) {
+        return canonicalizeModel(serialize(o));
+    }
+
+    // parses, and then serializes, dropping location information
+    static String canonicalizeModel(String d) {
+        return serialize(OpParser.fromString(ExtendedOp.FACTORY, d).get(0));
+    }
+
+    // serializes, dropping location information
+    static String serialize(Op o) {
+        StringWriter w = new StringWriter();
+        OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
+        return w.toString();
+    }
+
+    static String toMethodString(ExecutableElement e) {
+        return e.getEnclosingElement() + "." + e;
+    }
+
+}
diff a/test/langtools/tools/javac/reflect/ThrowTest.java b/test/langtools/tools/javac/reflect/ThrowTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/ThrowTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with throw statements.
+ * @build ThrowTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester ThrowTest
+ */
+
+public class ThrowTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : ThrowTest)void -> {
+                %1 : java.lang.RuntimeException = new @"func<java.lang.RuntimeException>";
+                throw %1;
+            };
+            """)
+    void test1() {
+        throw new RuntimeException();
+    }
+}
diff a/test/langtools/tools/javac/reflect/TryTest.java b/test/langtools/tools/javac/reflect/TryTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/TryTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with try statements.
+ * @build TryTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester TryTest
+ */
+
+public class TryTest {
+
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : TryTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.try
+                    ()void -> {
+                        %3 : int = constant @"1";
+                        var.store %2 %3;
+                        yield;
+                    }
+                    ^catch(%4 : java.lang.Exception)void -> {
+                        %5 : Var<java.lang.Exception> = var %4 @"e";
+                        %6 : int = constant @"2";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^finally()void -> {
+                        %7 : int = constant @"3";
+                        var.store %2 %7;
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test1() {
+        int i = 0;
+        try {
+            i = 1;
+        } catch (Exception e) {
+            i = 2;
+        } finally {
+            i = 3;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : TryTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.try
+                    ()void -> {
+                        %3 : int = constant @"1";
+                        var.store %2 %3;
+                        yield;
+                    }
+                    ^finally()void -> {
+                        %4 : int = constant @"3";
+                        var.store %2 %4;
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test2() {
+        int i = 0;
+        try {
+            i = 1;
+        } finally {
+            i = 3;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : TryTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.try
+                    ()void -> {
+                        %3 : int = constant @"1";
+                        var.store %2 %3;
+                        yield;
+                    }
+                    ^catch(%4 : java.lang.Exception)void -> {
+                        %5 : Var<java.lang.Exception> = var %4 @"e";
+                        %6 : java.lang.Exception = var.load %5;
+                        invoke %6 @"java.lang.Exception::printStackTrace()void";
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test3() {
+        int i = 0;
+        try {
+            i = 1;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    static class A implements AutoCloseable {
+        final B b;
+
+        public A() {
+            this.b = null;
+        }
+
+        @Override
+        public void close() throws Exception {
+
+        }
+    }
+
+    static class B implements AutoCloseable {
+        C c;
+
+        @Override
+        public void close() throws Exception {
+
+        }
+    }
+
+    static class C implements AutoCloseable {
+        @Override
+        public void close() throws Exception {
+
+        }
+    }
+
+    A a() {
+        return null;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test4" (%0 : TryTest)void -> {
+                java.try
+                    ^resources()Tuple<Var<TryTest$A>, TryTest$B, Var<TryTest$C>> -> {
+                        %1 : TryTest$A = invoke %0 @"TryTest::a()TryTest$A";
+                        %2 : Var<TryTest$A> = var %1 @"a";
+                        %3 : TryTest$A = var.load %2;
+                        %4 : TryTest$B = field.load %3 @"TryTest$A::b()TryTest$B";
+                        %5 : TryTest$A = var.load %2;
+                        %6 : TryTest$B = field.load %5 @"TryTest$A::b()TryTest$B";
+                        %7 : TryTest$C = field.load %6 @"TryTest$B::c()TryTest$C";
+                        %8 : Var<TryTest$C> = var %7 @"c";
+                        %9 : Tuple<Var<TryTest$A>, TryTest$B, Var<TryTest$C>> = tuple %2 %4 %8;
+                        yield %9;
+                    }
+                    (%10 : Var<TryTest$A>, %11 : Var<TryTest$C>)void -> {
+                        %12 : TryTest$A = var.load %10;
+                        %13 : Var<TryTest$A> = var %12 @"_a";
+                        %14 : TryTest$C = var.load %11;
+                        %15 : Var<TryTest$C> = var %14 @"_c";
+                        yield;
+                    }
+                    ^catch(%16 : java.lang.Throwable)void -> {
+                        %17 : Var<java.lang.Throwable> = var %16 @"t";
+                        %18 : java.lang.Throwable = var.load %17;
+                        invoke %18 @"java.lang.Throwable::printStackTrace()void";
+                        yield;
+                    }
+                    ^finally()void -> {
+                        %19 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %20 : java.lang.String = constant @"F";
+                        invoke %19 %20 @"java.io.PrintStream::println(java.lang.String)void";
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test4() throws Exception {
+        try (A a = a(); a.b; C c = a.b.c) {
+            A _a = a;
+            C _c = c;
+        } catch (Throwable t) {
+            t.printStackTrace();
+        } finally {
+            System.out.println("F");
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test5" (%0 : TryTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.try
+                    ()void -> {
+                        %3 : int = constant @"1";
+                        var.store %2 %3;
+                        yield;
+                    }
+                    ^catch(%4 : java.lang.NullPointerException)void -> {
+                        %5 : Var<java.lang.NullPointerException> = var %4 @"e";
+                        %6 : int = constant @"2";
+                        var.store %2 %6;
+                        yield;
+                    }
+                    ^catch(%7 : java.lang.OutOfMemoryError)void -> {
+                        %8 : Var<java.lang.OutOfMemoryError> = var %7 @"e";
+                        %9 : int = constant @"3";
+                        var.store %2 %9;
+                        yield;
+                    };
+                return;
+            };
+            """)
+    void test5() {
+        int i = 0;
+        try {
+            i = 1;
+        } catch (NullPointerException e) {
+            i = 2;
+        } catch (OutOfMemoryError e) {
+            i = 3;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test6" (%0 : TryTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.try
+                    ()void -> {
+                        return;
+                    }
+                    ^catch(%3 : java.lang.Exception)void -> {
+                        %4 : Var<java.lang.Exception> = var %3 @"e";
+                        %5 : java.lang.Exception = var.load %4;
+                        throw %5;
+                    }
+                    ^finally()void -> {
+                        return;
+                    };
+                return;
+            };
+            """)
+    void test6() {
+        int i = 0;
+        try {
+            return;
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            return;
+        }
+     }
+
+}
diff a/test/langtools/tools/javac/reflect/WhileLoopTest.java b/test/langtools/tools/javac/reflect/WhileLoopTest.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/WhileLoopTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.runtime.CodeReflection;
+
+/*
+ * @test
+ * @summary Smoke test for code reflection with while loops.
+ * @build WhileLoopTest
+ * @build CodeReflectionTester
+ * @run main CodeReflectionTester WhileLoopTest
+ */
+
+public class WhileLoopTest {
+    @CodeReflection
+    @IR("""
+            func @"test1" (%0 : WhileLoopTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.while
+                    ^cond()boolean -> {
+                        %3 : int = var.load %2;
+                        %4 : int = constant @"10";
+                        %5 : boolean = lt %3 %4;
+                        yield %5;
+                    }
+                    ^body()void -> {
+                        %6 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %7 : int = var.load %2;
+                        invoke %6 %7 @"java.io.PrintStream::println(int)void";
+                        %8 : int = var.load %2;
+                        %9 : int = constant @"1";
+                        %10 : int = add %8 %9;
+                        var.store %2 %10;
+                        java.continue;
+                    };
+                return;
+            };
+            """)
+    void test1() {
+        int i = 0;
+        while (i < 10) {
+            System.out.println(i);
+            i = i + 1;
+        }
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test2" (%0 : WhileLoopTest)int -> {
+              %1 : int = constant @"0";
+              %2 : Var<int> = var %1 @"i";
+              java.while
+                  ^cond()boolean -> {
+                      %3 : int = var.load %2;
+                      %4 : int = constant @"10";
+                      %5 : boolean = lt %3 %4;
+                      yield %5;
+                  }
+                  ^body()void -> {
+                      %6 : int = var.load %2;
+                      return %6;
+                  };
+              %7 : int = constant @"-1";
+              return %7;
+            };
+            """)
+    int test2() {
+        int i = 0;
+        while (i < 10) {
+            return i;
+        }
+        return -1;
+    }
+
+    @CodeReflection
+    @IR("""
+            func @"test3" (%0 : WhileLoopTest)void -> {
+                %1 : int = constant @"0";
+                %2 : Var<int> = var %1 @"i";
+                java.do.while
+                    ^body()void -> {
+                        %3 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
+                        %4 : int = var.load %2;
+                        invoke %3 %4 @"java.io.PrintStream::println(int)void";
+                        %5 : int = var.load %2;
+                        %6 : int = constant @"1";
+                        %7 : int = add %5 %6;
+                        var.store %2 %7;
+                        java.continue;
+                    }
+                    ^cond()boolean -> {
+                        %8 : int = var.load %2;
+                        %9 : int = constant @"10";
+                        %10 : boolean = lt %8 %9;
+                        yield %10;
+                    };
+                return;
+            };
+            """)
+    void test3() {
+        int i = 0;
+        do {
+            System.out.println(i);
+            i = i + 1;
+        } while (i < 10);
+    }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestAssignment.java b/test/langtools/tools/javac/reflect/quoted/TestAssignment.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestAssignment.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test /nodynamiccopyright/
+ * @compile/fail/ref=TestAssignment.out -XDrawDiagnostics TestAssignment.java
+ */
+
+import java.lang.reflect.code.Quoted;
+
+class TestAssignment {
+    void test(boolean cond) {
+        Quoted f_NoRet = () -> {};
+        Quoted fiS_NoRet = (int i) -> ""; // ok (int->String)
+        Quoted fiS_Ret = (int i) -> { return ""; }; // ok (int->String)
+        Quoted fiV_NoRet = (int i) -> { }; // ok (int->V)
+        Quoted fiV_Ret = (int i) -> { return; }; // ok (int->V)
+        Quoted fiV_RetRet = (int i) -> { if (cond) return; else return; }; // ok (int->V)
+        Quoted fiS_RetRet = (int i) -> { if (cond) return "1"; else return "2"; }; // ok (int->String)
+    }
+
+    void testImplicit(boolean cond) {
+        Quoted fiS_NoRet = (i) -> ""; // error - no parameter types
+        Quoted fiS_Ret = (i) -> { return ""; }; // error - no parameter types
+        Quoted fiV_NoRet = (i) -> { }; // error - no parameter types
+        Quoted fiV_Ret = (i) -> { return; }; // error - no parameter types
+        Quoted fiV_RetRet = (i) -> { if (cond) return; else return; }; // error - no parameter types
+        Quoted fiS_RetRet = (i) -> { if (cond) return "1"; else return "2"; }; // error - no parameter types
+    }
+
+    void testImplicitVar(boolean cond) {
+        Quoted fiS_NoRet = (var i) -> ""; // error - no parameter types
+        Quoted fiS_Ret = (var i) -> { return ""; }; // error - no parameter types
+        Quoted fiV_NoRet = (var i) -> { }; // error - no parameter types
+        Quoted fiV_Ret = (var i) -> { return; }; // error - no parameter types
+        Quoted fiV_RetRet = (var i) -> { if (cond) return; else return; }; // error - no parameter types
+        Quoted fiS_RetRet = (var i) -> { if (cond) return "1"; else return "2"; }; // error - no parameter types
+    }
+
+    void testBadInferredReturn(boolean cond) {
+        Quoted fi_RetVRetS = (int i) -> { if (cond) return; else return ""; }; // error - only one branch returns
+        Quoted fi_RetS = (int i) -> { if (cond) { return "2"; } }; // error - one return, but body completes normally
+    }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestAssignment.out b/test/langtools/tools/javac/reflect/quoted/TestAssignment.out
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestAssignment.out
@@ -0,0 +1,15 @@
+TestAssignment.java:43:28: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:44:26: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:45:28: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:46:26: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:47:29: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:48:29: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:52:28: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:53:26: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:54:28: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:55:26: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:56:29: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:57:29: compiler.err.quoted.lambda.must.be.explicit
+TestAssignment.java:61:41: compiler.err.cant.infer.quoted.lambda.return.type: void,java.lang.String
+TestAssignment.java:62:37: compiler.err.cant.infer.quoted.lambda.return.type: java.lang.String,void
+14 errors
diff a/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for captured values in quotable lambdas.
+ * @run testng TestCaptureQuotable
+ */
+
+import org.testng.annotations.*;
+
+import java.lang.reflect.code.op.CoreOp.Var;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Quotable;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.function.IntSupplier;
+import java.util.function.IntUnaryOperator;
+import java.util.function.ToIntFunction;
+import java.util.stream.IntStream;
+
+import static org.testng.Assert.*;
+
+public class TestCaptureQuotable {
+
+    @Test(dataProvider = "ints")
+    public void testCaptureIntParam(int x) {
+        Quotable quotable = (Quotable & IntUnaryOperator)y -> x + y;
+        Quoted quoted = quotable.quoted();
+        assertEquals(quoted.capturedValues().size(), 1);
+        assertEquals(((Var)quoted.capturedValues().values().iterator().next()).value(), x);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, x + 1);
+    }
+
+    @Test
+    public void testCaptureRefAndIntConstant() {
+        final int x = 100;
+        String hello = "hello";
+        Quotable quotable = (Quotable & ToIntFunction<Number>)y -> y.intValue() + hello.length() + x;
+        Quoted quoted = quotable.quoted();
+        assertEquals(quoted.capturedValues().size(), 2);
+        Iterator<Object> it = quoted.capturedValues().values().iterator();
+        assertEquals(((Var)it.next()).value(), hello);
+        assertEquals(((Var)it.next()).value(), x);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, x + 1 + hello.length());
+    }
+
+    @Test
+    public void testCaptureMany() {
+        int[] ia = new int[8];
+        int i1 = ia[0] = 0;
+        int i2 = ia[1] = 1;
+        int i3 = ia[2] = 2;
+        int i4 = ia[3] = 3;
+        int i5 = ia[4] = 4;
+        int i6 = ia[5] = 5;
+        int i7 = ia[6] = 6;
+        int i8 = ia[7] = 7;
+
+        Quotable quotable = (Quotable & IntSupplier) () -> i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8;
+        Quoted quoted = quotable.quoted();
+        assertEquals(quoted.capturedValues().size(), ia.length);
+        assertEquals(quoted.op().capturedValues(), new ArrayList<>(quoted.capturedValues().keySet()));
+        Iterator<Object> it = quoted.capturedValues().values().iterator();
+        int i = 0;
+        while (it.hasNext()) {
+            int actual = (int) ((Var)it.next()).value();
+            assertEquals(actual, ia[i++]);
+        }
+    }
+
+    @Test(dataProvider = "ints")
+    public void testCaptureIntField(int x) {
+        class Context {
+            final int x;
+
+            Context(int x) {
+                this.x = x;
+            }
+
+            Quotable quotable() {
+                return (Quotable & IntUnaryOperator) y -> x + y;
+            }
+        }
+        Context context = new Context(x);
+        Quotable quotable = context.quotable();
+        Quoted quoted = quotable.quoted();
+        assertEquals(quoted.capturedValues().size(), 1);
+        assertEquals(quoted.capturedValues().values().iterator().next(), context);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, x + 1);
+    }
+
+    @DataProvider(name = "ints")
+    public Object[][] ints() {
+        return IntStream.range(0, 50)
+                .mapToObj(i -> new Object[] { i })
+                .toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "ints")
+    public void testCaptureReferenceReceiver(int i) {
+        int prevCount = Box.count;
+        Quotable quotable = (Quotable & IntUnaryOperator)new Box(i)::add;
+        Quoted quoted = quotable.quoted();
+        assertEquals(Box.count, prevCount + 1); // no duplicate receiver computation!
+        assertEquals(quoted.capturedValues().size(), 1);
+        assertEquals(((Box)((Var)quoted.capturedValues().values().iterator().next()).value()).i, i);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, i + 1);
+    }
+
+    record Box(int i) {
+
+        static int count = 0;
+
+        Box {
+           count++; // keep track of side-effects
+        }
+
+        int add(int i) {
+            return i + this.i;
+        }
+    }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestCaptureQuoted.java b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuoted.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuoted.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for captured values in quoted lambdas.
+ * @run testng TestCaptureQuoted
+ */
+
+import java.lang.reflect.code.op.CoreOp.Var;
+import java.lang.reflect.code.Op;
+import java.lang.reflect.code.Quoted;
+import java.lang.reflect.code.interpreter.Interpreter;
+import java.lang.invoke.MethodHandles;
+import java.util.stream.IntStream;
+
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class TestCaptureQuoted {
+
+    @Test(dataProvider = "ints")
+    public void testCaptureIntParam(int x) {
+        Quoted quoted = (int y) -> x + y;
+        assertEquals(quoted.capturedValues().size(), 1);
+        assertEquals(((Var)quoted.capturedValues().values().iterator().next()).value(), x);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, x + 1);
+    }
+
+    @Test(dataProvider = "ints")
+    public void testCaptureIntField(int x) {
+        class Context {
+            final int x;
+
+            Context(int x) {
+                this.x = x;
+            }
+
+            Quoted quoted() {
+                return (int y) -> x + y;
+            }
+        }
+        Context context = new Context(x);
+        Quoted quoted = context.quoted();
+        assertEquals(quoted.capturedValues().size(), 1);
+        assertEquals(quoted.capturedValues().values().iterator().next(), context);
+        int res = (int)Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) quoted.op(),
+                quoted.capturedValues(), 1);
+        assertEquals(res, x + 1);
+    }
+
+    @DataProvider(name = "ints")
+    public Object[][] ints() {
+        return IntStream.range(0, 50)
+                .mapToObj(i -> new Object[] { i })
+                .toArray(Object[][]::new);
+    }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.java b/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test /nodynamiccopyright/
+ * @compile/fail/ref=TestGenericMethodCall.out -XDrawDiagnostics TestGenericMethodCall.java
+ */
+
+import java.lang.reflect.code.Quoted;
+
+public class TestGenericMethodCall {
+    void test(boolean cond) {
+        apply(Quoted.class, () -> {});
+        apply(Quoted.class, (int i) -> ""); // ok (int->String)
+        apply(Quoted.class, (int i) -> { return ""; }); // ok (int->String)
+        apply(Quoted.class, (int i) -> { }); // ok (int->V)
+        apply(Quoted.class, (int i) -> { return; }); // ok (int->V)
+        apply(Quoted.class, (int i) -> { if (cond) return; else return; }); // ok (int->V)
+        apply(Quoted.class, (int i) -> { if (cond) return "1"; else return "2"; }); // ok (int->String)
+    }
+
+    void testImplicit(boolean cond) {
+        apply(Quoted.class, (i) -> ""); // error - no parameter types
+        apply(Quoted.class, (i) -> { return ""; }); // error - no parameter types
+        apply(Quoted.class, (i) -> { }); // error - no parameter types
+        apply(Quoted.class, (i) -> { return; }); // error - no parameter types
+        apply(Quoted.class, (i) -> { if (cond) return; else return; }); // error - no parameter types
+        apply(Quoted.class, (i) -> { if (cond) return "1"; else return "2"; }); // error - no parameter types
+    }
+
+    void testImplicitVar(boolean cond) {
+        apply(Quoted.class, (var i) -> ""); // error - no parameter types
+        apply(Quoted.class, (var i) -> { return ""; }); // error - no parameter types
+        apply(Quoted.class, (var i) -> { }); // error - no parameter types
+        apply(Quoted.class, (var i) -> { return; }); // error - no parameter types
+        apply(Quoted.class, (var i) -> { if (cond) return; else return; }); // error - no parameter types
+        apply(Quoted.class, (var i) -> { if (cond) return "1"; else return "2"; }); // error - no parameter types
+    }
+
+    void testBadInferredReturn(boolean cond) {
+        apply(Quoted.class, (int i) -> { if (cond) return; else return ""; }); // error - only one branch returns
+        apply(Quoted.class, (int i) -> { if (cond) { return "2"; } }); // error - one return, but body completes normally
+    }
+
+    <Z> void apply(Class<Z> clazz, Z quoted) { }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.out b/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.out
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestGenericMethodCall.out
@@ -0,0 +1,15 @@
+TestGenericMethodCall.java:43:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:44:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:45:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:46:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:47:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:48:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:52:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:53:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:54:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:55:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:56:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:57:29: compiler.err.quoted.lambda.must.be.explicit
+TestGenericMethodCall.java:61:40: compiler.err.cant.infer.quoted.lambda.return.type: void,java.lang.String
+TestGenericMethodCall.java:62:40: compiler.err.cant.infer.quoted.lambda.return.type: java.lang.String,void
+14 errors
diff a/test/langtools/tools/javac/reflect/quoted/TestMethodCall.java b/test/langtools/tools/javac/reflect/quoted/TestMethodCall.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestMethodCall.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test /nodynamiccopyright/
+ * @compile/fail/ref=TestMethodCall.out -XDrawDiagnostics TestMethodCall.java
+ */
+
+import java.lang.reflect.code.Quoted;
+
+public class TestMethodCall {
+    void test(boolean cond) {
+        apply(() -> {});
+        apply((int i) -> ""); // ok (int->String)
+        apply((int i) -> { return ""; }); // ok (int->String)
+        apply((int i) -> { }); // ok (int->V)
+        apply((int i) -> { return; }); // ok (int->V)
+        apply((int i) -> { if (cond) return; else return; }); // ok (int->V)
+        apply((int i) -> { if (cond) return "1"; else return "2"; }); // ok (int->String)
+    }
+
+    void testImplicit(boolean cond) {
+        apply((i) -> ""); // error - no parameter types
+        apply((i) -> { return ""; }); // error - no parameter types
+        apply((i) -> { }); // error - no parameter types
+        apply((i) -> { return; }); // error - no parameter types
+        apply((i) -> { if (cond) return; else return; }); // error - no parameter types
+        apply((i) -> { if (cond) return "1"; else return "2"; }); // error - no parameter types
+    }
+
+    void testImplicitVar(boolean cond) {
+        apply((var i) -> ""); // error - no parameter types
+        apply((var i) -> { return ""; }); // error - no parameter types
+        apply((var i) -> { }); // error - no parameter types
+        apply((var i) -> { return; }); // error - no parameter types
+        apply((var i) -> { if (cond) return; else return; }); // error - no parameter types
+        apply((var i) -> { if (cond) return "1"; else return "2"; }); // error - no parameter types
+    }
+
+    void testBadInferredReturn(boolean cond) {
+        apply((int i) -> { if (cond) return; else return ""; }); // error - only one branch returns
+        apply((int i) -> { if (cond) { return "2"; } }); // error - one return, but body completes normally
+    }
+
+    void apply(Quoted quoted) { }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestMethodCall.out b/test/langtools/tools/javac/reflect/quoted/TestMethodCall.out
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestMethodCall.out
@@ -0,0 +1,15 @@
+TestMethodCall.java:43:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:44:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:45:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:46:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:47:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:48:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:52:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:53:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:54:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:55:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:56:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:57:15: compiler.err.quoted.lambda.must.be.explicit
+TestMethodCall.java:61:26: compiler.err.cant.infer.quoted.lambda.return.type: void,java.lang.String
+TestMethodCall.java:62:26: compiler.err.cant.infer.quoted.lambda.return.type: java.lang.String,void
+14 errors
diff a/test/langtools/tools/javac/reflect/quoted/TestRecovery.java b/test/langtools/tools/javac/reflect/quoted/TestRecovery.java
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestRecovery.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test /nodynamiccopyright/
+ * @compile/fail/ref=TestRecovery.out -XDrawDiagnostics TestRecovery.java
+ */
+
+import java.lang.reflect.code.Quoted;
+
+public class TestRecovery {
+    void testRecoveryInAssignment() {
+        Quoted res = (x) -> { System.out.println(nonExistent); };
+    }
+
+    void testRecoveryInMethodCall() {
+        apply((x) -> { System.out.println(nonExistent); });
+    }
+
+    void testRecoveryInGenericMethodCall() {
+        apply(Quoted.class, (x) -> { System.out.println(nonExistent); });
+    }
+
+    void apply(Quoted quoted) { }
+    <Z> void apply(Class<Z> clazz, Z quoted) { }
+}
diff a/test/langtools/tools/javac/reflect/quoted/TestRecovery.out b/test/langtools/tools/javac/reflect/quoted/TestRecovery.out
--- /dev/null
+++ b/test/langtools/tools/javac/reflect/quoted/TestRecovery.out
@@ -0,0 +1,7 @@
+TestRecovery.java:33:22: compiler.err.quoted.lambda.must.be.explicit
+TestRecovery.java:33:50: compiler.err.cant.resolve.location: kindname.variable, nonExistent, , , (compiler.misc.location: kindname.class, TestRecovery, null)
+TestRecovery.java:37:15: compiler.err.quoted.lambda.must.be.explicit
+TestRecovery.java:37:43: compiler.err.cant.resolve.location: kindname.variable, nonExistent, , , (compiler.misc.location: kindname.class, TestRecovery, null)
+TestRecovery.java:41:29: compiler.err.quoted.lambda.must.be.explicit
+TestRecovery.java:41:57: compiler.err.cant.resolve.location: kindname.variable, nonExistent, , , (compiler.misc.location: kindname.class, TestRecovery, null)
+6 errors
