1 /*
  2  * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.incubator.code.bytecode.impl;
 26 
 27 import java.lang.invoke.MethodHandles;
 28 import java.util.ArrayList;
 29 import java.util.Collections;
 30 import java.util.List;
 31 import java.util.Optional;
 32 import java.util.SequencedMap;
 33 import java.util.Set;
 34 
 35 import jdk.incubator.code.Block;
 36 import jdk.incubator.code.Body;
 37 import jdk.incubator.code.CodeContext;
 38 import jdk.incubator.code.CodeTransformer;
 39 import jdk.incubator.code.Op;
 40 import jdk.incubator.code.Value;
 41 import jdk.incubator.code.dialect.core.CoreOp;
 42 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 43 import jdk.incubator.code.dialect.java.ClassType;
 44 import jdk.incubator.code.dialect.java.JavaOp;
 45 import jdk.incubator.code.dialect.java.JavaType;
 46 import jdk.incubator.code.dialect.java.MethodRef;
 47 import jdk.incubator.code.dialect.java.PrimitiveType;
 48 import jdk.incubator.code.internal.BranchTarget;
 49 import jdk.incubator.code.internal.ControlFlowPreparation;
 50 
 51 import static jdk.incubator.code.dialect.core.CoreOp.YieldOp;
 52 import static jdk.incubator.code.dialect.core.CoreOp.branch;
 53 import static jdk.incubator.code.dialect.java.JavaType.*;
 54 
 55 /**
 56  * Lowering transformer generates models supported by {@code BytecodeGenerator}.
 57  * It expands lambda operations into synthetic functions and dynamic function calls,
 58  * removes unused constants, lowers operations, and prepares control flow.
 59  * Constant-labeled switch statements and switch expressions are lowered to
 60  * {@code ConstantLabelSwitchOp} using compile-time constant label values.
 61  * We expect label value to be the second operand to the operation that perform equality check.
 62  */
 63 public final class LoweringTransformer {
 64 
 65     private static CodeTransformer getInstance(MethodHandles.Lookup lookup) {
 66         return (block, op) -> switch (op) {
 67             case JavaOp.SwitchOp swOp -> {
 68                 Optional<LabelsAndTargets> opt = isCaseConstantSwitchWithIntegralSelector(swOp, lookup);
 69                 if (opt.isPresent()) {
 70                     yield lowerToConstantLabelSwitchOp(block, block.transformer(), swOp, opt.get());
 71                 }
 72                 yield swOp.lower(block, null);
 73             }
 74             case Op.Lowerable lop -> lop.lower(block, null);
 75             default -> {
 76                 block.add(op);
 77                 yield block;
 78             }
 79         };
 80     }
 81 
 82     public static <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(MethodHandles.Lookup lookup,
 83                                                                           SequencedMap<String, ? extends O> ops) {
 84         CodeTransformer lowering = getInstance(lookup);
 85         // lambda expansion goes first, so reflectable lambdas use the original model
 86         CoreOp.ModuleOp module = LambdaExpansionTransformer.transform(lookup, ops);
 87         List<FuncOp> functions = new ArrayList<>();
 88         for (FuncOp fop : module.functionTable().sequencedValues()) {
 89             FuncOp lowered = fop.elements().anyMatch(op -> op instanceof Op.Lowerable
 90                     // lambda lowering never replaces itself
 91                     && !(op instanceof JavaOp.LambdaOp))
 92                     ? fop.transform(CodeContext.create(), lowering)
 93                     : fop;
 94             functions.add(ControlFlowPreparation.transform(lowered));
 95         }
 96         return CoreOp.module(functions);
 97     }
 98 
 99     private static Block.Builder lowerToConstantLabelSwitchOp(Block.Builder block, CodeTransformer transformer,
100                                                               JavaOp.SwitchOp swOp, LabelsAndTargets labelsAndTargets) {
101         List<Block> targets = labelsAndTargets.targets();
102         List<Block.Builder> blocks = new ArrayList<>();
103         for (int i = 0; i < targets.size(); i++) {
104             Block.Builder bb = block.block();
105             blocks.add(bb);
106         }
107 
108         Block.Builder exit;
109         if (targets.isEmpty()) {
110             exit = block;
111         } else {
112             if (swOp.resultType() != VOID) {
113                 exit = block.block(swOp.resultType());
114             } else {
115                 exit = block.block();
116             }
117             if (swOp instanceof JavaOp.SwitchExpressionOp) {
118                 exit.context().mapValue(swOp.result(), exit.parameters().get(0));
119             }
120         }
121 
122         BranchTarget.setBranchTarget(block.context(), swOp, exit, null);
123         // map statement body to nextExprBlock
124         // this mapping will be used for lowering SwitchFallThroughOp
125         for (int i = 0; i < targets.size() - 1; i++) {
126             BranchTarget.setBranchTarget(block.context(), targets.get(i).parent(), null, blocks.get(i + 1));
127         }
128 
129         for (int i = 0; i < targets.size(); i++) {
130             Block.Builder curr = blocks.get(i);
131             curr.transformBody(targets.get(i).parent(), blocks.get(i).parameters(), (b, op) -> switch (op) {
132                 case YieldOp _ when swOp instanceof JavaOp.SwitchStatementOp -> {
133                     b.add(branch(exit.reference()));
134                     yield b;
135                 }
136                 case YieldOp yop when swOp instanceof JavaOp.SwitchExpressionOp -> {
137                     b.add(branch(exit.reference(b.context().getValue(yop.yieldValue()))));
138                     yield b;
139                 }
140                 default -> transformer.acceptOp(b, op);
141             });
142         }
143 
144         Value selector = block.context().getValue(swOp.operands().get(0));
145         if (integralReferenceTypes.contains(selector.type())) {
146             // unbox selector
147             if (selector.type().equals(J_L_CHARACTER)) {
148                 selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "charValue", JavaType.CHAR), selector));
149             } else {
150                 selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "intValue", JavaType.INT), selector));
151             }
152         }
153         var labels = labelsAndTargets.labels();
154         if (!labels.contains(null)) {
155             // implicit default to exit
156             labels.add(null);
157             blocks.add(exit);
158         }
159         block.add(new ConstantLabelSwitchOp(selector, labels, blocks.stream().map(Block.Builder::reference).toList()));
160         return exit;
161     }
162 
163     public record LabelsAndTargets(List<Integer> labels, List<Block> targets) {}
164 
165     // @@@ should we add these functionalities to the API ?
166     private static final Set<ClassType> integralReferenceTypes = Set.of(J_L_BYTE, J_L_CHARACTER, J_L_SHORT, J_L_INTEGER);
167     private static final Set<PrimitiveType> integralPrimitiveTypes = Set.of(BYTE, CHAR, SHORT, INT);
168 
169     public static Optional<LabelsAndTargets> isCaseConstantSwitchWithIntegralSelector(JavaOp.SwitchOp swOp, MethodHandles.Lookup lookup) {
170         //@@@ we only check for case constant switch that has integral type
171         // so labels in model / source code will be identical to the one we will find in bytecode
172         Value selector = swOp.operands().get(0);
173         if (!integralPrimitiveTypes.contains(selector.type()) && !integralReferenceTypes.contains(selector.type())) {
174             return Optional.empty();
175         }
176         var labels = new ArrayList<Integer>();
177         var targets = new ArrayList<Block>();
178         for (int i = 0; i < swOp.bodies().size(); i += 2) {
179             Body label = swOp.bodies().get(i);
180             List<Integer> ls = isCaseConstantLabel(label);
181             if (ls.isEmpty()) {
182                 return Optional.empty();
183             }
184             labels.addAll(ls);
185             targets.addAll(Collections.nCopies(ls.size(), swOp.bodies().get(i + 1).entryBlock()));
186         }
187         return Optional.of(new LabelsAndTargets(labels, targets));
188     }
189 
190     private static List<Integer> isCaseConstantLabel(Body label) {
191         List<Integer> empty = new ArrayList<>();
192         if (label.blocks().size() != 1 || !(label.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
193                 !(yop.yieldValue() instanceof Op.Result r)) {
194             return empty;
195         }
196         List<Integer> labels = new ArrayList<>();
197         // we can yield a list
198         switch (r.op()) {
199             case JavaOp.EqOp eqOp -> {
200                 if (eqOp.operands().getLast() instanceof Op.Result labelResult
201                         && labelResult.op() instanceof CoreOp.ConstantOp constant) {
202                     Integer value = toInteger(constant.value());
203                     if (value != null) labels.add(value);
204                 }
205             }
206             case JavaOp.ConditionalOrOp cor -> {
207                 for (Body corb : cor.bodies()) {
208                     List<Integer> corbl = isCaseConstantLabel(corb);
209                     if (corbl.isEmpty()) {
210                         return empty;
211                     }
212                     labels.addAll(corbl);
213                 }
214             }
215             case CoreOp.ConstantOp cop when cop.value() instanceof Boolean b && b -> {
216                 labels.add(null);
217             }
218             default -> {
219             }
220         }
221         return labels;
222     }
223 
224     private static Integer toInteger(Object o) {
225         return switch (o) {
226             case Byte b -> Integer.valueOf(b);
227             case Short s -> Integer.valueOf(s);
228             case Character c -> Integer.valueOf(c);
229             case Integer i -> i;
230             // @@@ should not reach here
231             default -> throw new IllegalArgumentException("Object of type " + o.getClass().getName() + " can't be cast to Integer");
232         };
233     }
234 }