docs: private void emitSubroutine(final Instantiation instant,
              final List<Instantiation> worklist, final InsnList newInstructions,
              final List<TryCatchBlockNode> newTryCatchBlocks,
              final List<LocalVariableNode> newLocalVariables) {
          LabelNode duplbl = null;
  
          if (LOGGING) {
              log("--------------------------------------------------------");
              log("Emitting instantiation of subroutine " + instant.subroutine);
          }
  
          // Emit the relevant instructions for this instantiation, translating
          // labels and jump targets as we go:
          for (int i = 0, c = instructions.size(); i < c; i++) {
              AbstractInsnNode insn = instructions.get(i);
              Instantiation owner = instant.findOwner(i);
  
              // Always remap labels:
              if (insn.getType() == AbstractInsnNode.LABEL) {
                  // Translate labels into their renamed equivalents.
                  // Avoid adding the same label more than once. Note
                  // that because we own this instruction the gotoTable
                  // and the rangeTable will always agree.
                  LabelNode ilbl = (LabelNode) insn;
                  LabelNode remap = instant.rangeLabel(ilbl);
                  if (LOGGING) {
                      // TODO use of default toString().
                      log("Translating lbl #" + i + ':' + ilbl + " to " + remap);
                  }
                  if (remap != duplbl) {
                      newInstructions.add(remap);
                      duplbl = remap;
                  }
                  continue;
              }
  
              // We don't want to emit instructions that were already
              // emitted by a subroutine higher on the stack. Note that
              // it is still possible for a given instruction to be
              // emitted twice because it may belong to two subroutines
              // that do not invoke each other.
              if (owner != instant) {
                  continue;
              }
  
              if (LOGGING) {
                  log("Emitting inst #" + i);
              }
  
              if (insn.getOpcode() == RET) {
                  // Translate RET instruction(s) to a jump to the return label
                  // for the appropriate instantiation. The problem is that the
                  // subroutine may "fall through" to the ret of a parent
                  // subroutine; therefore, to find the appropriate ret label we
                  // find the lowest subroutine on the stack that claims to own
                  // this instruction. See the class javadoc comment for an
                  // explanation on why this technique is safe (note: it is only
                  // safe if the input is verifiable).
                  LabelNode retlabel = null;
                  for (Instantiation p = instant; p != null; p = p.previous) {
                      if (p.subroutine.get(i)) {
                          retlabel = p.returnLabel;
                      }
                  }
                  if (retlabel == null) {
                      // This is only possible if the mainSubroutine owns a RET
                      // instruction, which should never happen for verifiable
                      // code.
                      throw new RuntimeException("Instruction #" + i
                              + " is a RET not owned by any subroutine");
                  }
                  newInstructions.add(new JumpInsnNode(GOTO, retlabel));
              } else if (insn.getOpcode() == JSR) {
                  LabelNode lbl = ((JumpInsnNode) insn).label;
                  BitSet sub = subroutineHeads.get(lbl);
                  Instantiation newinst = new Instantiation(instant, sub);
                  LabelNode startlbl = newinst.gotoLabel(lbl);
  
                  if (LOGGING) {
                      log(" Creating instantiation of subr " + sub);
                  }
  
                  // Rather than JSRing, we will jump to the inline version and
                  // push NULL for what was once the return value. This hack
                  // allows us to avoid doing any sort of data flow analysis to
                  // figure out which instructions manipulate the old return value
                  // pointer which is now known to be unneeded.
                  newInstructions.add(new InsnNode(ACONST_NULL));
                  newInstructions.add(new JumpInsnNode(GOTO, startlbl));
                  newInstructions.add(newinst.returnLabel);
  
                  // Insert this new instantiation into the queue to be emitted
                  // later.
                  worklist.add(newinst);
              } else {
                  newInstructions.add(insn.clone(instant));
              }
          }
  
          // Emit try/catch blocks that are relevant to this method.
          for (Iterator<TryCatchBlockNode> it = tryCatchBlocks.iterator(); it
                  .hasNext();) {
              TryCatchBlockNode trycatch = it.next();
  
              if (LOGGING) {
                  // TODO use of default toString().
                  log("try catch block original labels=" + trycatch.start + '-'
                          + trycatch.end + "->" + trycatch.handler);
              }
  
              final LabelNode start = instant.rangeLabel(trycatch.start);
              final LabelNode end = instant.rangeLabel(trycatch.end);
  
              // Ignore empty try/catch regions
              if (start == end) {
                  if (LOGGING) {
                      log(" try catch block empty in this subroutine");
                  }
                  continue;
              }
  
              final LabelNode handler = instant.gotoLabel(trycatch.handler);
  
              if (LOGGING) {
                  // TODO use of default toString().
                  log(" try catch block new labels=" + start + '-' + end + "->"
                          + handler);
              }
  
              if (start == null || end == null || handler == null) {
                  throw new RuntimeException("Internal error!");
              }
  
              newTryCatchBlocks.add(new TryCatchBlockNode(start, end, handler,
                      trycatch.type));
          }
  
          for (Iterator<LocalVariableNode> it = localVariables.iterator(); it
                  .hasNext();) {
              LocalVariableNode lvnode = it.next();
              if (LOGGING) {
                  log("local var " + lvnode.name);
              }
              final LabelNode start = instant.rangeLabel(lvnode.start);
              final LabelNode end = instant.rangeLabel(lvnode.end);
              if (start == end) {
                  if (LOGGING) {
                      log("  local variable empty in this sub");
                  }
                  continue;
              }
              newLocalVariables.add(new LocalVariableNode(lvnode.name,
                      lvnode.desc, lvnode.signature, start, end, lvnode.index));
          }
      }