Torque. Язык для V8.
Yuriy KarpovЯ периодически читаю исходники V8, чтобы понять, как реализован тот или иной метод. Исходно V8 написан на C++, с которым у меня есть небольшой опыт, поэтому на уровне читателя я справляюсь.
Однажды я полез посмотреть реализацию методов Array — и неожиданно столкнулся с файлами с расширением .tq. Код в них был похож на внебрачного сына C++ и TypeScript. Это оказался язык Torque.
В этой статье я расскажу, что это за язык, зачем его придумали разработчики V8 и как он влияет на производительность и читаемость движка.
Torque
Torque — это домен-специфический язык программирования (DSL), созданный специально для работы внутри движка V8. Он предназначен для реализации встроенных функций JavaScript, таких как map, reduce, split, isArray, и многих других.
Torque пришёл на смену части кода, ранее написанного на C++ и JavaScript, и делает его более:
- читаемым,
- безопасным,
- и оптимизированным для исполнения внутри V8.
Почему V8 использует Torque
1. Интеграция с V8
Torque тесно связан с внутренними структурами движка:
- управлением памятью (кучей),
- типами объектов (массивы, строки, функции),
- структурой элементов массивов и т.д.
2. Высокоуровневый синтаксис
Синтаксис Torque схож с JavaScript, а точнее с TypeScript, что делает его более доступным для разработчиков. Он позволяет описывать функции на высоком уровне с обеспечением строгой проверки типов. Это упрощает разработку встроенных функций.
Примеры Torque-кода
HelloWorld на Torque:
@export
macro PrintHelloWorld(): void {
Print('Hello world!');
}
Подсчёт длины массива:
@builtin
function ArrayLength(array: JSArray): Smi {
return %_GetProperty(array, "length");
}
@builtin— аннотация, указывающая, что функция встроена в движок.array: JSArray— строгая типизация, указывающая, что аргумент — это JavaScript-массив.Smi— представляет собой низкоуровневый тип для маленьких целых чисел.
Реальный пример: Array.isArray
Функция Array.isArray() реализована на Torque вот так:
Исходник на GitHub
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
namespace runtime {
extern runtime ArrayIsArray(implicit context: Context)(JSAny): JSAny;
} // namespace runtime
namespace array {
// ES #sec-array.isarray
javascript builtin ArrayIsArray(
js-implicit context: NativeContext)(arg: JSAny): JSAny {
// 1. Return ? IsArray(arg).
typeswitch (arg) {
case (JSArray): {
return True;
}
case (JSProxy): {
// TODO(verwaest): Handle proxies in-place
return runtime::ArrayIsArray(arg);
}
case (JSAny): {
return False;
}
}
}
} // namespace array
Что здесь происходит:
Определяет встроенную функцию ArrayIsArray, соответствующую Array.isArray() в JavaScript.
Принимает аргумент любого типа (JSAny) и неявно получает текущий JS-контекст.
Проверяет тип аргумента с помощью typeswitch:
- Проверяется, является ли аргумент массивом (
JSArray) — тогда возвращаетсяtrue. - Если аргумент —
Proxy, вызывается внешняяruntime-функция. - Всё остальное возвращает
false.
Оборачивает реализацию в namespace array, чтобы отделить встроенные функции.
Подключает runtime-функцию через namespace runtime, чтобы использовать её при необходимости.
Процесс выполнения:
Например, вызов reduce в JavaScript может быть перенаправлен в реализацию на Torque, если этот метод реализован в Torque. Torque-функция выполняет операции (например, итерацию по массиву) с использованием внутренних структур V8. Результат возвращается обратно в JavaScript-код. Torque-код транслируется в C++, а затем компилируется стандартными инструментами V8 в машинный код, который эффективно исполняется внутри движка.
Читатель, обрати внимание, что трансляция происходит не рантайм, а перед компиляцией V8, далее будет подробнее об этом.
Безопасность и производительность:
- Строгая типизация предотвращает ошибки, связанные с неправильными типами.
- Интеграция с оптимизирующим компилятором V8 обеспечивает быструю генерацию машинного кода.
- Проверки на уровне компиляции упрощают написание корректного и оптимизированного кода.
Как Torque улучшает работу V8:
- Читаемость: Код на Torque проще и понятнее, что важно для команды разработчиков V8 и сторонних участников.
- Легкость разработки: Встроенные методы и примитивы легче писать и обновлять, без необходимости управления низкоуровневыми аспектами.
- Оптимизация производительности: Код, написанный на Torque, автоматически генерируется с учетом особенностей V8, что делает его более эффективным.
- Совместимость: Torque встроен в процесс разработки V8, что позволяет работать с внутренними структурами, типами и оптимизациями движка.
Torque в контексте разработки V8:
Torque заменяет часть старого C++ кода, улучшая читаемость и упрощая разработку. Его основной вклад заключается в оптимизации встроенных методов, снижении количества багов благодаря строгой типизации и проверкам на этапе компиляции, а также в упрощении написания и поддержки встроенных функций. Это делает внутреннюю работу V8 более мощной, гибкой и доступной для будущих улучшений.
Можете посмотреть, что уже заменили на Torque https://chromium.googlesource.com/v8/v8/+/refs/heads/main/src/builtins/
Как Torque компилируется?
- Превращение Torque в C++
Torque-компилятор берет написанный код и генерирует из него C++ код. Этот процесс называется транспиляцией(перевод из одного языка в другой). Сгенерированный C++ код затем становится частью движка V8.
2. Компиляция C++ в машинный код
Сгенерированный C++ вызывает существующий интерфейс CodeStubAssembler в V8. CodeStubAssembler, в свою очередь, использует бэкенд компилятора TurboFan для генерации эффективного машинного кода.
Таким образом, компиляция с помощью Torque включает несколько этапов:
- Сборка через GN запускает компилятор Torque, который обрабатывает все файлы с расширением
*.tq. Каждый файл Torquepath/to/file.tqприводит к генерации нескольких файлов. - GN-сборка компилирует сгенерированные
-csa.ccфайлы (из шага 1) в исполняемый файлmksnapshot. - Во время выполнения
mksnapshotвсе встроенные функции V8 (builtins) генерируются и упаковываются в snapshot-файл, включая те, что были определены через Torque, а также другие builtins, использующие функциональность, определенную в Torque. - Оставшаяся часть V8 собирается. Все встроенные функции, созданные с помощью Torque, становятся доступными через snapshot-файл, который линкуется в V8. Эти функции можно вызывать как любые другие builtins. Кроме того, исполняемые файлы
d8илиchromeтакже включают напрямую сгенерированные единицы компиляции, связанные с определениями классов.

