Байт-код

Байт-код (байтко́д; , также иногда p-код,  от ) — стандартное промежуточное представление, в которое может быть переведена компьютерная программа автоматическими средствами. По сравнению с исходным кодом, удобным для создания и чтения человеком, байт-код — это компактное представление программы, уже прошедшей синтаксический и семантический анализ. В нём в явном виде закодированы типы, области видимости и другие конструкции. С технической точки зрения байт-код представляет собой машинно-независимый код низкого уровня, генерируемый транслятором из исходного кода.

Многие современные языки программирования, особенно интерпретируемые, используют байт-код для облегчения и ускорения работы интерпретатора. Трансляция в байт-код является методом, промежуточным по эффективности между прямой интерпретацией и компиляцией в машинный код.

По форме байт-код похож на машинный код, но предназначен для исполнения не реальным процессором, а виртуальной машиной. В качестве виртуальной машины обычно выступает интерпретатор соответствующего языка программирования (иногда дополненный JIT- или AOT-компилятором). Спецификации байт-кода и исполняющих его виртуальных машин могут сильно различаться для разных языков: часто байт-код состоит из инструкций для  виртуальной машины, однако могут использоваться и  машины. Тем не менее, большинство инструкций байт-кода обычно эквивалентны одной или нескольким командам ассемблера.

Байт-код называется так, потому что длина каждого кода операции традиционно составляет один байт. Каждая инструкция обычно представляет собой однобайтовый код операции (от 0 до 255), за которым могут следовать различные параметры, например, номер регистра или адрес в памяти.

Исполнение 
Программа на байт-коде обычно выполняется интерпретатором байт-кода. Преимущество байт-кода в большей эффективности и портируемости, то есть один и тот же байт-код может исполняться на разных архитектурах, для которых реализован интерпретатор. То же самое преимущество дают непосредственно интерпретируемые языки, однако, поскольку байт-код обычно менее абстрактен и более компактен, чем исходный код, эффективность интерпретации байт-кода обычно выше, чем чистая интерпретация исходного кода или интерпретация АСД. Кроме того, интерпретатор байт-кода зачастую проще интерпретатора исходного кода и его проще перенести (портировать) на другую аппаратную платформу.

В высокопроизводительных реализациях виртуальных машин может применяться комбинация интерпретатора и JIT-компилятора, который во время исполнения программы транслирует часто используемые фрагменты байт-кода в машинный код, применяя при этом различные оптимизации. Вместо JIT-компиляции может применяться AOT-компилятор, транслирующий байт-код в машинный код предварительно, до исполнения.

В то же время возможно создание процессоров, для которых данный байт-код является непосредственно машинным кодом (такие экспериментальные процессоры создавались, например, для языков Java и Форт).

История 
Среди первых систем, использовавших байт-код, были O-code для BCPL (1960-е), Smalltalk (1976), SIL (System Implementation Language) для языка Snobol-4 (1967), p-код (p-code, 1970-е, при участии Никлауса Вирта) для переносимых компиляторов языка программирования Pascal.

Варианты p-кода широко использовались в различных реализациях языка Pascal, например, в UCSD p-System (UCSD Pascal).

Применение 
К интерпретируемым языкам, использующим байт-код, относятся Perl, PHP (например Zend Engine), Ruby (начиная с версии 1.9), Python, Erlang и многие другие.

Широко распространённые платформы, использующие байт-код:
 Байт-код Java (стековая виртуальная машина), исполняемый различными виртуальными машинами Java. Платформа была создана компанией Sun для языка Java, но стала использоваться и для других языков; существуют десятки высокопроизводительных реализаций JVM, использующих JIT-компиляторы.
 Существуют варианты трансляции Java в байт-код регистровых машин, например, в виртуальной машине Dalvik (с JIT-компиляцией) или при AOT-компиляции в ART
 Платформа Microsoft .NET использует стековый байт-код Intermediate Language (CIL, MSIL), исполняемый с помощью Common Language Runtime (CLR), создана Microsoft для C# и некоторых других языков.
 Сценарный язык JavaScript выполняется различными высокопроизводительными «движками», в основном, встроенными в веб-браузеры, часто с возможностью JIT-оптимизации. Многие интерпретаторы построены с применением байт-кода, однако программы на Javascript распространяются в виде исходных кодов.
 Сценарный язык ActionScript транслируется в стековый байт-код, распространяется в составе swf- и pdf-файлов, и выполняется виртуальными машинами в Adobe Flash и Adobe Acrobat.

