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 }