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