|
40 | 40 | import java.util.ArrayList; |
41 | 41 | import java.util.Collection; |
42 | 42 | import java.util.Collections; |
43 | | -import java.util.HashMap; |
44 | 43 | import java.util.HashSet; |
45 | 44 | import java.util.List; |
46 | 45 | import java.util.Map; |
@@ -75,7 +74,7 @@ public class CapturedContextInstrumentor extends Instrumentor { |
75 | 74 | private int exitContextVar = -1; |
76 | 75 | private int timestampStartVar = -1; |
77 | 76 | private int throwableListVar = -1; |
78 | | - private Collection<LocalVariableNode> unscopedLocalVars; |
| 77 | + private Collection<LocalVariableNode> hoistedLocalVars = Collections.emptyList(); |
79 | 78 |
|
80 | 79 | public CapturedContextInstrumentor( |
81 | 80 | ProbeDefinition definition, |
@@ -413,12 +412,7 @@ private void instrumentMethodEnter() { |
413 | 412 | if (methodNode.tryCatchBlocks.size() > 0) { |
414 | 413 | throwableListVar = declareThrowableList(insnList); |
415 | 414 | } |
416 | | - unscopedLocalVars = Collections.emptyList(); |
417 | | - if (Config.get().isDynamicInstrumentationHoistLocalVarsEnabled() |
418 | | - && language == JvmLanguage.JAVA) { |
419 | | - // for now, only hoist local vars for Java |
420 | | - unscopedLocalVars = initAndHoistLocalVars(insnList); |
421 | | - } |
| 415 | + hoistedLocalVars = initAndHoistLocalVars(methodNode); |
422 | 416 | insnList.add(contextInitLabel); |
423 | 417 | if (definition instanceof SpanDecorationProbe |
424 | 418 | && definition.getEvaluateAt() == MethodLocation.EXIT) { |
@@ -485,213 +479,20 @@ private void instrumentMethodEnter() { |
485 | 479 |
|
486 | 480 | // Initialize and hoist local variables to the top of the method |
487 | 481 | // if there is name/slot conflict, do nothing for the conflicting local variable |
488 | | - private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) { |
489 | | - if (methodNode.localVariables == null || methodNode.localVariables.isEmpty()) { |
| 482 | + private Collection<LocalVariableNode> initAndHoistLocalVars(MethodNode methodNode) { |
| 483 | + int hoistingLevel = Config.get().getDynamicInstrumentationLocalVarHoistingLevel(); |
| 484 | + if (hoistingLevel == 0 || language != JvmLanguage.JAVA) { |
| 485 | + // for now, only hoist local vars for Java |
490 | 486 | return Collections.emptyList(); |
491 | 487 | } |
492 | | - Map<String, LocalVariableNode> localVarsByName = new HashMap<>(); |
493 | | - Map<Integer, LocalVariableNode> localVarsBySlot = new HashMap<>(); |
494 | | - Map<String, Set<LocalVariableNode>> hoistableVarByName = new HashMap<>(); |
495 | | - for (LocalVariableNode localVar : methodNode.localVariables) { |
496 | | - int idx = localVar.index - localVarBaseOffset; |
497 | | - if (idx < argOffset) { |
498 | | - // this is an argument |
499 | | - continue; |
500 | | - } |
501 | | - checkHoistableLocalVar(localVar, localVarsByName, localVarsBySlot, hoistableVarByName); |
502 | | - } |
503 | | - removeDuplicatesFromArgs(hoistableVarByName, localVarsBySlotArray); |
504 | | - // hoist vars |
505 | | - List<LocalVariableNode> results = new ArrayList<>(); |
506 | | - for (Map.Entry<String, Set<LocalVariableNode>> entry : hoistableVarByName.entrySet()) { |
507 | | - Set<LocalVariableNode> hoistableVars = entry.getValue(); |
508 | | - LocalVariableNode newVarNode; |
509 | | - if (hoistableVars.size() > 1) { |
510 | | - // merge variables |
511 | | - LocalVariableNode firstHoistableVar = hoistableVars.iterator().next(); |
512 | | - String name = firstHoistableVar.name; |
513 | | - String desc = firstHoistableVar.desc; |
514 | | - Type localVarType = getType(desc); |
515 | | - int newSlot = newVar(localVarType); // new slot for the local variable |
516 | | - newVarNode = new LocalVariableNode(name, desc, null, null, null, newSlot); |
517 | | - Set<LabelNode> endLabels = new HashSet<>(); |
518 | | - for (LocalVariableNode localVar : hoistableVars) { |
519 | | - // rewrite each usage of the old var to the new slot |
520 | | - rewriteLocalVarInsn(localVar, localVar.index, newSlot); |
521 | | - endLabels.add(localVar.end); |
522 | | - } |
523 | | - hoistVar(insnList, newVarNode); |
524 | | - newVarNode.end = findLatestLabel(methodNode.instructions, endLabels); |
525 | | - // remove previous local variables |
526 | | - methodNode.localVariables.removeIf(hoistableVars::contains); |
527 | | - methodNode.localVariables.add(newVarNode); |
528 | | - } else { |
529 | | - // hoist the single variable and rewrite all its local var instructions |
530 | | - newVarNode = hoistableVars.iterator().next(); |
531 | | - int oldIndex = newVarNode.index; |
532 | | - newVarNode.index = newVar(getType(newVarNode.desc)); // new slot for the local variable |
533 | | - rewriteLocalVarInsn(newVarNode, oldIndex, newVarNode.index); |
534 | | - hoistVar(insnList, newVarNode); |
535 | | - } |
536 | | - results.add(newVarNode); |
537 | | - } |
538 | | - return results; |
539 | | - } |
540 | | - |
541 | | - private void removeDuplicatesFromArgs( |
542 | | - Map<String, Set<LocalVariableNode>> hoistableVarByName, |
543 | | - LocalVariableNode[] localVarsBySlotArray) { |
544 | | - for (int idx = 0; idx < argOffset; idx++) { |
545 | | - LocalVariableNode localVar = localVarsBySlotArray[idx]; |
546 | | - if (localVar == null) { |
547 | | - continue; |
548 | | - } |
549 | | - // we are removing local variables that are arguments |
550 | | - hoistableVarByName.remove(localVar.name); |
551 | | - } |
552 | | - } |
553 | | - |
554 | | - private LabelNode findLatestLabel(InsnList instructions, Set<LabelNode> endLabels) { |
555 | | - for (AbstractInsnNode insn = instructions.getLast(); insn != null; insn = insn.getPrevious()) { |
556 | | - if (insn instanceof LabelNode && endLabels.contains(insn)) { |
557 | | - return (LabelNode) insn; |
558 | | - } |
559 | | - } |
560 | | - return null; |
561 | | - } |
562 | | - |
563 | | - private void hoistVar(InsnList insnList, LocalVariableNode varNode) { |
564 | | - LabelNode labelNode = new LabelNode(); // new start label for the local variable |
565 | | - insnList.add(labelNode); |
566 | | - varNode.start = labelNode; // update the start label of the local variable |
567 | | - Type localVarType = getType(varNode.desc); |
568 | | - addStore0Insn(insnList, varNode, localVarType); |
569 | | - } |
570 | | - |
571 | | - private static void addStore0Insn( |
572 | | - InsnList insnList, LocalVariableNode localVar, Type localVarType) { |
573 | | - switch (localVarType.getSort()) { |
574 | | - case Type.BOOLEAN: |
575 | | - case Type.CHAR: |
576 | | - case Type.BYTE: |
577 | | - case Type.SHORT: |
578 | | - case Type.INT: |
579 | | - insnList.add(new InsnNode(Opcodes.ICONST_0)); |
580 | | - break; |
581 | | - case Type.LONG: |
582 | | - insnList.add(new InsnNode(Opcodes.LCONST_0)); |
583 | | - break; |
584 | | - case Type.FLOAT: |
585 | | - insnList.add(new InsnNode(Opcodes.FCONST_0)); |
586 | | - break; |
587 | | - case Type.DOUBLE: |
588 | | - insnList.add(new InsnNode(Opcodes.DCONST_0)); |
589 | | - break; |
590 | | - default: |
591 | | - insnList.add(new InsnNode(Opcodes.ACONST_NULL)); |
592 | | - break; |
593 | | - } |
594 | | - insnList.add(new VarInsnNode(localVarType.getOpcode(Opcodes.ISTORE), localVar.index)); |
595 | | - } |
596 | | - |
597 | | - private void checkHoistableLocalVar( |
598 | | - LocalVariableNode localVar, |
599 | | - Map<String, LocalVariableNode> localVarsByName, |
600 | | - Map<Integer, LocalVariableNode> localVarsBySlot, |
601 | | - Map<String, Set<LocalVariableNode>> hoistableVarByName) { |
602 | | - LocalVariableNode previousVarBySlot = localVarsBySlot.putIfAbsent(localVar.index, localVar); |
603 | | - LocalVariableNode previousVarByName = localVarsByName.putIfAbsent(localVar.name, localVar); |
604 | | - if (previousVarBySlot != null) { |
605 | | - // there are multiple local variables with the same slot but different names |
606 | | - // by hoisting in a new slot, we can avoid the conflict |
607 | | - hoistableVarByName.computeIfAbsent(localVar.name, k -> new HashSet<>()).add(localVar); |
608 | | - } |
609 | | - if (previousVarByName != null) { |
610 | | - // there are multiple local variables with the same name |
611 | | - // checking type to see if they are compatible |
612 | | - Type previousType = getType(previousVarByName.desc); |
613 | | - Type currentType = getType(localVar.desc); |
614 | | - if (!ASMHelper.isStoreCompatibleType(previousType, currentType)) { |
615 | | - reportWarning( |
616 | | - "Local variable " |
617 | | - + localVar.name |
618 | | - + " has multiple definitions with incompatible types: " |
619 | | - + previousVarByName.desc |
620 | | - + " and " |
621 | | - + localVar.desc); |
622 | | - // remove name from hoistable |
623 | | - hoistableVarByName.remove(localVar.name); |
624 | | - return; |
625 | | - } |
626 | | - // Merge variables because compatible type |
627 | | - } |
628 | | - // by default, there is no conflict => hoistable |
629 | | - hoistableVarByName.computeIfAbsent(localVar.name, k -> new HashSet<>()).add(localVar); |
630 | | - } |
631 | | - |
632 | | - private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int newSlot) { |
633 | | - rewritePreviousStoreInsn(localVar, oldSlot, newSlot); |
634 | | - for (AbstractInsnNode insn = localVar.start; |
635 | | - insn != null && insn != localVar.end; |
636 | | - insn = insn.getNext()) { |
637 | | - if (insn instanceof VarInsnNode) { |
638 | | - VarInsnNode varInsnNode = (VarInsnNode) insn; |
639 | | - if (varInsnNode.var == oldSlot) { |
640 | | - varInsnNode.var = newSlot; |
641 | | - } |
642 | | - } |
643 | | - if (insn instanceof IincInsnNode) { |
644 | | - IincInsnNode iincInsnNode = (IincInsnNode) insn; |
645 | | - if (iincInsnNode.var == oldSlot) { |
646 | | - iincInsnNode.var = newSlot; |
647 | | - } |
648 | | - } |
649 | | - } |
650 | | - } |
651 | | - |
652 | | - // Previous insn(s) to local var range could be a store to index that need to be rewritten as well |
653 | | - // LocalVariableTable ranges starts after the init of the local var. |
654 | | - // ex: |
655 | | - // 9: iconst_0 |
656 | | - // 10: astore_1 |
657 | | - // 11: aload_1 |
658 | | - // range for slot 1 starts at 11 |
659 | | - // javac often starts the range right after the init of the local var, so we can just look for |
660 | | - // the previous instruction. But not always, and we put an arbitrary limit to 10 instructions |
661 | | - // for kotlinc, many instructions can separate the init and the range start |
662 | | - // ex: |
663 | | - // LocalVariableTable: |
664 | | - // Start Length Slot Name Signature |
665 | | - // 70 121 4 $i$f$map I |
666 | | - // |
667 | | - // 64: istore 4 |
668 | | - // 66: aload_2 |
669 | | - // 67: iconst_2 |
670 | | - // 68: iconst_1 |
671 | | - // 69: bastore |
672 | | - // 70: aload_3 |
673 | | - private static void rewritePreviousStoreInsn( |
674 | | - LocalVariableNode localVar, int oldSlot, int newSlot) { |
675 | | - AbstractInsnNode previous = localVar.start.getPrevious(); |
676 | | - int processed = 0; |
677 | | - // arbitrary fixing limit to 10 previous instructions to look at |
678 | | - while (previous != null && !isVarStoreForSlot(previous, oldSlot) && processed < 10) { |
679 | | - previous = previous.getPrevious(); |
680 | | - processed++; |
681 | | - } |
682 | | - if (isVarStoreForSlot(previous, oldSlot)) { |
683 | | - VarInsnNode varInsnNode = (VarInsnNode) previous; |
684 | | - if (varInsnNode.var == oldSlot) { |
685 | | - varInsnNode.var = newSlot; |
686 | | - } |
| 488 | + if (methodNode.localVariables == null || methodNode.localVariables.isEmpty()) { |
| 489 | + return Collections.emptyList(); |
687 | 490 | } |
| 491 | + LOGGER.debug( |
| 492 | + "Hoisting local variables level={} for method: {}", hoistingLevel, methodNode.name); |
| 493 | + return LocalVarHoisting.processMethod(methodNode, hoistingLevel); |
688 | 494 | } |
689 | 495 |
|
690 | | - private static boolean isVarStoreForSlot(AbstractInsnNode node, int slotIdx) { |
691 | | - return node instanceof VarInsnNode |
692 | | - && isStore(node.getOpcode()) |
693 | | - && ((VarInsnNode) node).var == slotIdx; |
694 | | - } |
695 | 496 |
|
696 | 497 | private void createInProbeFinallyHandler(LabelNode inProbeStartLabel, LabelNode inProbeEndLabel) { |
697 | 498 | LabelNode handlerLabel = new LabelNode(); |
@@ -916,20 +717,18 @@ private void collectLocalVariables(AbstractInsnNode location, InsnList insnList) |
916 | 717 | return; |
917 | 718 | } |
918 | 719 | Collection<LocalVariableNode> localVarNodes; |
919 | | - boolean isLocalVarHoistingEnabled = |
920 | | - Config.get().isDynamicInstrumentationHoistLocalVarsEnabled(); |
921 | | - if (definition.isLineProbe() || !isLocalVarHoistingEnabled) { |
| 720 | + if (definition.isLineProbe() || hoistedLocalVars.isEmpty()) { |
922 | 721 | localVarNodes = methodNode.localVariables; |
923 | 722 | } else { |
924 | | - localVarNodes = unscopedLocalVars; |
| 723 | + localVarNodes = hoistedLocalVars; |
925 | 724 | } |
926 | 725 | List<LocalVariableNode> applicableVars = new ArrayList<>(); |
927 | 726 | boolean isLineProbe = definition.isLineProbe(); |
928 | 727 | for (LocalVariableNode variableNode : localVarNodes) { |
929 | 728 | int idx = variableNode.index - localVarBaseOffset; |
930 | 729 | if (idx >= argOffset) { |
931 | 730 | // var is local not arg |
932 | | - if (isLineProbe || !isLocalVarHoistingEnabled) { |
| 731 | + if (isLineProbe || hoistedLocalVars.isEmpty()) { |
933 | 732 | if (ASMHelper.isInScope(methodNode, variableNode, location)) { |
934 | 733 | applicableVars.add(variableNode); |
935 | 734 | } |
|
0 commit comments