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