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 package java.lang.reflect.code.bytecode;
 26 
 27 import java.lang.classfile.CodeElement;
 28 import java.lang.classfile.Instruction;
 29 import java.lang.classfile.Label;
 30 import java.lang.classfile.Opcode;
 31 import java.lang.classfile.attribute.StackMapFrameInfo;
 32 import java.lang.classfile.attribute.StackMapFrameInfo.*;
 33 import java.lang.classfile.attribute.StackMapTableAttribute;
 34 import java.lang.classfile.instruction.*;
 35 import java.lang.constant.ClassDesc;
 36 import java.lang.constant.ConstantDescs;
 37 import java.lang.constant.DirectMethodHandleDesc;
 38 import java.lang.constant.DynamicConstantDesc;
 39 import java.lang.constant.MethodTypeDesc;
 40 import java.util.ArrayDeque;
 41 import java.util.ArrayList;
 42 import java.util.HashMap;
 43 import java.util.Iterator;
 44 import java.util.LinkedHashSet;
 45 import java.util.List;
 46 import java.util.Map;
 47 import java.util.NoSuchElementException;
 48 import java.util.Set;
 49 import java.util.Optional;
 50 import java.util.stream.Collectors;
 51 
 52 import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*;
 53 import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.NULL;
 54 import static java.lang.constant.ConstantDescs.*;
 55 
 56 /**
 57  * LocalsToVarMapper scans bytecode for slot operations, forms oriented flow graphs of the slot operation segments,
 58  * analyzes the graphs and maps the segments to distinct variables, calculates each variable type and identifies
 59  * single-assigned variables and variables requiring initialization in the entry block.
 60  */
 61 final class LocalsToVarMapper {
 62 
 63     /**
 64      * Variable identity object result of the LocalsToVarMapper analysis.
 65      */
 66     public static final class Variable {
 67         private ClassDesc type;
 68         private boolean single;
 69 
 70         /**
 71          * {@return Variable type}
 72          */
 73         ClassDesc type() {
 74             return type;
 75         }
 76 
 77         /**
 78          * {@return whether the variable has only single assignement}
 79          */
 80         boolean hasSingleAssignment() {
 81             return single;
 82         }
 83     }
 84 
 85     /**
 86      * Segment of bytecode related to one local slot, it represents a node in the segment graph.
 87      */
 88     private static final class Segment {
 89 
 90         /**
 91          * Categorization of the segment graph nodes.
 92          */
 93         enum Kind {
 94 
 95             /**
 96              * Segment storing a value into the local slot.
 97              */
 98             STORE,
 99 
100             /**
101              * Segment requesting to load value from the local slot.
102              */
103             LOAD,
104 
105             /**
106              * Segment forming a frame of connection to other segments.
107              * This kind of segment is later either resolved as LOAD or it identifies false connection.
108              */
109             FRAME;
110         }
111 
112         /**
113          * Link between segments.
114          */
115         record Link(Segment segment, Link other) {}
116 
117         /**
118          * Kind of segment.
119          * The value is not final, {@link Kind.FRAME} segments may be later resolved to {@link Kind.LOAD}.
120          */
121         Kind kind;
122 
123         /**
124          * Segment type.
125          * The value is not final, int type may be later changed to {@code boolean}, {@code byte}, {@code short} or {@code char}.
126          */
127         ClassDesc type;
128 
129         /**
130          * Variable this segment belongs to.
131          * The value is calculated later in the process.
132          */
133         Variable var;
134 
135 
136         /**
137          * Incoming segments in the flow graph.
138          */
139         Link from;
140 
141         /**
142          * Outgoing segments in the flow graph.
143          */
144         Link to;
145 
146         /**
147          * Links this segment to an outgoing segment.
148          * @param toSegment outgoing segment
149          */
150         void link(Segment toSegment) {
151             if (this != toSegment) {
152                 toSegment.from = new Link(this, toSegment.from);
153                 this.to = new Link(toSegment, this.to);
154             }
155         }
156 
157         /**
158          * {@return Iterable over incomming segments.}
159          */
160         Iterable<Segment> fromSegments() {
161             return () -> new LinkIterator(from);
162         }
163 
164         /**
165          * {@return Iterable over outgoing segments.}
166          */
167         Iterable<Segment> toSegments() {
168             return () -> new LinkIterator(to);
169         }
170 
171         private static final class LinkIterator implements Iterator<Segment> {
172             Link l;
173             public LinkIterator(Link l) {
174                 this.l = l;
175             }
176 
177             @Override
178             public boolean hasNext() {
179                 return l != null;
180             }
181 
182             @Override
183             public Segment next() {
184                 if (l == null) throw new NoSuchElementException();
185                 Segment s = l.segment();
186                 l = l.other();
187                 return s;
188             }
189         }
190     }
191 
192     /**
193      * Stack map frame
194      */
195     private record Frame(List<ClassDesc> stack, List<Segment> locals) {}
196 
197     /**
198      * Specific instance of CD_Object identifying null initialized objects.
199      */
200     private static final ClassDesc NULL_TYPE = ClassDesc.ofDescriptor(CD_Object.descriptorString());
201 
202     /**
203      * Map from instruction index to a segment.
204      */
205     private final Map<Integer, Segment> insMap;
206 
207     /**
208      * Set of all involved segments.
209      */
210     private final LinkedHashSet<Segment> allSegments;
211 
212     /**
213      * This class descriptor.
214      */
215     private final ClassDesc thisClass;
216 
217     /**
218      * All exception handlers.
219      */
220     private final List<ExceptionCatch> exceptionHandlers;
221 
222     /**
223      * Actual exception handlers stack.
224      */
225     private final Set<ExceptionCatch> handlersStack;
226 
227     /**
228      * Actual stack.
229      */
230     private final List<ClassDesc> stack;
231 
232     /**
233      * Actual locals.
234      */
235     private final List<Segment> locals;
236 
237     /**
238      * Stack map.
239      */
240     private final Map<Label, Frame> stackMap;
241 
242     /**
243      * Map of new object types (to resolve unitialized verification types in the stack map).
244      */
245     private final Map<Label, ClassDesc> newMap;
246 
247     /**
248      * Dirty flag indicates modified stack map frame (sub-int adjustments), so the scanning process must restart
249      */
250     private boolean frameDirty;
251 
252     /**
253      * Initial set of slots. Static part comes from method arguments.
254      * Later phase of the analysis adds synthetic slots (declarations of multiple-assigned variables)
255      * with mandatory initialization in the entry block.
256      */
257     private final List<Segment> initSlots;
258 
259     /**
260      * Constructor and executor of the LocalsToVarMapper.
261      * @param thisClass This class descriptor.
262      * @param initFrameLocals Entry frame locals, expanded form of the method receiver and arguments. Second positions of double slots are null.
263      * @param exceptionHandlers Exception handlers.
264      * @param stackMapTableAttribute Stack map table attribute.
265      * @param codeElements Code elements list. Indexes of this list are keys to the {@link #instructionVar(int) } method.
266      */
267     public LocalsToVarMapper(ClassDesc thisClass,
268                      List<ClassDesc> initFrameLocals,
269                      List<ExceptionCatch> exceptionHandlers,
270                      Optional<StackMapTableAttribute> stackMapTableAttribute,
271                      List<CodeElement> codeElements) {
272         this.insMap = new HashMap<>();
273         this.thisClass = thisClass;
274         this.exceptionHandlers = exceptionHandlers;
275         this.handlersStack = new LinkedHashSet<>();
276         this.stack = new ArrayList<>();
277         this.locals = new ArrayList<>();
278         this.allSegments = new LinkedHashSet<>();
279         this.newMap = computeNewMap(codeElements);
280         this.initSlots = new ArrayList<>();
281         this.stackMap = stackMapTableAttribute.map(a -> a.entries().stream().collect(Collectors.toMap(
282                 StackMapFrameInfo::target,
283                 this::toFrame))).orElse(Map.of());
284         for (ClassDesc cd : initFrameLocals) {
285             initSlots.add(cd == null ? null : newSegment(cd, Segment.Kind.STORE));
286         }
287         int initSize = allSegments.size();
288 
289         // Main loop of the scan phase
290         do {
291             // Reset of the exception handler stack
292             handlersStack.clear();
293             // Slot states reset if running additional rounds (changed stack map frames)
294             if (allSegments.size() > initSize) {
295                 while (allSegments.size() > initSize) allSegments.removeLast();
296                 allSegments.forEach(sl -> {
297                     sl.from = null;
298                     sl.to = null;
299                     sl.var = null;
300                 });
301             }
302             // Initial frame store
303             for (int i = 0; i < initFrameLocals.size(); i++) {
304                 storeLocal(i, initSlots.get(i), locals);
305             }
306             this.frameDirty = false;
307             // Iteration over all code elements
308             for (int i = 0; i < codeElements.size(); i++) {
309                 var ce = codeElements.get(i);
310                 scan(i, ce);
311             }
312             endOfFlow();
313         } while (this.frameDirty);
314 
315         // Segment graph analysis phase
316         // First resolve FRAME segments to LOAD segments if directly followed by a LOAD segment
317         // Remaining FRAME segments do not form connection with segments of the same variable and will be ignored.
318         boolean changed = true;
319         while (changed) {
320             changed = false;
321             for (Segment segment : allSegments) {
322                 if (segment.kind == Segment.Kind.FRAME) {
323                     for (Segment to : segment.toSegments()) {
324                         if (to.kind == Segment.Kind.LOAD) {
325                             changed = true;
326                             segment.kind = Segment.Kind.LOAD;
327                             break;
328                         }
329                     }
330                 }
331             }
332         }
333 
334         // Assign variable to segments, calculate var type
335         Set<Segment> stores = new LinkedHashSet<>(); // Helper set to collect all STORE segments of a variable
336         ArrayDeque<Segment> q = new ArrayDeque<>(); // Working queue
337         Set<Segment> visited = new LinkedHashSet<>(); // Helper set to traverse segment graph to filter initial stores
338         for (Segment segment : allSegments) {
339             // Only STORE and LOAD segments without assigned var are computed
340             if (segment.var == null && segment.kind != Segment.Kind.FRAME) {
341                 Variable var = new Variable(); // New variable
342                 q.add(segment);
343                 var.type = segment.type; // Initial variable type
344                 while (!q.isEmpty()) {
345                     Segment se = q.pop();
346                     if (se.var == null) {
347                         se.var = var; // Assign variable to the segment
348                         for (Segment to : se.toSegments()) {
349                             // All following LOAD segments belong to the same variable
350                             if (to.kind == Segment.Kind.LOAD) {
351                                 if (var.type == NULL_TYPE) {
352                                     var.type = to.type; // Initially null type re-assignemnt
353                                 }
354                                 if (to.var == null) {
355                                     q.add(to);
356                                 }
357                             }
358                         }
359                         if (se.kind == Segment.Kind.LOAD) {
360                             // Segments preceeding LOAD segment also belong to the same variable
361                             for (Segment from : se.fromSegments()) {
362                                 if (from.kind != Segment.Kind.FRAME) { // FRAME segments are ignored
363                                     if (var.type == NULL_TYPE) {
364                                         var.type = from.type; // Initially null type re-assignemnt
365                                     }
366                                     if (from.var == null) {
367                                         q.add(from);
368                                     }
369                                 }
370                             }
371                         }
372                     }
373                     if (se.var == var && se.kind == Segment.Kind.STORE) {
374                         stores.add(se); // Collection of all STORE segments of the variable
375                     }
376                 }
377 
378                 // Single-assigned variable has only one STORE segment
379                 var.single = stores.size() < 2;
380 
381                 // Identification of initial STORE segments
382                 for (var it = stores.iterator(); it.hasNext();) {
383                     visited.clear();
384                     Segment s = it.next();
385                     if (s.from != null && varDominatesOverSegmentPredecessors(s, var, visited)) {
386                         // A store preceeding dominantly with segments of the same variable is not initial
387                         it.remove();
388                     }
389                 }
390 
391                 // Remaining stores are all initial.
392                 if (stores.size() > 1) {
393                     // A synthetic default-initialized dominant segment must be inserted to the variable, if there is more than one initial store segment.
394                     // It is not necessary to link it with other variable segments, the analysys ends here.
395                     Segment initialSegment = new Segment();
396                     initialSegment.var = var;
397                     initSlots.add(initialSegment);
398                     if (var.type == CD_long || var.type == CD_double) {
399                         initSlots.add(null); // Do not forget to alocate second slot for double slots.
400                     }
401                 }
402                 stores.clear();
403             }
404         }
405     }
406 
407     /**
408      * {@return Number of slots to initialize at entry block (method receiver + arguments + synthetic variable initialization segments).}
409      */
410     public int slotsToInit() {
411         return initSlots.size();
412     }
413 
414     /**
415      * {@return Variable related to the given initial slot or null}
416      * @param initSlot initial slot index
417      */
418     public Variable initSlotVar(int initSlot) {
419         Segment s = initSlots.get(initSlot);
420         return s == null ? null : s.var;
421     }
422 
423     /**
424      * Method returns relevant {@link Variable} for instructions operating with local slots,
425      * such as {@link LoadInstruction}, {@link StoreInstruction} and {@link IncrementInstruction}.
426      * For all other elements it returns {@code null}.
427      *
428      * Instructions are identified by index into the {@code codeElements} list used in the {@link LocalsToVarMapper} initializer.
429      *
430      * {@link IncrementInstruction} relates to two potentially distinct variables, one variable to load the value from
431      * and one variable to store the incremented value into (see: {@link BytecodeLift#liftBody() }).
432      *
433      * @param codeElementIndex code element index
434      * @return Variable related to the given code element index or null
435      */
436     public Variable instructionVar(int codeElementIndex) {
437         return insMap.get(codeElementIndex).var;
438     }
439 
440     /**
441      * Tests if variable dominates over the segment predecessors.
442      * All incoming paths to the segment must lead from segments of the given variable and not of any other variable.
443      * The paths may pass through {@code FRAME} segments, which do not belong to any variable and their dominance must be computed.
444      * Implementation relies on loops-avoiding breadth-first negative search.
445      */
446     private static boolean varDominatesOverSegmentPredecessors(Segment segment, Variable var, Set<Segment> visited) {
447         if (visited.add(segment)) {
448             for (Segment pred : segment.fromSegments()) {
449                 // Breadth-first
450                 if (pred.kind != Segment.Kind.FRAME && pred.var != var) {
451                     return false;
452                 }
453             }
454             for (Segment pred : segment.fromSegments()) {
455                 // Preceeding FRAME segment implies there is no directly preceeding variable and the dominance test must go deeper
456                 if (pred.kind == Segment.Kind.FRAME && !varDominatesOverSegmentPredecessors(pred, var, visited)) {
457                     return false;
458                 }
459             }
460         }
461         return true;
462     }
463 
464     /**
465      * Cconverts {@link StackMapFrameInfo} to {@code Frame}, where locals are expanded form ({@code null}-filled second slots for double-slots)
466      * of {@code FRAME} segments.
467      * @param smfi StackMapFrameInfo
468      * @return Frame
469      */
470     private Frame toFrame(StackMapFrameInfo smfi) {
471         List<ClassDesc> fstack = new ArrayList<>(smfi.stack().size());
472         List<Segment> flocals = new ArrayList<>(smfi.locals().size() * 2);
473         for (var vti : smfi.stack()) {
474             fstack.add(vtiToStackType(vti));
475         }
476         int i = 0;
477         for (var vti : smfi.locals()) {
478             storeLocal(i, vtiToStackType(vti), flocals, Segment.Kind.FRAME);
479             i += vti == DOUBLE || vti == LONG ? 2 : 1;
480         }
481         return new Frame(fstack, flocals);
482     }
483 
484     /**
485      * {@return map of labels immediately preceding {@link NewObjectInstruction} to the object types}
486      * The map is important to resolve unitialized verification types in the stack map.
487      * @param codeElements List of code elements to scan
488      */
489     private static Map<Label, ClassDesc> computeNewMap(List<CodeElement> codeElements) {
490         Map<Label, ClassDesc> newMap = new HashMap<>();
491         Label lastLabel = null;
492         for (int i = 0; i < codeElements.size(); i++) {
493             switch (codeElements.get(i)) {
494                 case LabelTarget lt -> lastLabel = lt.label();
495                 case NewObjectInstruction newI -> {
496                     if (lastLabel != null) {
497                         newMap.put(lastLabel, newI.className().asSymbol());
498                     }
499                 }
500                 case Instruction _ -> lastLabel = null; //invalidate label
501                 default -> {} //skip
502             }
503         }
504         return newMap;
505     }
506 
507     /**
508      * {@return new segment and registers it in {@code allSegments} list}
509      * @param type class descriptor of segment type
510      * @param kind one of the segment kinds: {@code STORE}, {@code LOAD} or {@code FRAME}
511      */
512     private Segment newSegment(ClassDesc type, Segment.Kind kind) {
513         Segment s = new Segment();
514         s.kind = kind;
515         s.type = type;
516         allSegments.add(s);
517         return s;
518     }
519 
520     /**
521      * {@return resolved class descriptor of the stack map frame verification type, custom {@code NULL_TYPE} for {@code ITEM_NULL}
522      * or {@code null} for {@code ITEM_TOP}}
523      * @param vti stack map frame verification type
524      */
525     private ClassDesc vtiToStackType(StackMapFrameInfo.VerificationTypeInfo vti) {
526         return switch (vti) {
527             case INTEGER -> CD_int;
528             case FLOAT -> CD_float;
529             case DOUBLE -> CD_double;
530             case LONG -> CD_long;
531             case UNINITIALIZED_THIS -> thisClass;
532             case NULL -> NULL_TYPE;
533             case ObjectVerificationTypeInfo ovti -> ovti.classSymbol();
534             case UninitializedVerificationTypeInfo uvti ->
535                 newMap.computeIfAbsent(uvti.newTarget(), l -> {
536                     throw new IllegalArgumentException("Unitialized type does not point to a new instruction");
537                 });
538             case TOP -> null;
539         };
540     }
541 
542     /**
543      * Pushes the class descriptor on {@link #stack}, except for {@code void}.
544      * @param type class descriptor
545      */
546     private void push(ClassDesc type) {
547         if (!ConstantDescs.CD_void.equals(type)) stack.add(type);
548     }
549 
550     /**
551      * Pushes the class descriptors on the {@link #stack} at the relative position, except for {@code void}.
552      * @param pos position relative to the stack tip
553      * @param types class descriptors
554      */
555     private void pushAt(int pos, ClassDesc... types) {
556         for (var t : types)
557             if (!ConstantDescs.CD_void.equals(t))
558                 stack.add(stack.size() + pos, t);
559     }
560 
561     /**
562      * {@return if class descriptor on the {@link #stack} at the relative position is {@code long} or {@code double}}
563      * @param pos position relative to the stack tip
564      */
565     private boolean doubleAt(int pos) {
566         var t  = stack.get(stack.size() + pos);
567         return t.equals(CD_long) || t.equals(CD_double);
568     }
569 
570     /**
571      * {@return class descriptor poped from the {@link #stack}}
572      */
573     private ClassDesc pop() {
574         return stack.removeLast();
575     }
576 
577     /**
578      * {@return class descriptor from the relative position of the {@link #stack}}
579      * @param pos position relative to the stack tip
580      */
581     private ClassDesc get(int pos) {
582         return stack.get(stack.size() + pos);
583     }
584 
585     /**
586      * {@return class descriptor from the tip of the {@link #stack}}
587      */
588     private ClassDesc top() {
589         return stack.getLast();
590     }
591 
592     /**
593      * {@return two class descriptors from the tip of the {@link #stack}}
594      */
595     private ClassDesc[] top2() {
596         return new ClassDesc[] {stack.get(stack.size() - 2), stack.getLast()};
597     }
598 
599     /**
600      * Pops given number of class descriptors from the {@link #stack}.
601      * @param i number of class descriptors to pop
602      * @return this LocalsToVarMapper
603      */
604     private LocalsToVarMapper pop(int i) {
605         while (i-- > 0) pop();
606         return this;
607     }
608 
609     /**
610      * Stores class descriptor as a new {@code STORE} {@link Segment} to the {@link #locals}.
611      * The new segment is linked with the previous segment on the same slot position (if any).
612      * @param slot locals slot number
613      * @param type new segment class descriptor
614      */
615     private void storeLocal(int slot, ClassDesc type) {
616         storeLocal(slot, type, locals, Segment.Kind.STORE);
617     }
618 
619     /**
620      * Stores class descriptor as a new {@link Segment} of given kind to the given list .
621      * The new segment is linked with the previous segment on the same slot position (if any).
622      * @param slot locals slot number
623      * @param type new segment class descriptor
624      * @param where target list of segments
625      * @param kind new segment kind
626      */
627     private void storeLocal(int slot, ClassDesc type, List<Segment> where, Segment.Kind kind) {
628         storeLocal(slot, type == null ? null : newSegment(type, kind), where);
629     }
630 
631     /**
632      * Stores the {@link Segment} to the given list.
633      * The new segment is linked with the previous segment on the same slot position (if any).
634      * @param slot locals slot number
635      * @param segment the segment to store
636      * @param where target list of segments
637      */
638     private void storeLocal(int slot, Segment segment, List<Segment> where) {
639         if (segment != null) {
640             for (int i = where.size(); i <= slot; i++) where.add(null);
641             Segment prev = where.set(slot, segment);
642             if (prev != null) {
643                 prev.link(segment);
644             }
645         }
646     }
647 
648     /**
649      * Links existing {@link Segment} of the {@link #locals} with a new {@code LOAD} {@link Segment} with inherited type.
650      * @param slot slot number to load
651      * @return type of the local
652      */
653     private ClassDesc loadLocal(int slot) {
654         Segment segment = locals.get(slot);
655         Segment newSegment = newSegment(segment.type, Segment.Kind.LOAD);
656         segment.link(newSegment);
657         return segment.type;
658     }
659 
660     /**
661      * Main code element scanning method of the scan loop.
662      * @param elementIndex element index
663      * @param el code element
664      */
665     private void scan(int elementIndex, CodeElement el) {
666         switch (el) {
667             case ArrayLoadInstruction _ ->
668                 pop(1).push(pop().componentType());
669             case ArrayStoreInstruction _ ->
670                 pop(3);
671             case BranchInstruction i -> {
672                 switch (i.opcode()) {
673                     case IFEQ, IFGE, IFGT, IFLE, IFLT, IFNE, IFNONNULL, IFNULL -> {
674                         pop();
675                         mergeToTargetFrame(i.target());
676                     }
677                     case IF_ACMPEQ, IF_ACMPNE, IF_ICMPEQ, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ICMPLT, IF_ICMPNE -> {
678                         pop(2);
679                         mergeToTargetFrame(i.target());
680                     }
681                     case GOTO, GOTO_W -> {
682                         mergeToTargetFrame(i.target());
683                         endOfFlow();
684                     }
685                 }
686             }
687             case ConstantInstruction i ->
688                 push(switch (i.constantValue()) {
689                     case null -> NULL_TYPE;
690                     case ClassDesc _ -> CD_Class;
691                     case Double _ -> CD_double;
692                     case Float _ -> CD_float;
693                     case Integer _ -> CD_int;
694                     case Long _ -> CD_long;
695                     case String _ -> CD_String;
696                     case DynamicConstantDesc<?> cd when cd.equals(NULL) -> NULL_TYPE;
697                     case DynamicConstantDesc<?> cd -> cd.constantType();
698                     case DirectMethodHandleDesc _ -> CD_MethodHandle;
699                     case MethodTypeDesc _ -> CD_MethodType;
700                 });
701             case ConvertInstruction i ->
702                 pop(1).push(i.toType().upperBound());
703             case FieldInstruction i -> {
704                 switch (i.opcode()) {
705                     case GETSTATIC ->
706                         push(i.typeSymbol());
707                     case GETFIELD ->
708                         pop(1).push(i.typeSymbol());
709                     case PUTSTATIC ->
710                         pop(1);
711                     case PUTFIELD ->
712                         pop(2);
713                 }
714             }
715             case IncrementInstruction i -> { // Increment instruction maps to two segments
716                 loadLocal(i.slot());
717                 insMap.put(-elementIndex - 1, locals.get(i.slot())); // source segment is mapped with -elementIndex - 1 key
718                 storeLocal(i.slot(), CD_int);
719                 insMap.put(elementIndex, locals.get(i.slot())); // target segment is mapped with elementIndex key
720                 for (var ec : handlersStack) {
721                     mergeLocalsToTargetFrame(stackMap.get(ec.handler()));
722                 }
723             }
724             case InvokeDynamicInstruction i ->
725                 pop(i.typeSymbol().parameterCount()).push(i.typeSymbol().returnType());
726             case InvokeInstruction i ->
727                 pop(i.typeSymbol().parameterCount() + (i.opcode() == Opcode.INVOKESTATIC ? 0 : 1))
728                         .push(i.typeSymbol().returnType());
729             case LoadInstruction i -> {
730                 push(loadLocal(i.slot())); // Load instruction segment is mapped with elementIndex key
731                 insMap.put(elementIndex, locals.get(i.slot()));
732             }
733             case StoreInstruction i -> {
734                 storeLocal(i.slot(), pop());
735                 insMap.put(elementIndex, locals.get(i.slot()));  // Store instruction segment is mapped with elementIndex key
736                 for (var ec : handlersStack) {
737                     mergeLocalsToTargetFrame(stackMap.get(ec.handler()));
738                 }
739             }
740             case MonitorInstruction _ ->
741                 pop(1);
742             case NewMultiArrayInstruction i ->
743                 pop(i.dimensions()).push(i.arrayType().asSymbol());
744             case NewObjectInstruction i ->
745                 push(i.className().asSymbol());
746             case NewPrimitiveArrayInstruction i ->
747                 pop(1).push(i.typeKind().upperBound().arrayType());
748             case NewReferenceArrayInstruction i ->
749                 pop(1).push(i.componentType().asSymbol().arrayType());
750             case OperatorInstruction i ->
751                 pop(switch (i.opcode()) {
752                     case ARRAYLENGTH, INEG, LNEG, FNEG, DNEG -> 1;
753                     default -> 2;
754                 }).push(i.typeKind().upperBound());
755             case StackInstruction i -> {
756                 switch (i.opcode()) {
757                     case POP -> pop(1);
758                     case POP2 -> pop(doubleAt(-1) ? 1 : 2);
759                     case DUP -> push(top());
760                     case DUP2 -> {
761                         if (doubleAt(-1)) {
762                             push(top());
763                         } else {
764                             pushAt(-2, top2());
765                         }
766                     }
767                     case DUP_X1 -> pushAt(-2, top());
768                     case DUP_X2 -> pushAt(doubleAt(-2) ? -2 : -3, top());
769                     case DUP2_X1 -> {
770                         if (doubleAt(-1)) {
771                             pushAt(-2, top());
772                         } else {
773                             pushAt(-3, top2());
774                         }
775                     }
776                     case DUP2_X2 -> {
777                         if (doubleAt(-1)) {
778                             pushAt(doubleAt(-2) ? -2 : -3, top());
779                         } else {
780                             pushAt(doubleAt(-3) ? -3 : -4, top2());
781                         }
782                     }
783                     case SWAP -> pushAt(-1, pop());
784                 }
785             }
786             case TypeCheckInstruction i ->
787                 pop(1).push(i.opcode() == Opcode.CHECKCAST ? i.type().asSymbol() : ConstantDescs.CD_int);
788             case LabelTarget lt -> {
789                 var frame = stackMap.get(lt.label());
790                 if (frame != null) { // Here we reached a stack map frame, so we merge actual stack and locals into the frame
791                     if (!stack.isEmpty() || !locals.isEmpty()) {
792                         mergeToTargetFrame(lt.label());
793                         endOfFlow();
794                     }
795                     // Stack and locals are then taken from the frame
796                     stack.addAll(frame.stack());
797                     locals.addAll(frame.locals());
798                 }
799                 for (ExceptionCatch ec : exceptionHandlers) {
800                     if (lt.label() == ec.tryStart()) { // Entering a try block
801                         handlersStack.add(ec);
802                         mergeLocalsToTargetFrame(stackMap.get(ec.handler()));
803                     }
804                     if (lt.label() == ec.tryEnd()) { // Leaving a try block
805                         handlersStack.remove(ec);
806                     }
807                 }
808             }
809             case ReturnInstruction _ , ThrowInstruction _ -> {
810                 endOfFlow();
811             }
812             case TableSwitchInstruction tsi -> {
813                 pop();
814                 mergeToTargetFrame(tsi.defaultTarget());
815                 for (var c : tsi.cases()) {
816                     mergeToTargetFrame(c.target());
817                 }
818                 endOfFlow();
819             }
820             case LookupSwitchInstruction lsi -> {
821                 pop();
822                 mergeToTargetFrame(lsi.defaultTarget());
823                 for (var c : lsi.cases()) {
824                     mergeToTargetFrame(c.target());
825                 }
826                 endOfFlow();
827             }
828             default -> {}
829         }
830     }
831 
832     private void endOfFlow() {
833         stack.clear();
834         locals.clear();
835     }
836 
837     /**
838      * Merge of the actual {@link #stack} and {@link #locals} to the target stack map frame
839      * @param target label of the target stack map frame
840      */
841     private void mergeToTargetFrame(Label target) {
842         Frame targetFrame = stackMap.get(target);
843         // Merge stack
844         assert stack.size() == targetFrame.stack.size();
845         for (int i = 0; i < targetFrame.stack.size(); i++) {
846             ClassDesc se = stack.get(i);
847             ClassDesc fe = targetFrame.stack.get(i);
848             if (!se.equals(fe)) {
849                 if (se.isPrimitive() && CD_int.equals(fe)) {
850                     targetFrame.stack.set(i, se); // Override int target frame type with more specific int sub-type
851                     this.frameDirty = true; // This triggers scan loop to run again, as the stack map frame has been adjusted
852                 } else {
853                     stack.set(i, fe); // Override stack type with target frame type
854                 }
855             }
856         }
857         mergeLocalsToTargetFrame(targetFrame);
858     }
859 
860 
861     /**
862      * Merge of the actual {@link #locals} to the target stack map frame
863      * @param targetFrame target stack map frame
864      */
865     private void mergeLocalsToTargetFrame(Frame targetFrame) {
866         // Merge locals
867         int lSize = Math.min(locals.size(), targetFrame.locals.size());
868         for (int i = 0; i < lSize; i++) {
869             Segment le = locals.get(i);
870             Segment fe = targetFrame.locals.get(i);
871             if (le != null && fe != null) {
872                 le.link(fe); // Link target frame var with its source
873                 if (!le.type.equals(fe.type)) {
874                     if (le.type.isPrimitive() && CD_int.equals(fe.type) ) {
875                         fe.type = le.type; // Override int target frame type with more specific int sub-type
876                         this.frameDirty = true; // This triggers scan loop to run again, as the stack map frame has been adjusted
877                     }
878                 }
879             }
880         }
881     }
882 }