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.Objects;
 32 import java.util.Optional;
 33 import java.util.SequencedMap;
 34 import java.util.Set;
 35 
 36 import jdk.incubator.code.Block;
 37 import jdk.incubator.code.Body;
 38 import jdk.incubator.code.CodeContext;
 39 import jdk.incubator.code.CodeTransformer;
 40 import jdk.incubator.code.Op;
 41 import jdk.incubator.code.Value;
 42 import jdk.incubator.code.dialect.core.CoreOp;
 43 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 44 import jdk.incubator.code.dialect.core.NormalizeBlocksTransformer;
 45 import jdk.incubator.code.dialect.java.ClassType;
 46 import jdk.incubator.code.dialect.java.ConstantExpressionTransformer;
 47 import jdk.incubator.code.dialect.java.JavaOp;
 48 import jdk.incubator.code.dialect.java.JavaType;
 49 import jdk.incubator.code.dialect.java.MethodRef;
 50 import jdk.incubator.code.dialect.java.PrimitiveType;
 51 import jdk.incubator.code.internal.BranchTarget;
 52 import jdk.incubator.code.internal.RemoveUnusedConstantTransformer;
 53 
 54 import static jdk.incubator.code.dialect.core.CoreOp.YieldOp;
 55 import static jdk.incubator.code.dialect.core.CoreOp.branch;
 56 import static jdk.incubator.code.dialect.java.JavaType.*;
 57 
 58 /**
 59  * Lowering transformer generates models supported by {@code BytecodeGenerator}.
 60  * It expands lambda operations into synthetic functions and dynamic function calls,
 61  * evaluates constant expressions, removes unused constants, lowers operations, and
 62  * normalizes blocks.
 63  * Constant-labeled switch statements and switch expressions are lowered to
 64  * {@code ConstantLabelSwitchOp} with evaluated labels.
 65  * We expect label value to be the second operand to the operation that perform equality check.
 66  */
 67 public final class LoweringTransformer {
 68 
 69     private static CodeTransformer getInstance(MethodHandles.Lookup lookup) {
 70         return (block, op) -> switch (op) {
 71             case JavaOp.JavaSwitchOp swOp -> {
 72                 Optional<LabelsAndTargets> opt = isCaseConstantSwitchWithIntegralSelector(swOp, lookup);
 73                 if (opt.isPresent()) {
 74                     yield lowerToConstantLabelSwitchOp(block, block.transformer(), swOp, opt.get());
 75                 }
 76                 yield swOp.lower(block, null);
 77             }
 78             case Op.Lowerable lop -> lop.lower(block, null);
 79             default -> {
 80                 block.add(op);
 81                 yield block;
 82             }
 83         };
 84     }
 85 
 86     public static <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(MethodHandles.Lookup lookup,
 87                                                                           SequencedMap<String, ? extends O> ops) {
 88         CoreOp.ModuleOp module = LambdaExpansionTransformer.transform(lookup, ops);
 89         CodeTransformer lowering = getInstance(lookup);
 90         List<FuncOp> functions = new ArrayList<>();
 91         for (FuncOp fop : module.functionTable().sequencedValues()) {
 92             Op transformed = ConstantExpressionTransformer.transform(lookup, fop);
 93             transformed = transformed.transform(CodeContext.create(), RemoveUnusedConstantTransformer.INSTANCE);
 94             functions.add(NormalizeBlocksTransformer.transform(
 95                     (FuncOp) transformed.transform(CodeContext.create(), lowering)));
 96         }
 97         return CoreOp.module(functions);
 98     }
 99 
100     private static Block.Builder lowerToConstantLabelSwitchOp(Block.Builder block, CodeTransformer transformer,
101                                                               JavaOp.JavaSwitchOp swOp, LabelsAndTargets labelsAndTargets) {
102         List<Block> targets = labelsAndTargets.targets();
103         List<Block.Builder> blocks = new ArrayList<>();
104         for (int i = 0; i < targets.size(); i++) {
105             Block.Builder bb = block.block();
106             blocks.add(bb);
107         }
108 
109         Block.Builder exit;
110         if (targets.isEmpty()) {
111             exit = block;
112         } else {
113             if (swOp.resultType() != VOID) {
114                 exit = block.block(swOp.resultType());
115             } else {
116                 exit = block.block();
117             }
118             if (swOp instanceof JavaOp.SwitchExpressionOp) {
119                 exit.context().mapValue(swOp.result(), exit.parameters().get(0));
120             }
121         }
122 
123         BranchTarget.setBranchTarget(block.context(), swOp, exit, null);
124         // map statement body to nextExprBlock
125         // this mapping will be used for lowering SwitchFallThroughOp
126         for (int i = 0; i < targets.size() - 1; i++) {
127             BranchTarget.setBranchTarget(block.context(), targets.get(i).parent(), null, blocks.get(i + 1));
128         }
129 
130         for (int i = 0; i < targets.size(); i++) {
131             Block.Builder curr = blocks.get(i);
132             curr.transformBody(targets.get(i).parent(), blocks.get(i).parameters(), (b, op) -> switch (op) {
133                 case YieldOp _ when swOp instanceof JavaOp.SwitchStatementOp -> {
134                     b.add(branch(exit.reference()));
135                     yield b;
136                 }
137                 case YieldOp yop when swOp instanceof JavaOp.SwitchExpressionOp -> {
138                     b.add(branch(exit.reference(b.context().getValue(yop.yieldValue()))));
139                     yield b;
140                 }
141                 default -> transformer.acceptOp(b, op);
142             });
143         }
144 
145         Value selector = block.context().getValue(swOp.operands().get(0));
146         if (integralReferenceTypes.contains(selector.type())) {
147             // unbox selector
148             if (selector.type().equals(J_L_CHARACTER)) {
149                 selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "charValue", JavaType.CHAR), selector));
150             } else {
151                 selector = block.add(JavaOp.invoke(MethodRef.method(selector.type(), "intValue", JavaType.INT), selector));
152             }
153         }
154         var labels = labelsAndTargets.labels();
155         if (!labels.contains(null)) {
156             // implicit default to exit
157             labels.add(null);
158             blocks.add(exit);
159         }
160         block.add(new ConstantLabelSwitchOp(selector, labels, blocks.stream().map(Block.Builder::reference).toList()));
161         return exit;
162     }
163 
164     public record LabelsAndTargets(List<Integer> labels, List<Block> targets) {}
165 
166     // @@@ should we add these functionalities to the API ?
167     private static final Set<ClassType> integralReferenceTypes = Set.of(J_L_BYTE, J_L_CHARACTER, J_L_SHORT, J_L_INTEGER);
168     private static final Set<PrimitiveType> integralPrimitiveTypes = Set.of(BYTE, CHAR, SHORT, INT);
169 
170     public static Optional<LabelsAndTargets> isCaseConstantSwitchWithIntegralSelector(JavaOp.JavaSwitchOp swOp, MethodHandles.Lookup lookup) {
171         //@@@ we only check for case constant switch that has integral type
172         // so labels in model / source code will be identical to the one we will find in bytecode
173         Value selector = swOp.operands().get(0);
174         if (!integralPrimitiveTypes.contains(selector.type()) && !integralReferenceTypes.contains(selector.type())) {
175             return Optional.empty();
176         }
177         var labels = new ArrayList<Integer>();
178         var targets = new ArrayList<Block>();
179         for (int i = 0; i < swOp.bodies().size(); i += 2) {
180             Body label = swOp.bodies().get(i);
181             List<Integer> ls = isCaseConstantLabel(lookup, label);
182             if (ls.isEmpty()) {
183                 return Optional.empty();
184             }
185             labels.addAll(ls);
186             targets.addAll(Collections.nCopies(ls.size(), swOp.bodies().get(i + 1).entryBlock()));
187         }
188         return Optional.of(new LabelsAndTargets(labels, targets));
189     }
190 
191     private static List<Integer> isCaseConstantLabel(MethodHandles.Lookup l, Body label) {
192         List<Integer> empty = new ArrayList<>();
193         if (label.blocks().size() != 1 || !(label.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
194                 !(yop.yieldValue() instanceof Op.Result r)) {
195             return empty;
196         }
197         List<Integer> labels = new ArrayList<>();
198         // we can yield a list
199         MethodRef objectsEquals = MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class);
200         switch (r.op()) {
201             case JavaOp.EqOp eqOp -> {
202                 Optional<Object> v = JavaOp.JavaExpression.evaluate(l, eqOp.operands().getLast());
203                 v.ifPresent(o -> labels.add(toInteger(o)));
204             }
205             case JavaOp.InvokeOp ie when ie.invokeReference().equals(objectsEquals) -> {
206                 Optional<Object> v = JavaOp.JavaExpression.evaluate(l, ie.operands().getLast());
207                 v.ifPresent(o -> labels.add(toInteger(o)));
208             }
209             case JavaOp.ConditionalOrOp cor -> {
210                 for (Body corb : cor.bodies()) {
211                     List<Integer> corbl = isCaseConstantLabel(l, corb);
212                     if (corbl.isEmpty()) {
213                         return empty;
214                     }
215                     labels.addAll(corbl);
216                 }
217             }
218             case CoreOp.ConstantOp cop when cop.value() instanceof Boolean b && b -> {
219                 labels.add(null);
220             }
221             default -> {
222             }
223         }
224         return labels;
225     }
226 
227     private static Integer toInteger(Object o) {
228         return switch (o) {
229             case Byte b -> Integer.valueOf(b);
230             case Short s -> Integer.valueOf(s);
231             case Character c -> Integer.valueOf(c);
232             case Integer i -> i;
233             // @@@ should not reach here
234             default -> throw new IllegalArgumentException("Object of type " + o.getClass().getName() + " can't be cast to Integer");
235         };
236     }
237 }