tgoop.com/soer_code/10
Last Update:
TurboFan: анализ оптимизаций в V8 с помощью ассемблера (часть 1)
Чем глубже мы узнаем языки программирования, тем больше начинаем любить и ценить ассемблер. Сегодня покажу, как знание ассемблера помогает в изучении возможностей движка V8.
V8 — это движок для работы с языком JavaScript, он используется в NodeJS и браузере Chrome. Одна из особенностей этого движка — представление «горячего» кода в виде оптимизированных машинных инструкций. Для этого в движок интегрирован оптимизирующий JIT-компилятор TurboFan.
Основные задачи, которые решает TurboFan:
Для работы с машинным кодом традиционно используется ассемблер, поэтому давайте разберем небольшой пример, который позволит лучше понять, как работает TurboFan. В этой статье будет общее описание блоков кода, а в будущих статьях разберем детали каждого из них.
Основные моменты, которые нужны для старта:
node --print-opt-code --code-comments -allow-natives-syntax your_script.js
Давайте сделаем простой скрипт:
// sum.js
function sum(a, b) {
return a + b;
}
// Прогреваем функцию (вызываем много раз, чтобы V8 её оптимизировал)
for (let i = 0; i < 10000; i++) {
sum(i, i + 1); // используем целые числа, чтобы TurboFan сделал оптимизацию именно под них
}
// Явно просим V8 оптимизировать функцию (требует --allow-natives-syntax) иначе в выводе не будет описане функции
%OptimizeFunctionOnNextCall(sum);
// Вызываем ещё раз (теперь с оптимизацией)
sum(1, 2);
Теперь запустим скрипт
node --print-opt-code --code-comments -allow-natives-syntax sum.js
, если мы сделали всё правильно, то получим огромный вывод на экран, из которого интересна вот эта часть:
--- Raw source ---
(a, b) {
return a + b;
}
--- Optimized code ---
optimization_id = 1
source_position = 22
kind = TURBOFAN
name = sum
stack_slots = 6
compiler = turbofan
address = 0x2edae09247a1
Instructions (size = 184)
; загрузка hidden класса объекта (проверка структуры)
0x1097064c0 0 488b59f8 REX.W movq rbx, [rcx-0x8]
; проверка контекста
0x1097064c4 4 f6433501 testb [rbx+0x35],0x1
0x1097064c8 8 0f85f2e03dfb jnz 0x104ae45c0 (CompileLazyDeoptimizedCode) ;; деоптимизация
; пролог
0x1097064ce e 55 push rbp
0x1097064cf f 4889e5 REX.W movq rbp, rsp
0x1097064d2 12 56 push rsi
0x1097064d3 13 57 push rdi
0x1097064d4 14 50 push rax
; выравнивание стека и проверка лимитов
0x1097064d5 15 4883ec08 REX.W subq rsp,0x8
0x1097064d9 19 488975e0 REX.W movq [rbp-0x20],rsi
0x1097064dd 1d 493b65a0 REX.W cmpq rsp, [r13-0x60] (external value (StackGuard::address_of_jslimit()))
0x1097064e1 21 0f8653000000 jna 0x10970653a <+0x7a> ; если стек переполнен, переходим
; ====== Основная функция ======
; Загрузка аргумента "a"
0x1097064e7 27 488b5518 REX.W movq rdx, [rbp+0x18]
0x1097064eb 2b f6c201 testb rdx,0x1
0x1097064ee 2e 0f8572000000 jnz 0x109706566 <+0xa6>
; Загрузка аргумента "b"
0x1097064f4 34 488b4d20 REX.W movq rcx, [rbp+0x20]
0x1097064f8 38 48c1f920 REX.W sarq rcx, 32 ; сразу распаковка SMI для "b"
; Подготовка аргументов к сложению
0x1097064fc 3c 488bfa REX.W movq rdi,rdx
0x1097064ff 3f 48c1ff20 REX.W sarq rdi, 32 ; распаковка для "a"