Вывод
Torque – важный шаг в эволюции движка V8. Он упростил реализацию встроенных методов JavaScript, сделал код более читаемым, надёжным и одновременно повысил производительность.
Хотя Torque – это внутренний язык V8 и не предназначен для использования в прикладных проектах, его появление значительно улучшило архитектуру движка и упростило поддержку встроенных функций.
А зачем мне это?
Изучение исходников V8 даёт возможность лучше понять, как работает JavaScript в Chrome и Node.js на самом низком уровне. Благодаря Torque это стало заметно проще.
Ранее мы разобрали bytecode в статье "Как Ignition оптимизирует forEach() в bytecode v8" и подготовили Benchmark-платформу для доклада "На чем сегодня писать для WebAssembly?" (HolyJS), теперь же мы закрыли третий слой –исходный код движка.
В результате, у нас появляется полноценная трёхуровневая модель анализа JavaScript:
- Исходный код в V8
- Байткод, генерируемый при исполнении
- Измерения производительности в Benchmark
Эта связка даёт глубокое понимание того, как работает JavaScript под капотом. И мы будем использовать все эти знания для дальнейших исследований и статей.
Если вы хотите глубже изучить Torque или попробовать себя в разработке V8, читайте официальную документацию: https://v8.dev/docs/torque
Всем, кто прочитал спасибо, присоединяйтесь к моему каналу https://t.me/frontend_bookmark