Что на самом деле делает интерпретатор JVM (НЕ компилятор JIT)? - PullRequest
1 голос
/ 30 апреля 2019

Обратите внимание, что мой вопрос касается интерпретатора JVM, а не JIT-компилятора.JIT-компилятор преобразует Java-байт-коды в машинный код.Таким образом, это ДОЛЖНО означать, что интерпретатор в JVM НЕ конвертирует байт-коды в машинный код.Отсюда вопрос: по сути, что делает переводчик?Если кто-то может помочь мне ответить на это простым примером байтовых кодов, эквивалентных 1 + 1 = 2, то есть, что делает интерпретатор в отношении выполнения этой операции добавления?(Мой неявный вопрос: если интерпретатор не переводит в машинный код, какой процессор затем выполняет операцию ADD, как тогда выполняется эта операция? Какой машинный код выполняется в действительности для поддержки этой операции ADD?)

1 Ответ

2 голосов
/ 01 мая 2019

Выражение 1+1 будет компилироваться в следующий байт-код:

iconst_1
iconst_1
add

(На самом деле, оно просто компилируется в iconst_2, потому что компилятор Java выполняет свертывание констант, но давайте проигнорируем это дляцели этого ответа.)

Таким образом, чтобы точно узнать, что интерпретатор делает для этих инструкций, мы должны взглянуть на его исходный код .Соответствующие разделы для const_1 и add начинаются с строки 983 и строки 1221 соответственно, поэтому давайте посмотрим:

#define OPC_CONST_n(opcode, const_type, value)                          \
      CASE(opcode):                                                     \
          SET_STACK_ ## const_type(value, 0);                           \
          UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);

          OPC_CONST_n(_iconst_m1,   INT,       -1);
          OPC_CONST_n(_iconst_0,    INT,        0);
          OPC_CONST_n(_iconst_1,    INT,        1);
          // goes on for several other constants

//...
#define OPC_INT_BINARY(opcname, opname, test)                           \
      CASE(_i##opcname):                                                \
          if (test && (STACK_INT(-1) == 0)) {                           \
              VM_JAVA_ERROR(vmSymbols::java_lang_ArithmeticException(), \
                            "/ by zero", note_div0Check_trap);          \
          }                                                             \
          SET_STACK_INT(VMint##opname(STACK_INT(-2),                    \
                                      STACK_INT(-1)),                   \
                                      -2);                              \
          UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);                        \
          // and then the same thing for longs instead of ints

      OPC_INT_BINARY(add, Add, 0);
      // other operators

Все этонаходится внутри оператора switch, который проверяет код операции текущей инструкции.

Если мы расширим макромагию, заменим окружающий код предельно упрощенным шаблоном и сделаем некоторые упрощающие предположения (такие как стек, состоящий только изиз int с), мы в итоге получим что-то вроде этого:

enum OpCode {
  _iconst_1, _iadd
};

// ...
int* stack = new int[calculate_maximum_stack_size()];
size_t top_of_stack = 0;
size_t program_counter = 0;
while(program_counter < program_size) {
  switch(opcodes[pc]) {
    case _iconst_1:
      // SET_STACK_INT(1, 0);
      stack[top_of_stack] = 1;
      // UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
      program_counter += 1;
      top_of_stack += 1;
      break;

    case _iadd:
      // SET_STACK_INT(VMintAdd(STACK_INT(-2), STACK_INT(-1)), -2);
      stack[top_of_stack - 2] = stack[top_of_stack - 1] + stack[top_of_stack - 2];
      // UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
      program_counter += 1;
      top_of_stack += -1;
      break;
}

Так что для 1+1 последовательность операций будет такой:

stack[0] = 1;
stack[1] = 1;
stack[0] = stack[1] + stack[0];

И top_of_stack будетбудет 1, поэтому мы закончили бы стеком, который содержит значение 2 в качестве единственного элемента.

...