private static void internalDepthFirstSearch(@NotNull List<? extends Instruction> instructions,
                                               @NotNull InstructionClientVisitor<?> clientVisitor,
                                               int startOffset,
                                               int endOffset) {

    final WalkThroughStack walkThroughStack = new WalkThroughStack(instructions.size() / 2);
    walkThroughStack.push(startOffset);

    ControlFlowInstructionVisitor getNextOffsetVisitor = new ControlFlowInstructionVisitor() {
      @Override
      public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) {
        int newOffset = instruction.offset;
        // 'procedure' pointed by call instruction should be processed regardless of whether it was already visited or not
        // clear procedure text and return instructions afterwards
        int i;
        for (i = instruction.procBegin;
             i < clientVisitor.processedInstructions.length &&
             (i < instruction.procEnd || i < instructions.size() && instructions.get(i) instanceof ReturnInstruction); i++) {
          clientVisitor.processedInstructions[i] = false;
        }
        clientVisitor.procedureEntered(instruction.procBegin, i);
        walkThroughStack.currentStack = new CallStackItem(walkThroughStack.currentStack, offset + 1);
        walkThroughStack.push(offset, newOffset);
        walkThroughStack.push(newOffset);
      }

      @Override
      public void visitReturnInstruction(ReturnInstruction instruction, int offset, int nextOffset) {
        int newOffset = -1;
        if (walkThroughStack.currentStack != null) {
          newOffset = walkThroughStack.currentStack.target;
          walkThroughStack.currentStack = walkThroughStack.currentStack.next;
        }
        if (instruction.offset != 0) {
          newOffset = instruction.offset;
        }
        if (newOffset != -1) {
          walkThroughStack.push(offset, newOffset);
          walkThroughStack.push(newOffset);
        }
      }

      @Override
      public void visitBranchingInstruction(BranchingInstruction instruction, int offset, int nextOffset) {
        int newOffset = instruction.offset;
        walkThroughStack.push(offset, newOffset);
        walkThroughStack.push(newOffset);
      }

      @Override
      public void visitConditionalBranchingInstruction(ConditionalBranchingInstruction instruction, int offset, int nextOffset) {
        int newOffset = instruction.offset;

        walkThroughStack.push(offset, newOffset);
        walkThroughStack.push(offset, offset + 1);
        walkThroughStack.push(newOffset);
        walkThroughStack.push(offset + 1);
      }

      @Override
      public void visitInstruction(Instruction instruction, int offset, int nextOffset) {
        int newOffset = offset + 1;
        walkThroughStack.push(offset, newOffset);
        walkThroughStack.push(newOffset);
      }
    };
    while (!walkThroughStack.isEmpty()) {
      final int offset = walkThroughStack.peekOldOffset();
      final int newOffset = walkThroughStack.popNewOffset();

      if (offset >= endOffset) {
        continue;
      }
      Instruction instruction = instructions.get(offset);

      if (clientVisitor.processedInstructions[offset]) {
        if (newOffset != -1) {
          instruction.accept(clientVisitor, offset, newOffset);
        }
        continue;
      }

      clientVisitor.processedInstructions[offset] = true;
      instruction.accept(getNextOffsetVisitor, offset, newOffset);
    }
  }
private static void internalDepthFirstSearch(@NotNull List<? extends Instruction> instructions,
                                               @NotNull InstructionClientVisitor<?> clientVisitor,
                                               int startOffset,
                                               int endOffset)