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