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 }