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