DEV Community

собачья будка
собачья будка

Posted on • Updated on

разбираемся с байткодом v8 [перевод]

Вольный перевод статьи Franziska Hinkelmann "Understanding V8’s Bytecode"

Разбираемся с байткодом V8

V8 - это опенсорсный движок для JavaScript, разработанный Google. Chrome, Node.js и многие другие приложения используют V8. В этой статье будет предложено объяснение формата байткода V8, который, на самом деле, станет очень простым для чтения, как только вы поймете некоторые основные концепты.

https://miro.medium.com/max/450/1*g8Tutq52nx6x44ELgz_UWg.png

Ignition, lift-off! Интерпретатор Ignition является частью пайплайна компилятора с 2016-го года.

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

https://miro.medium.com/max/1019/1*ZIH_wjqDfZn6NRKsDi9mvA.png

Пайплайн компилятора V8

Если хотите узнать, для чего нам нужно именно два режима выполнения, можете посмотреть мое видео с конференции JSConfEU:

video: https://www.youtube.com/watch?v=p-iiEDtpy6I&feature=emb_logo

Bytecode является абстракцией машинного кода. Компилировать байткод в машинный код будет проще, если байткод был спроектирован с одинаковой вычислительной моделью, что и физический CPU. Поэтому интерпретаторы обычно являются регистровыми или стэковыми машинами. Ignition - регистровая машина с аккумулятором (регистром процессора).

https://miro.medium.com/max/995/1*aal_1sevnb-4UaX8AvUQCg.png

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

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

Множество байткодов начинается с Lda или Staa в выражении Ld**a** и St**a** обозначает aккумулятор. Например, LdaSmi [42] подгружает маленькое целое число (Small Integer - Smi) 42 в регистр аккумулятора. Star r0 будет хранить текущее значение регистра r0.

Пора взглянуть на байткод реальной функции.

function incrementX(obj) {
  return 1 + obj.x;
}

incrementX({x: 42});  // Компилятор V8 ленив. Если вы не запустите функцию - он ее не интерпретирует.
Enter fullscreen mode Exit fullscreen mode

Если вы хотите посмотреть на байткод V8 в JavaScript коде, то исполните команду с флагом --print-bytecode (для D8 и Node.js версии 8.3 и выше). Для 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)
Enter fullscreen mode Exit fullscreen mode

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

LdaSmi [1]

LdaSmi [1] загружает постоянное значение 1 в аккумулятор.

https://miro.medium.com/max/311/1*WIECS2Gd701BnheqXrWbag.png

Star r0

Далее, Star r0  в регистре r0 хранит значение, которое на момент находится в аккумуляторе (1).

https://miro.medium.com/max/311/1*271aYN7VC6ltaleyDfwhXg.png

LdaNamedProperty a0, [0], [4]

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

- length: 1
           0: 0x2ddf8db91611 <String[1]: x>
Enter fullscreen mode Exit fullscreen mode

Here, 0 отображает значение x, потому этот байткод загружает obj.x.

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

Теперь регистры выглядят так:

https://miro.medium.com/max/311/1*sGFN376VKgf2hWXctBqZnw.png

Add r0, [6]

Последняя инструкция добавляет r0 в аккумулятор, возвращая в результате 436 - это еще один индекс вектора обратной связи.

https://miro.medium.com/max/311/1*LAHuYIvZaXX8jH_STNHfmQ.png

Return

Return возвращает значение аккумулятора. Это конец функции incrementX(). Вызывающий функцию  incrementX() операнд получает значение 43 из аккумулятора и может начать работать с ним.

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

https://miro.medium.com/max/1024/1*ZrJKJqBsksWd-8uKM9OvgA.png

Примечание: в статье описывается байткод V8 версии 6.2, Chrome версии 62 и Node версии 9. Мы постоянно работаем над улучшением потребления памяти и производительности V8, потому детали в других версиях могут отличаться.

Top comments (0)