1 /*
2 * Copyright (c) 2025, 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.lang.reflect.AccessFlag;
29 import java.lang.reflect.Field;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Set;
35 import jdk.incubator.code.Block;
36 import jdk.incubator.code.Body;
37 import jdk.incubator.code.CodeTransformer;
38 import jdk.incubator.code.Op;
39 import jdk.incubator.code.TypeElement;
40 import jdk.incubator.code.Value;
41 import jdk.incubator.code.bytecode.BytecodeGenerator;
42 import jdk.incubator.code.dialect.core.CoreOp;
43 import jdk.incubator.code.dialect.core.CoreType;
44 import jdk.incubator.code.dialect.java.JavaOp;
45 import jdk.incubator.code.dialect.java.MethodRef;
46 import jdk.incubator.code.dialect.java.PrimitiveType;
47 import jdk.incubator.code.internal.BranchTarget;
48
49 import static jdk.incubator.code.dialect.core.CoreOp.YieldOp;
50 import static jdk.incubator.code.dialect.core.CoreOp.branch;
51 import jdk.incubator.code.dialect.java.JavaType;
52 import static jdk.incubator.code.dialect.java.JavaType.*;
53
54 /**
55 * Lowering transformer generates models supported by {@code BytecodeGenerator}.
56 * Constant-labeled switch statements and switch expressions are lowered to
57 * {@code ConstantLabelSwitchOp} with evaluated labels.
58 */
59 public final class LoweringTransform {
60
61 private static Block.Builder lowerToConstantLabelSwitchOp(Block.Builder block, CodeTransformer transformer,
62 JavaOp.JavaSwitchOp swOp, LabelsAndTargets labelsAndTargets) {
63 List<Block> targets = labelsAndTargets.targets();
64 List<Block.Builder> blocks = new ArrayList<>();
65 for (int i = 0; i < targets.size(); i++) {
66 Block.Builder bb = block.block();
67 blocks.add(bb);
68 }
69
70 Block.Builder exit;
71 if (targets.isEmpty()) {
72 exit = block;
73 } else {
74 if (swOp.resultType() != VOID) {
75 exit = block.block(swOp.resultType());
76 } else {
77 exit = block.block();
78 }
79 if (swOp instanceof JavaOp.SwitchExpressionOp) {
80 exit.context().mapValue(swOp.result(), exit.parameters().get(0));
81 }
82 }
83
84 BranchTarget.setBranchTarget(block.context(), swOp, exit, null);
85 // map statement body to nextExprBlock
86 // this mapping will be used for lowering SwitchFallThroughOp
87 for (int i = 0; i < targets.size() - 1; i++) {
88 BranchTarget.setBranchTarget(block.context(), targets.get(i).parent(), null, blocks.get(i+1));
89 }
90
91 for (int i = 0; i < targets.size(); i++) {
92 Block.Builder curr = blocks.get(i);
93 curr.body(targets.get(i).parent(), blocks.get(i).parameters(), (b, op) -> switch (op) {
94 case YieldOp _ when swOp instanceof JavaOp.SwitchStatementOp -> {
95 b.op(branch(exit.successor()));
96 yield b;
97 }
98 case YieldOp yop when swOp instanceof JavaOp.SwitchExpressionOp -> {
99 b.op(branch(exit.successor(b.context().getValue(yop.yieldValue()))));
100 yield b;
101 }
102 default -> transformer.acceptOp(b, op);
103 });
104 }
105
106 Value selector = block.context().getValue(swOp.operands().get(0));
107 if (ConstantLabelSwitchChecker.isIntegralReferenceType(selector.type())) {
108 // unbox selector
109 if (selector.type().equals(J_L_CHARACTER)) {
110 selector = block.op(JavaOp.invoke(MethodRef.method(selector.type(), "charValue", JavaType.CHAR), selector));
111 } else {
112 selector = block.op(JavaOp.invoke(MethodRef.method(selector.type(), "intValue", JavaType.INT), selector));
113 }
114 }
115 var labels = labelsAndTargets.labels();
116 if (!labels.contains(null)) {
117 // implicit default to exit
118 labels.add(null);
119 blocks.add(exit);
120 }
121 block.op(new ConstantLabelSwitchOp(selector, labels, blocks.stream().map(Block.Builder::successor).toList()));
122 return exit;
123 }
124
125 public static CodeTransformer getInstance(MethodHandles.Lookup lookup) {
126 return (block, op) -> switch (op) {
127 case JavaOp.JavaSwitchOp swOp when new ConstantLabelSwitchChecker(swOp, lookup).isCaseConstantSwitch() -> {
128 LabelsAndTargets labelsAndTargets = getLabelsAndTargets(lookup, swOp);
129 yield lowerToConstantLabelSwitchOp(block, block.transformer(), swOp, labelsAndTargets);
130 }
131 case Op.Lowerable lop -> lop.lower(block, null);
132 default -> {
133 block.op(op);
134 yield block;
135 }
136 };
137 }
138
139 public static final class ConstantLabelSwitchChecker {
140 private final MethodHandles.Lookup lookup;
141 private JavaOp.JavaSwitchOp swOp;
142
143 public ConstantLabelSwitchChecker(JavaOp.JavaSwitchOp swOp, MethodHandles.Lookup lookup) {
144 this.swOp = swOp;
145 this.lookup = lookup;
146 }
147
148 private static boolean isFinalVar(CoreOp.VarOp varOp) {
149 return varOp.initOperand() != null && varOp.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
150 }
151
152 private static boolean isBoxingMethod(MethodRef mr) {
153 return List.of(J_L_BYTE, J_L_CHARACTER, J_L_SHORT, J_L_INTEGER, J_L_LONG, J_L_FLOAT, J_L_DOUBLE).contains(mr.refType())
154 && mr.name().equals("valueOf");
155 }
156
157 private static boolean isIntegralType(TypeElement te) {
158 return isIntegralPrimitiveType(te) || isIntegralReferenceType(te);
159 }
160
161 private static boolean isIntegralPrimitiveType(TypeElement te) {
162 return List.of(BYTE, SHORT, CHAR, INT).contains(te);
163 }
164
165 private static boolean isIntegralReferenceType(TypeElement te) {
166 return List.of(J_L_BYTE, J_L_SHORT, J_L_CHARACTER, J_L_INTEGER).contains(te);
167 }
168
169 private boolean isConstantExpr(Value v) {
170 if (!(v instanceof Op.Result opr)) {
171 return false;
172 }
173 return switch (opr.op()) {
174 case CoreOp.ConstantOp cop ->
175 cop.resultType() instanceof PrimitiveType || cop.resultType().equals(J_L_STRING);
176 case CoreOp.VarAccessOp.VarLoadOp varLoadOp ->
177 isFinalVar(varLoadOp.varOp()) && isConstantExpr(varLoadOp.varOp().initOperand());
178 case JavaOp.ConvOp convOp ->
179 (convOp.resultType() instanceof PrimitiveType || convOp.resultType().equals(J_L_STRING)) &&
180 isConstantExpr(convOp.operands().get(0));
181 case JavaOp.InvokeOp invokeOp ->
182 isBoxingMethod(invokeOp.invokeDescriptor()) && isConstantExpr(invokeOp.operands().get(0));
183 case JavaOp.FieldAccessOp.FieldLoadOp fieldLoadOp -> {
184 Field field;
185 try {
186 field = fieldLoadOp.fieldDescriptor().resolveToField(lookup);
187 } catch (ReflectiveOperationException e) {
188 throw new RuntimeException(e);
189 }
190 yield field.isEnumConstant() || field.accessFlags().containsAll(Set.of(AccessFlag.STATIC, AccessFlag.FINAL));
191 }
192 case JavaOp.UnaryOp unaryOp -> isConstantExpr(unaryOp.operands().get(0));
193 case JavaOp.BinaryOp binaryOp -> binaryOp.operands().stream().allMatch(this::isConstantExpr);
194 case JavaOp.BinaryTestOp binaryTestOp ->
195 binaryTestOp.operands().stream().allMatch(this::isConstantExpr);
196 case JavaOp.ConditionalExpressionOp cexpr -> // bodies must yield constant expressions
197 isConstantExpr(((YieldOp) cexpr.bodies().get(0).entryBlock().terminatingOp()).yieldValue()) &&
198 isConstantExpr(((YieldOp) cexpr.bodies().get(1).entryBlock().terminatingOp()).yieldValue()) &&
199 isConstantExpr(((YieldOp) cexpr.bodies().get(2).entryBlock().terminatingOp()).yieldValue());
200
201 case JavaOp.ConditionalAndOp cand ->
202 isConstantExpr(((YieldOp) cand.bodies().get(0).entryBlock().terminatingOp()).yieldValue()) &&
203 isConstantExpr(((YieldOp) cand.bodies().get(1).entryBlock().terminatingOp()).yieldValue());
204 case JavaOp.ConditionalOrOp cor ->
205 // we can have a method isBodyYieldConstantExpr(Body)
206 isConstantExpr(((YieldOp) cor.bodies().get(0).entryBlock().terminatingOp()).yieldValue()) &&
207 isConstantExpr(((YieldOp) cor.bodies().get(1).entryBlock().terminatingOp()).yieldValue());
208 default -> false;
209 };
210 }
211
212 private boolean isCaseConstantLabel(Body label) {
213 if (label.blocks().size() != 1 || !(label.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
214 !(yop.yieldValue() instanceof Op.Result r)) {
215 return false;
216 }
217
218 // EqOp for primitives, method invocation for Strings and Reference Types
219 return switch (r.op()) {
220 case JavaOp.EqOp eqOp -> isConstantExpr(eqOp.operands().get(1));
221 case JavaOp.InvokeOp invokeOp when !invokeOp.invokeDescriptor().equals(OBJECTS_EQUALS) -> false;
222 case JavaOp.InvokeOp invokeOp -> {
223 // case null
224 if (invokeOp.operands().get(1) instanceof Op.Result opr && opr.op() instanceof CoreOp.ConstantOp cop && cop.value() == null) {
225 yield false;
226 }
227 yield isConstantExpr(invokeOp.operands().get(1));
228 }
229 case JavaOp.ConditionalOrOp cor -> cor.bodies().stream().allMatch(b -> isCaseConstantLabel(b));
230 default -> r.op() instanceof CoreOp.ConstantOp cop && cop.resultType().equals(BOOLEAN);
231 };
232 }
233
234 public boolean isCaseConstantSwitch() {
235 if (!isIntegralType(swOp.operands().get(0).type())) {
236 return false;
237 }
238 for (int i = 0; i < swOp.bodies().size(); i+=2) {
239 Body label = swOp.bodies().get(i);
240 if (!isCaseConstantLabel(label)) {
241 return false;
242 }
243 }
244 return true;
245 }
246 }
247
248 record LabelsAndTargets(List<Integer> labels, List<Block> targets) {}
249
250 static LabelsAndTargets getLabelsAndTargets(MethodHandles.Lookup lookup, JavaOp.JavaSwitchOp swOp) {
251 var labels = new ArrayList<Integer>();
252 var targets = new ArrayList<Block>();
253 for (int i = 0; i < swOp.bodies().size() - 1; i += 2) {
254 List<Integer> ls = getLabels(lookup, swOp.bodies().get(i));
255 labels.addAll(ls);
256 // getLabels returns list with null, for case default
257 targets.addAll(Collections.nCopies(ls.size(), swOp.bodies().get(i + 1).entryBlock()));
258 }
259 return new LabelsAndTargets(labels, targets);
260 }
261
262 static final MethodRef OBJECTS_EQUALS = MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class);
263
264 static List<Integer> getLabels(MethodHandles.Lookup lookup, Body body) {
265 if (body.blocks().size() != 1 || !(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
266 !(yop.yieldValue() instanceof Op.Result opr)) {
267 throw new IllegalStateException("Body of a java switch fails the expected structure");
268 }
269 var labels = new ArrayList<Integer>();
270 switch (opr.op()) {
271 case JavaOp.EqOp eqOp -> labels.add(extractConstantLabel(lookup, body, eqOp));
272 case JavaOp.InvokeOp invokeOp when invokeOp.invokeDescriptor().equals(OBJECTS_EQUALS) ->
273 labels.add(extractConstantLabel(lookup, body, invokeOp));
274 case JavaOp.ConditionalOrOp cor -> {
275 for (Body corbody : cor.bodies()) {
276 labels.addAll(getLabels(lookup, corbody));
277 }
278 }
279 case CoreOp.ConstantOp constantOp -> // default label
280 labels.add(null);
281 case null, default -> throw new IllegalStateException();
282 }
283 return labels;
284 }
285
286 static Integer extractConstantLabel(MethodHandles.Lookup lookup, Body body, Op whenToStop) {
287 Op lastOp = body.entryBlock().ops().get(body.entryBlock().ops().indexOf(whenToStop) - 1);
288 CoreOp.FuncOp funcOp = CoreOp.func("f", CoreType.functionType(lastOp.result().type())).body(block -> {
289 // in case we refer to constant variables in the label
290 for (Value capturedValue : body.capturedValues()) {
291 if (!(capturedValue instanceof Op.Result r) || !(r.op() instanceof CoreOp.VarOp vop)) {
292 continue;
293 }
294 Op cop = ((Op.Result) vop.initOperand()).op();
295 if (cop instanceof JavaOp.ConvOp) {
296 // converted constant
297 block.op(((Op.Result)cop.operands().getFirst()).op());
298 }
299 block.op(cop);
300 block.op(vop);
301 }
302 Op.Result last = null;
303 for (Op op : body.entryBlock().ops()) {
304 if (op.equals(whenToStop)) {
305 break;
306 }
307 last = block.op(op);
308 }
309 block.op(CoreOp.return_(last));
310 });
311 Object res;
312 try {
313 // @@@ workaround until JDK-8376974 Evaluate constant expressions
314 // Exposed evaluation of constant expressions should simplify this process.
315 // Then we can expose the computation of constant case values on the switch ops.
316 res = BytecodeGenerator.generate(lookup, funcOp.transform(CodeTransformer.LOWERING_TRANSFORMER)).invoke();
317 } catch (Throwable t) {
318 throw new IllegalStateException(t);
319 }
320 return switch (res) {
321 case Byte b -> Integer.valueOf(b);
322 case Short s -> Integer.valueOf(s);
323 case Character c -> Integer.valueOf(c);
324 case Integer i -> i;
325 default -> throw new IllegalStateException(); // @@@ not going to happen
326 };
327 }
328 }