diff a/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/impl/LoweringTransformer.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/impl/LoweringTransformer.java --- /dev/null +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/impl/LoweringTransformer.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2025, 2026, 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.incubator.code.bytecode.impl; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.SequencedMap; +import java.util.Set; + +import jdk.incubator.code.Block; +import jdk.incubator.code.Body; +import jdk.incubator.code.CodeContext; +import jdk.incubator.code.CodeTransformer; +import jdk.incubator.code.Op; +import jdk.incubator.code.Value; +import jdk.incubator.code.dialect.core.CoreOp; +import jdk.incubator.code.dialect.core.CoreOp.FuncOp; +import jdk.incubator.code.dialect.core.NormalizeBlocksTransformer; +import jdk.incubator.code.dialect.java.ClassType; +import jdk.incubator.code.dialect.java.ConstantExpressionTransformer; +import jdk.incubator.code.dialect.java.JavaOp; +import jdk.incubator.code.dialect.java.JavaType; +import jdk.incubator.code.dialect.java.MethodRef; +import jdk.incubator.code.dialect.java.PrimitiveType; +import jdk.incubator.code.internal.BranchTarget; +import jdk.incubator.code.internal.RemoveUnusedConstantTransformer; + +import static jdk.incubator.code.dialect.core.CoreOp.YieldOp; +import static jdk.incubator.code.dialect.core.CoreOp.branch; +import static jdk.incubator.code.dialect.java.JavaType.*; + +/** + * Lowering transformer generates models supported by {@code BytecodeGenerator}. + * It expands lambda operations into synthetic functions and dynamic function calls, + * evaluates constant expressions, removes unused constants, lowers operations, and + * normalizes blocks. + * Constant-labeled switch statements and switch expressions are lowered to + * {@code ConstantLabelSwitchOp} with evaluated labels. + * We expect label value to be the second operand to the operation that perform equality check. + */ +public final class LoweringTransformer { + + private static CodeTransformer getInstance(MethodHandles.Lookup lookup) { + return (block, op) -> switch (op) { + case JavaOp.JavaSwitchOp swOp -> { + Optional opt = isCaseConstantSwitchWithIntegralSelector(swOp, lookup); + if (opt.isPresent()) { + yield lowerToConstantLabelSwitchOp(block, block.transformer(), swOp, opt.get()); + } + yield swOp.lower(block, null); + } + case Op.Lowerable lop -> lop.lower(block, null); + default -> { + block.add(op); + yield block; + } + }; + } + + public static CoreOp.ModuleOp transform(MethodHandles.Lookup lookup, + SequencedMap ops) { + CoreOp.ModuleOp module = LambdaExpansionTransformer.transform(lookup, ops); + CodeTransformer lowering = getInstance(lookup); + List functions = new ArrayList<>(); + for (FuncOp fop : module.functionTable().sequencedValues()) { + Op transformed = ConstantExpressionTransformer.transform(lookup, fop); + transformed = transformed.transform(CodeContext.create(), RemoveUnusedConstantTransformer.INSTANCE); + functions.add(NormalizeBlocksTransformer.transform( + (FuncOp) transformed.transform(CodeContext.create(), lowering))); + } + return CoreOp.module(functions); + } + + private static Block.Builder lowerToConstantLabelSwitchOp(Block.Builder block, CodeTransformer transformer, + JavaOp.JavaSwitchOp swOp, LabelsAndTargets labelsAndTargets) { + List targets = labelsAndTargets.targets(); + List blocks = new ArrayList<>(); + for (int i = 0; i < targets.size(); i++) { + Block.Builder bb = block.block(); + blocks.add(bb); + } + + Block.Builder exit; + if (targets.isEmpty()) { + exit = block; + } else { + if (swOp.resultType() != VOID) { + exit = block.block(swOp.resultType()); + } else { + exit = block.block(); + } + if (swOp instanceof JavaOp.SwitchExpressionOp) { + exit.context().mapValue(swOp.result(), exit.parameters().get(0)); + } + } + + BranchTarget.setBranchTarget(block.context(), swOp, exit, null); + // map statement body to nextExprBlock + // this mapping will be used for lowering SwitchFallThroughOp + for (int i = 0; i < targets.size() - 1; i++) { + BranchTarget.setBranchTarget(block.context(), targets.get(i).parent(), null, blocks.get(i + 1)); + } + + for (int i = 0; i < targets.size(); i++) { + Block.Builder curr = blocks.get(i); + curr.transformBody(targets.get(i).parent(), blocks.get(i).parameters(), (b, op) -> switch (op) { + case YieldOp _ when swOp instanceof JavaOp.SwitchStatementOp -> { + b.add(branch(exit.reference())); + yield b; + } + case YieldOp yop when swOp instanceof JavaOp.SwitchExpressionOp -> { + b.add(branch(exit.reference(b.context().getValue(yop.yieldValue())))); + yield b; + } + default -> transformer.acceptOp(b, op); + }); + } + + Value selector = block.context().getValue(swOp.operands().get(0)); + if (integralReferenceTypes.contains(selector.type())) { + // unbox selector + if (selector.type().equals(J_L_CHARACTER)) { + selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "charValue", JavaType.CHAR), selector)); + } else { + selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "intValue", JavaType.INT), selector)); + } + } + var labels = labelsAndTargets.labels(); + if (!labels.contains(null)) { + // implicit default to exit + labels.add(null); + blocks.add(exit); + } + block.add(new ConstantLabelSwitchOp(selector, labels, blocks.stream().map(Block.Builder::reference).toList())); + return exit; + } + + public record LabelsAndTargets(List labels, List targets) {} + + // @@@ should we add these functionalities to the API ? + private static final Set integralReferenceTypes = Set.of(J_L_BYTE, J_L_CHARACTER, J_L_SHORT, J_L_INTEGER); + private static final Set integralPrimitiveTypes = Set.of(BYTE, CHAR, SHORT, INT); + + public static Optional isCaseConstantSwitchWithIntegralSelector(JavaOp.JavaSwitchOp swOp, MethodHandles.Lookup lookup) { + //@@@ we only check for case constant switch that has integral type + // so labels in model / source code will be identical to the one we will find in bytecode + Value selector = swOp.operands().get(0); + if (!integralPrimitiveTypes.contains(selector.type()) && !integralReferenceTypes.contains(selector.type())) { + return Optional.empty(); + } + var labels = new ArrayList(); + var targets = new ArrayList(); + for (int i = 0; i < swOp.bodies().size(); i += 2) { + Body label = swOp.bodies().get(i); + List ls = isCaseConstantLabel(lookup, label); + if (ls.isEmpty()) { + return Optional.empty(); + } + labels.addAll(ls); + targets.addAll(Collections.nCopies(ls.size(), swOp.bodies().get(i + 1).entryBlock())); + } + return Optional.of(new LabelsAndTargets(labels, targets)); + } + + private static List isCaseConstantLabel(MethodHandles.Lookup l, Body label) { + List empty = new ArrayList<>(); + if (label.blocks().size() != 1 || !(label.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) || + !(yop.yieldValue() instanceof Op.Result r)) { + return empty; + } + List labels = new ArrayList<>(); + // we can yield a list + MethodRef objectsEquals = MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class); + switch (r.op()) { + case JavaOp.EqOp eqOp -> { + Optional v = JavaOp.JavaExpression.evaluate(l, eqOp.operands().getLast()); + v.ifPresent(o -> labels.add(toInteger(o))); + } + case JavaOp.InvokeOp ie when ie.invokeReference().equals(objectsEquals) -> { + Optional v = JavaOp.JavaExpression.evaluate(l, ie.operands().getLast()); + v.ifPresent(o -> labels.add(toInteger(o))); + } + case JavaOp.ConditionalOrOp cor -> { + for (Body corb : cor.bodies()) { + List corbl = isCaseConstantLabel(l, corb); + if (corbl.isEmpty()) { + return empty; + } + labels.addAll(corbl); + } + } + case CoreOp.ConstantOp cop when cop.value() instanceof Boolean b && b -> { + labels.add(null); + } + default -> { + } + } + return labels; + } + + private static Integer toInteger(Object o) { + return switch (o) { + case Byte b -> Integer.valueOf(b); + case Short s -> Integer.valueOf(s); + case Character c -> Integer.valueOf(c); + case Integer i -> i; + // @@@ should not reach here + default -> throw new IllegalArgumentException("Object of type " + o.getClass().getName() + " can't be cast to Integer"); + }; + } +}