Компилятор Clipper создает исполняемый файл, в который включен байт-код, транслированный из исходного текста программы, и виртуальная машина, исполняющая этот байт-код.

Программы на Java обычно компилируются в , содержащие байт-код Java. Эти универсальные файлы передаются на различные целевые машины.

В ранних реализациях Visual Basic (до версии 6) использовался высокоуровневый Microsoft p-code

Высокоуровневые p-коды и байт коды применялись в СУБД, некоторых реализациях Бейсика и Паскаля.

В стандарте открытых загрузчиков Open Firmware фирмы Sun Microsystems байт-код представляет операторы языка Форт.

Примеры

Python 
Код:
>>> print("Hello, World!")
Hello, World!
Байт-код:
>>> import dis #импортируем модуль "dis" - Disassembler of Python byte code into mnemonics.
>>> dis.dis('print("Hello, World!")')
  1           0 LOAD_NAME                0 (print)
              2 LOAD_CONST               0 ('Hello, World!')
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Java 
Код:
outer:
for (int i = 2; i < 1000; i++) {
    for (int j = 2; j < i; j++) {
        if (i % j == 0)
            continue outer;
    }
    System.out.println(i);
}
Байт-код:
  0:   iconst_2
  1:   istore_1
  2:   iload_1
  3:   sipush  1000
  6:   if_icmpge       44
  9:   iconst_2
  10:  istore_2
  11:  iload_2
  12:  iload_1
  13:  if_icmpge       31
  16:  iload_1
  17:  iload_2
  18:  irem
  19:  ifne    25
  22:  goto    38
  25:  iinc    2, 1
  28:  goto    11
  31:  getstatic       #84; //Field java/lang/System.out:Ljava/io/PrintStream;
  34:  iload_1
  35:  invokevirtual   #85; //Method java/io/PrintStream.println:(I)V
  38:  iinc    1, 1
  41:  goto    2
  44:  return

Критика 
Традиционно байт-код проектируется в стиле стековых виртуальных машин, что упрощает генерацию из AST, позволяет использовать более простую и компактную кодировку байт-кода, упростить интерпретатор и уменьшить количество машинного кода, требуемого для исполнения одной инструкции байт-кода. С другой стороны, такие варианты байт-кода для заданной программы содержат большее количество инструкций, чем байт-коды регистровых виртуальных машин, из-за чего интерпретатор должен совершить больше непрямых переходов, для которых плохо работает предсказание переходов. Байт-код для регистровых виртуальных машин имеет немного больший размер машинных кодов, однако количество инструкций по сравнению со стековым байт кодом примерно в два раза меньше, а интерпретатор — быстрее на десятки процентов. Также байт-код стековых машин сложнее для проведения оптимизаций (выражения становятся неявными, связанные инструкции не сгруппированы, выражения распределены по нескольким базовым блокам) и требует верификации корректности использования стека.

Ошибки верификации байт-кода стековых машин приводили к появлению множества экстремально опасных уязвимостей, в частности десятков в виртуальной машине AVM2, используемой в Adobe Flash для исполнения скриптов ActionScript и нескольких в ранних популярных системах исполнения Java (JVM)

В конце 2000-х — начале 2010-х авторы компиляторов V8 (для языка JavaScript, часто реализуемого через байт-код) и Dart усомнились в том, что промежуточные байт-коды обязательны для быстрых и эффективных виртуальных машин. В этих проектах была реализована непосредственная JIT-компиляция (компиляция во время исполнения) из исходных кодов сразу в машинный код.

Примечания 

Байт-код