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