Путь к пониманию байт-кода V8

в 9:00, , рубрики: javascript, V8, байт-код, Блог компании RUVDS.com, разработка, Разработка веб-сайтов

V8 — это JavaScript-движок Google с открытым кодом. Его используют Chrome, Node.js и многие другие приложения. Этот материал, подготовленный сотрудником Google Франциской Хинкельманн, посвящён описанию формата байт-кода V8. Байт-код довольно просто читать, если понять некоторые базовые вещи.

image

Конвейер компиляции V8

Путь к пониманию байт-кода V8 - 2

Зажигание! Пуск! Интерпретатор Ignition, название которого можно перевести как «зажигание», является частью конвейера компиляции V8 с 2016-го года

Когда V8 компилирует JavaScript-код, парсер генерирует абстрактное синтаксическое дерево. Синтаксическое дерево — это древовидное представление синтаксической структуры JS-кода. Интерпретатор Ignition генерирует байт-код из этой структуры данных. Оптимизирующий компилятор TurboFan, в итоге, генерирует из байт-кода оптимизированный машинный код.

Путь к пониманию байт-кода V8 - 3

Конвейер компиляции V8

Если вы хотите узнать о том, почему V8 имеет два режима исполнения, взгляните на моё выступление с JSConfEU.

Основы байт-кода V8

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

Интерпретатор Ignition — это регистровая машина с накопительным регистром.

Путь к пониманию байт-кода V8 - 4

Код слева удобен для людей. Код справа — для машин

Байт-коды V8 можно воспринимать как маленькие строительные блоки, которые, собранные вместе, могут реализовать любой функционал JavaScript. V8 имеет несколько сотен байт-кодов. Существуют коды для операторов, вроде Add или TypeOf, или для загрузки свойств — вроде LdaNamedProperty. V8, кроме того, имеет некоторые довольно специфические байт-коды, такие, как CreateObjectLiteral или SuspendGenerator. В заголовочном файле bytecodes.h можно найти полный перечень байт-кодов V8.

Каждый байт-код определяет свои входные и выходные данные как регистровые операнды. Ignition использует регистры r0, r1, r2, ... и накопительный регистр. Почти все байт-коды задействуют накопительный регистр. Он похож на обычный регистр, за исключением того, что его явно не указывают в байт-кодах. Например, команда Add r1 добавляет значение из регистра r1 к тому, что хранится в накопительном регистре. Это делает байт-коды короче и экономит память.

Имена многих байт-кодов начинаются с Lda или Sta. Буква a в Lda и Sta является сокращением слова accumulator (накопительный регистр).

Например, команда LdaSmi [42] загружаем маленькое целое число (Small Integer, Smi) 42 в накопительный регистр. Команда Star r0 записывает значение, которое находится в накопительном регистре, в регистр r0.

Анализ байт-кода функции

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

function incrementX(obj) {
  return 1 + obj.x;
}
incrementX({x: 42});  // Компилятор V8 ленив, поэтому, если вы не вызовете функцию, он не будет её интерпретировать

Если вы хотите увидеть байт-код для JavaScript-кода, вывести его можно, вызвав отладчик D8 или Node.js (начиная с версии 8.3) с флагом --print-bytecode. В случае с Chrome — запустите его из командной строки с ключом --js-flags="--print-bytecode". Вот материал о вызове Chromium с ключами.

$ node --print-bytecode incrementX.js
...
[generating bytecode for function: incrementX]
Parameter count 2
Frame size 8
  12 E> 0x2ddf8802cf6e @    StackCheck
  19 S> 0x2ddf8802cf6f @    LdaSmi [1]
        0x2ddf8802cf71 @    Star r0
  34 E> 0x2ddf8802cf73 @    LdaNamedProperty a0, [0], [4]
  28 E> 0x2ddf8802cf77 @    Add r0, [6]
  36 S> 0x2ddf8802cf7a @    Return
Constant pool (size = 1)
0x2ddf8802cf21: [FixedArray] in OldSpace
 - map = 0x2ddfb2d02309 <Map(HOLEY_ELEMENTS)>
 - length: 1
           0: 0x2ddf8db91611 <String[1]: x>
Handler Table (size = 16)

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

LdaSmi [1]

Команда LdaSmi [1] загружает константу 1 в накопительный регистр.

Путь к пониманию байт-кода V8 - 5

Star r0

Команда Star r0 записывает значение, находящееся в накопительном регистре, то есть 1, в регистр r0.

Путь к пониманию байт-кода V8 - 6

LdaNamedProperty a0, [0], [4]

Команда LdaNamedProperty загружает именованное свойство a0 в накопительный регистр. Конструкция ai ссылается на i-й аргумент функции incrementX(). В этом примере мы обращаемся к именованному свойству по адресу a0, то есть — к первому аргументу incrementX(). Имя определяется константой 0. LdaNamedProperty использует 0 для поиска имени в отдельной таблице:

- length: 1
           0: 0x2ddf8db91611 <String[1]: x>

Здесь 0 отображается на x. В итоге оказывается, что данный байт-код загружает obj.x.

Для чего используется операнд с цифрой 4? Это индекс так называемого вектора обратной связи (feedback vector) функции increment(x). Вектор обратной связи содержит информацию времени выполнения, которая используется для оптимизации производительности.

Теперь содержимое регистров выглядит следующим образом.

Путь к пониманию байт-кода V8 - 7

Add r0, [6]

Последняя инструкция добавляет содержимое r0 к накопительному регистру, что приводит к получению итогового значения 43. Число 6 — это ещё один индекс вектора обратной связи.

Путь к пониманию байт-кода V8 - 8

Return

Команда Return возвращает содержимое накопительного регистра. Это — завершение функции incrementX(). То, что вызвало incrementX(), начинает работу с числом 43 в накопительном регистре и может продолжать выполнять некие действия с этим значением.

Обратите внимание на то, что байт-код, которому посвящён этот материал, используется в V8 версии 6.2, в Chrome 62 и в ещё не выпущенном Node 9. Мы, в Google, постоянно работаем над V8 в направлениях улучшения производительности и уменьшения потребления памяти. В других версиях V8 в байт-коде могут присутствовать некоторые отличия от того, что было описано здесь.

Итоги

На первый взгляд байт-код V8 может показаться довольно-таки загадочным, особенно когда он выводится с массой дополнительных сведений. Однако, как только вы узнаете о том, что Ignition — это регистровая машина с накопительным регистром, вы сможете понять назначение большинства байт-кодов.

Уважаемые читатели! Планируете ли вы анализировать байт-код ваших JS-программ?

Автор: ru_vds

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js