1 /*
  2  * Copyright (c) 2024, 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 
 26 import java.lang.classfile.TypeKind;
 27 import jdk.incubator.code.Block;
 28 import jdk.incubator.code.Body;
 29 import jdk.incubator.code.CodeElement;
 30 import jdk.incubator.code.CopyContext;
 31 import jdk.incubator.code.Op;
 32 import jdk.incubator.code.TypeElement;
 33 import jdk.incubator.code.Value;
 34 import jdk.incubator.code.dialect.core.CoreOp;
 35 import jdk.incubator.code.dialect.java.JavaOp;
 36 import jdk.incubator.code.dialect.java.JavaType;
 37 import java.util.ArrayDeque;
 38 import java.util.ArrayList;
 39 import java.util.BitSet;
 40 import java.util.Deque;
 41 import java.util.HashSet;
 42 import java.util.IdentityHashMap;
 43 import java.util.Iterator;
 44 import java.util.List;
 45 import java.util.Map;
 46 import java.util.NoSuchElementException;
 47 import java.util.Set;
 48 import java.util.function.Consumer;
 49 import java.util.function.Function;
 50 
 51 /**
 52  *
 53  */
 54 final class SlotToVarTransformer {
 55 
 56     static final class Var {
 57         boolean single;
 58         TypeKind typeKind;
 59         Value value;
 60         Body parentBody;
 61     }
 62 
 63     record ExcStackMap(List<Block> catchBlocks, Map<Block, BitSet> map) implements Function<Block, ExcStackMap> {
 64         @Override
 65         public ExcStackMap apply(Block b) {
 66             BitSet excStack = map.computeIfAbsent(b, _ -> new BitSet());
 67             switch (b.terminatingOp()) {
 68                 case JavaOp.ExceptionRegionEnter ere -> {
 69                     BitSet entries = new BitSet();
 70                     for (Block.Reference cbr : ere.catchBlocks()) {
 71                         Block cb = cbr.targetBlock();
 72                         int i = catchBlocks.indexOf(cb);
 73                         if (i < 0) {
 74                             i = catchBlocks.size();
 75                             catchBlocks.add(cb);
 76                             map.put(cb, excStack);
 77                         }
 78                         entries.set(i);
 79                     }
 80                     entries.or(excStack);
 81                     map.put(ere.start().targetBlock(), entries);
 82                 }
 83                 case JavaOp.ExceptionRegionExit ere -> {
 84                     excStack = (BitSet) excStack.clone();
 85                     for (Block.Reference cbr : ere.catchBlocks()) {
 86                         excStack.clear(catchBlocks.indexOf(cbr.targetBlock()));
 87                     }
 88                     map.put(ere.end().targetBlock(), excStack);
 89                 }
 90                 case Op op -> {
 91                     for (Block.Reference tbr : op.successors()) {
 92                         map.put(tbr.targetBlock(), excStack);
 93                     }
 94                 }
 95             }
 96             return this;
 97         }
 98 
 99         void forEachHandler(Block b, Consumer<Block> hbc) {
100             map.get(b).stream().mapToObj(catchBlocks::get).forEach(hbc);
101         }
102 
103         void forEachTryBlock(Block hb, Consumer<Block> bc) {
104             int i = catchBlocks.indexOf(hb);
105             if (i >= 0) {
106                 for (var me : map.entrySet()) {
107                     if (me.getValue().get(i)) bc.accept(me.getKey());
108                 }
109             }
110         }
111     }
112 
113     static CoreOp.FuncOp transform(CoreOp.FuncOp func) {
114         try {
115             return new SlotToVarTransformer().convert(func);
116         } catch (Throwable t) {
117             System.out.println(func.toText());
118             throw t;
119         }
120     }
121 
122     private final Map<SlotOp, Var> varMap;
123 
124     private SlotToVarTransformer() {
125         varMap = new IdentityHashMap<>();
126     }
127 
128     private CoreOp.FuncOp convert(CoreOp.FuncOp func) {
129         // Composing exception stack map to be able to follow slot ops from try to the handler
130         ExcStackMap excMap = func.traverse(new ExcStackMap(new ArrayList<>(), new IdentityHashMap<>()),
131                 CodeElement.blockVisitor((map, b) -> map.apply(b)));
132 
133         List<Var> toInitialize = func.body().traverse(new ArrayList<>(), CodeElement.opVisitor((toInit, op) -> {
134             if (op instanceof SlotOp slotOp && !varMap.containsKey(slotOp)) {
135 
136                 // Assign variable to segments, calculate var slotType
137                 Var var = new Var(); // New variable
138                 var.parentBody = slotOp.ancestorBody();
139                 var q = new ArrayDeque<SlotOp>();
140                 var stores = new ArrayList<SlotOp.SlotStoreOp>();
141                 q.add(slotOp);
142                 while (!q.isEmpty()) {
143                     SlotOp se = q.pop();
144                     if (!varMap.containsKey(se)) {
145                         varMap.put(se, var); // Assign variable to the segment
146                         if (var.typeKind == null) var.typeKind = se.typeKind(); // TypeKind is identical for all SlotOps of the same variable
147                         for (SlotOp to : slotImmediateSuccessors(se, excMap)) {
148                             // All following SlotLoadOp belong to the same variable
149                             if (to instanceof SlotOp.SlotLoadOp) {
150                                 if (!varMap.containsKey(to)) {
151                                     q.add(to);
152                                 }
153                             }
154                         }
155                         if (se instanceof SlotOp.SlotLoadOp) {
156                             // Segments preceeding SlotLoadOp also belong to the same variable
157                             for (SlotOp from : slotImmediatePredecessors(se, excMap)) {
158                                 if (!varMap.containsKey(from)) {
159                                     q.add(from);
160                                 }
161                             }
162                         } else if (se instanceof SlotOp.SlotStoreOp store) {
163                             stores.add(store); // Collection of all SlotStoreOps of the variable
164                         }
165                     }
166                 }
167 
168                 // Single-assigned variable has only one SlotStoreOp
169                 var.single = stores.size() < 2;
170 
171                 // Identification of initial SlotStoreOp
172                 for (var it = stores.iterator(); it.hasNext();) {
173                     SlotOp s = it.next();
174                     if (isDominatedByTheSameVar(s, excMap)) {
175                         // A store preceeding dominantly with segments of the same variable is not initial
176                         it.remove();
177                     }
178                 }
179 
180                 // Remaining stores are all initial.
181                 if (stores.size() > 1) {
182                     // A synthetic default-initialized dominant segment must be inserted to the variable, if there is more than one initial store segment.
183                     // It is not necessary to link it with other variable segments, the analysys ends here.
184                     toInit.add(varMap.get(stores.getFirst()));
185                 }
186 
187 
188             }
189             return toInit;
190         }));
191 
192         return func.transform((block, op) -> {
193             if (!toInitialize.isEmpty()) {
194                 for (var it = toInitialize.iterator(); it.hasNext();) {
195                     Var var = it.next();
196                     if (var.parentBody == op.ancestorBody()) {
197                         var.value = block.op(CoreOp.var(toTypeElement(var.typeKind)));
198                         it.remove();
199                     }
200                 }
201             }
202             CopyContext cc = block.context();
203             switch (op) {
204                 case SlotOp.SlotLoadOp slo -> {
205                     Var var = varMap.get(slo);
206                     if (var.value == null) {
207                         System.out.println(slo);
208                         throw new AssertionError();
209                     }
210                     cc.mapValue(op.result(), var.single ? var.value : block.op(CoreOp.varLoad(var.value)));
211                 }
212                 case SlotOp.SlotStoreOp sso -> {
213                     Var var = varMap.get(sso);
214                     Value val = sso.operands().getFirst();
215                     val = cc.getValueOrDefault(val, val);
216                     if (var.single) {
217                         var.value = val;
218                     } else if (var.value == null) {
219                         TypeElement varType = switch (val.type()) {
220                             case UnresolvedType.Ref _ -> UnresolvedType.unresolvedRef();
221                             case UnresolvedType.Int _ -> UnresolvedType.unresolvedInt();
222                             default -> val.type();
223                         };
224                         var.value = block.op(CoreOp.var(null, varType, val));
225                     } else {
226                         block.op(CoreOp.varStore(var.value, val));
227                     }
228                 }
229                 default ->
230                     block.op(op);
231             }
232             return block;
233         });
234     }
235 
236     private static TypeElement toTypeElement(TypeKind tk) {
237         return switch (tk) {
238             case INT -> UnresolvedType.unresolvedInt();
239             case REFERENCE -> UnresolvedType.unresolvedRef();
240             case LONG -> JavaType.LONG;
241             case DOUBLE -> JavaType.DOUBLE;
242             case FLOAT -> JavaType.FLOAT;
243             case BOOLEAN -> JavaType.BOOLEAN;
244             case BYTE -> JavaType.BYTE;
245             case SHORT -> JavaType.SHORT;
246             case CHAR -> JavaType.CHAR;
247             case VOID -> throw new IllegalStateException("Unexpected void type.");
248         };
249     }
250 
251     // Traverse immediate same-slot successors of a SlotOp
252     private static Iterable<SlotOp> slotImmediateSuccessors(SlotOp slotOp, ExcStackMap excMap) {
253         return () -> new SlotOpIterator(slotOp, excMap, true);
254     }
255 
256     // Traverse immediate same-slot predecessors of a SlotOp
257     private static Iterable<SlotOp> slotImmediatePredecessors(SlotOp slotOp, ExcStackMap excMap) {
258         return () -> new SlotOpIterator(slotOp, excMap, false);
259     }
260 
261     private boolean isDominatedByTheSameVar(SlotOp slotOp, ExcStackMap excMap) {
262         Var var = varMap.get(slotOp);
263         Set<Op.Result> predecessors = new HashSet<>();
264         for (SlotOp pred : slotImmediatePredecessors(slotOp, excMap)) {
265             if (varMap.get(pred) != var) {
266                 return false;
267             }
268             if (pred != slotOp) predecessors.add(pred.result());
269         }
270         return isDominatedBy(slotOp.result(), predecessors);
271     }
272 
273     /**
274      * Returns {@code true} if this value is dominated by the given set of values {@code doms}.
275      * <p>
276      * The set dominates if every path from the entry node go through any member of the set.
277      * <p>
278      * First part checks individual dominance of every member of the set.
279      * <p>
280      * If no member of the set is individually dominant, the second part tries to find path
281      * to the entry block bypassing all blocks from the tested set.
282      * <p>
283      * Implementation searches for the paths by traversing the value declaring block predecessors,
284      * stopping at blocks where values from the tested set are declared or at blocks already processed.
285      * Negative test result is returned when the entry block is reached.
286      * Positive test result is returned when no path to the entry block is found.
287      *
288      * @param value the value
289      * @param doms the dominating set of values
290      * @return {@code true} if this value is dominated by the given set of values {@code dom}.
291      * @throws IllegalStateException if the declaring block is partially built
292      */
293     public static boolean isDominatedBy(Value value, Set<? extends Value> doms) {
294         if (doms.isEmpty()) {
295             return false;
296         }
297 
298         for (Value dom : doms) {
299             if (value.isDominatedBy(dom)) {
300                 return true;
301             }
302         }
303 
304         Set<Block> stopBlocks = new HashSet<>();
305         for (Value dom : doms) {
306             stopBlocks.add(dom.declaringBlock());
307         }
308 
309         Deque<Block> toProcess = new ArrayDeque<>();
310         toProcess.add(value.declaringBlock());
311         stopBlocks.add(value.declaringBlock());
312         while (!toProcess.isEmpty()) {
313             for (Block b : toProcess.pop().predecessors()) {
314                 if (b.isEntryBlock()) {
315                     return false;
316                 }
317                 if (stopBlocks.add(b)) {
318                     toProcess.add(b);
319                 }
320             }
321         }
322         return true;
323     }
324 
325     static final class SlotOpIterator implements Iterator<SlotOp> {
326 
327         SlotOp op;
328         final int slot;
329         final ExcStackMap map;
330         final TypeKind tk;
331         final boolean fwd;
332         Block b;
333         List<Op> ops;
334         int i;
335         BitSet visited;
336         ArrayDeque<Block> toVisit;
337 
338 
339         public SlotOpIterator(SlotOp slotOp, ExcStackMap excMap, boolean forward) {
340             slot = slotOp.slot;
341             tk = slotOp.typeKind();
342             map = excMap;
343             fwd = forward;
344             b = slotOp.ancestorBlock();
345             ops = fwd ? b.ops() : b.ops().reversed();
346             i = ops.indexOf(slotOp) + 1;
347         }
348 
349         @Override
350         public boolean hasNext() {
351             while (hasNextSlot()) {
352                 // filter loads and stores of the same TypeKind (if known)
353                 if (op.typeKind() == tk || op.typeKind() == null || tk == null) {
354                     return true;
355                 }
356                 op = null;
357             }
358             return false;
359         }
360 
361         private boolean hasNextSlot() {
362             if (op != null) {
363                 return true;
364             } else {
365                 while (b != null || toVisit != null && !toVisit.isEmpty()) {
366                     if (b == null) {
367                         b = toVisit.pop();
368                         ops = fwd ? b.ops() : b.ops().reversed();
369                         i = 0;
370                     }
371                     while (i < ops.size()) {
372                         if (ops.get(i++) instanceof SlotOp sop && sop.slot == slot) {
373                             op = sop;
374                             b = null;
375                             return true;
376                         }
377                     }
378                     if (toVisit == null) {
379                         toVisit = new ArrayDeque<>();
380                         visited = new BitSet();
381                     }
382                     if (fwd) {
383                         for (Block.Reference sr : b.successors()) {
384                             Block sb = sr.targetBlock();
385                             if (!visited.get(sb.index())) {
386                                 toVisit.add(sb);
387                                 visited.set(sb.index());
388                             }
389                         }
390                         // Visit also relevant exception handlers
391                         map.forEachHandler(b, sb -> {
392                             if (!visited.get(sb.index())) {
393                                 toVisit.add(sb);
394                                 visited.set(sb.index());
395                             }
396                         });
397                     } else {
398                         for (Block pb : b.predecessors()) {
399                             if (!visited.get(pb.index())) {
400                                 toVisit.add(pb);
401                                 visited.set(pb.index());
402                             }
403                         }
404                         // Visit also relevant try blocks from handler
405                         map.forEachTryBlock(b, sb -> {
406                             if (!visited.get(sb.index())) {
407                                 toVisit.add(sb);
408                                 visited.set(sb.index());
409                             }
410                         });
411                     }
412                     b = null;
413                 }
414                 return false;
415             }
416         }
417 
418         @Override
419         public SlotOp next() {
420             if (!hasNext()) throw new NoSuchElementException();
421             SlotOp ret = op;
422             op = null;
423             return ret;
424         }
425     }
426 }