Дротик — это одновременно легкое оружие и название языка программирования. Виртуальная машина Dart VM этого языка встроена в фреймворк Flutter и широко используется в мобильной разработке. Итак, можем ли мы избавиться от Flutter и внедрить Dart VM только в нативные проекты (такие как игровые движки или высокопроизводительные графические приложения)? Именно этому сценарию посвящена данная статья, а именно внедрению виртуальных машин.
В настоящее время Dart уже предоставляет аналогичныеnode
изdart
При запуске используется непосредственно в терминале. Но ведь это монолитное приложение, и его непросто интегрировать в другие программы. Как отделить Dart VM от этого единственного приложения или Flutter и использовать его отдельно? Чтобы решить эту задачу, примерно нужно разобраться в содержании этих трех частей:
- Основные концепции и работа Dart VM.
- Как скомпилировать встраиваемую статическую библиотеку Dart VM для iOS.
- Как внедрить Dart VM на iOS, чтобы запустить простейший Hello World.
В следующей статье платформа iOS будет использоваться в качестве примера, чтобы продемонстрировать, как программно использовать виртуальную машину Dart в собственном собственном проекте без Flutter.
Как работают виртуальные машины Dart
Способ, которым Dart VM выполняет код, сильно отличается от знакомых механизмов обработки сценариев, таких как V8. Самая большая разница в том, что,Dart VM не поддерживает прямую интерпретацию и выполнение исходного кода строки.. Если вы хотите думать об этом как о подсистеме сценариев, ориентированной на удобство, это определенно недостаток. Однако жертва гибкостью привела к значительному прорыву в инженерных возможностях двигателя, в основном включая следующие три различных режима работы:
- JIT-выполнение на основе двоичных файлов AST (так называемых Kernel Binary).
- JIT-выполнение на основе прогревочных снимков (так называемый AppJIT).
- Выполнение AOT на основе моментальных снимков AOT (так называемый AppAOT).
Dart VM на самом деле можно настроить многими другими способами, такими как отключение JIT-интерпретации и тому подобное. Здесь перечислены только самые критические из них.
Среди этих режимов работы Dart VM, за исключением первого, наиболее близкого к текущему движку JS, оставшиеся два режима трудно достижимы в рамках стека технологий JS (конечно, V8 также частично поддерживает моментальные снимки). Мало того, виртуальная машина Dart в реальном мире сыграла еще больше. В процессе глубокой поддержки Flutter режим выполнения Dart VM на мобильной стороне еще больше отличается от исходной настольной версии. Эти различия могут быть очень запутанными и должны быть разъяснены в первую очередь.
Короче говоря, Dart VM имеет разные режимы работы в сценариях, перечисленных ниже:
- Выполнить стандарт на рабочем столе
dart
команда, движок пропустит внутренний компонент CFE Common Front End (Common Front End),.dart
Исходный код формата анализируется в синтаксическом дереве AST в двоичной форме, а затем выполняется в основном изолированном (аналогично основному потоку) JIT,Соответствующий режим 1. - Выполнить на рабочем столе
dart --snapshot-kind=app-jit
команда, двигатель будет анализировать.dart
После исходного кода используйте обучающие данные для JIT и сохраните состояние виртуальной машины как.snapshot
отформатировать файл снимка. Этот моментальный снимок может быть повторно прочитан виртуальной машиной для восстановления JIT-сцены за один шаг, что оптимизирует производительность при запуске. Как типичный пример, Flutter'sflutter run
Команда выполняетсяflutter_tools.snapshot
снимок,Соответствующий режим 2. - Выполнить на рабочем столе
dart2native
команда, исходный код Dart будет скомпилирован в машинный код платформы, получить.aot
формат продукта. Этот продукт похож на собственный исполняемый формат ELF и может быть предварительно скомпилирован.dart_precompiled_runtime
Выполнение динамической загрузки во время выполнения,Соответствующий режим 3. - В режиме отладки Flutter на мобильном терминале исходный код Dart будет скомпилирован на рабочем столе разработчика в
.dill
отформатируйте Kernel Binary, затем эти.dill
Файлы динамически обновляются на мобильном устройстве через службу RPC. Это основа для Flutter поддержки черных технологий, таких как инкрементная компиляция и горячая перезагрузка.Вариант, соответствующий режиму 1. - В режиме выпуска Flutter на мобильном терминале исходный код Dart будет кросс-компилирован в машинный код ARM на рабочем столе разработчика и связан с предварительно скомпилированной средой выполнения.Вариант для режима 3.
Звучит сложно? В реальной эксплуатации достаточно запомнить эти простые и грубые правила:
- Самый простой двоичный формат ядра:
.dill
, Платформа универсальная. - Моментальный снимок, сгенерированный прогревом AppJIT,
.snapshot
Формат, платформа не распространены. - Команда компиляции AOT генерирует
.aot
Формат файла, платформа не распространена.
Dart VM основан на стандартном режиме работы Kernel Binary.
На рисунке выше показана стандартная виртуальная машина Dart, работающая на рабочем столе.Обратите внимание на архитектурное разделение внешнего интерфейса компиляции CFE и виртуальной машины: хотя она выполняется напрямую..dart
Мы не чувствуем себя так, когда дело доходит до файлов, но в мобильном сценарии Flutter это другая история. Flutter напрямую инкапсулирует CFE в проект командной строки Flutter Tool на рабочем столе (чистая реализация Dart), так что только часть виртуальной машины включается в мобильный Flutter Engine (смешанная реализация C++ и Dart). Следующим образом:
Режим запуска Dart VM на Flutter
Во Flutter детали компиляции Dart VM инкапсулированы фреймворком. Но в этом несложно разобраться в деталях, отладив Flutter Tool через точки останова VSCode, которые здесь не будут раскрываться.
Прежде чем приступить к следующему практическому этапу, наконец, популяризируйте несколько общих вопросов:
- Двоичный AST не является байт-кодом, он больше похож на структуру JSON скомпилированного синтаксического дерева Babel в двоичной форме. В связи с этим вы можете обратиться к личным комментариям на TC39Binary ASTНаука предложения.
- Языки высокого уровня также можно скомпилировать непосредственно в машинный код, просто связав с нативной средой выполнения, поддерживающей базовые возможности, такие как сборка мусора и ввод-вывод платформы. как иди иStatic TypeScriptВсе так делается. Что вы скажете о таких особенно динамичных частях, как JS? Интерпретатор скриптов также может быть скомпилирован в машинный код, в принципе, его можно вернуть к интерпретации и исполнению (это не значит, что компиляция в машинный код должна быть быстрой, иногда технология основана на замене оболочки).
- Dart выполняет компиляцию AOT не потому, что AOT обязательно лучше, чем JIT. И наоборот, языки высокого уровня, такие как Java, как правило, имеют более высокие потолки производительности для JIT, чем для AOT. Основной отправной точкой перехода Dart VM является соответствие ограничениям политики, которые iOS запрещает JIT в течение многих лет, и соответствие характеристикам мобильных сценариев (таким как короткое время пребывания страницы, необходимость быстрого достижения максимальной производительности, чувствительность к коду). размер и др.).
Скомпилируйте статическую библиотеку Dart VM
Теперь, когда мы полностью ознакомились с основными моментами Amway Dart VM на PPT, можно приступать к работе.
Во-первых, предположим, что у нас есть проект C++, как использовать для него Dart VM в качестве скриптового движка? Как и для любой другой библиотеки C++, для этого требуются заголовочные и библиотечные файлы сторонней библиотеки. Общая библиотека C++ будет в своемinclude
Файлы заголовков помещаются в каталог для внешнего использования, а различные типы файлов компилируются по умолчанию..a
Файлы библиотеки для повторного использования. Но раздражает то, что виртуальная машина Dart не делает это так, как это обычно известно, и в дереве исходных текстов нет примеров этого, как это делает Skia. К счастью, Вячеслав Егоров, сидящий в команде Dart (Парень, который оптимизировал производительность JS помимо Rust) недавно давал неофициалкуEmbedder Example. Пока предоставленный им патч помещается непосредственно в исходный код Dart, пример проекта C++, встроенный в Dart VM, может быть скомпилирован на основе существующей системы сборки Dart VM. Конкретный код его C++ части немного многословен, в целом он разбит на следующие шаги:
- существует
dart::embedder::InitOnce
ПозжеDart_Initialize
. - использовать
Dart_CreateIsolateGroupFromKernel
Загрузите исполняемый файл ядра и создайте соответствующий изолят. - запускать
Dart_RunLoop
, который формально выполняет код Dart.
Соответствующая конфигурация построения GN выглядит следующим образом (это относительно непопулярна, но система построения проектов на основе Google все еще очень хороша после знакомства, и я могу продолжить систематическое введение):
# 嵌入 Dart VM 的可执行文件入口
executable("embedder_example_1") {
# 该可执行文件依赖下面定义出的静态库
deps = [ ":libdartvm_for_embedding_nosnapshot_jit" ]
sources = [ "embedder_example_1.cc" ]
include_dirs = [ ".." ]
}
# 包含 Dart VM 的最小静态库
static_library("libdartvm_for_embedding_nosnapshot_jit") {
deps = [
":standalone_dart_io",
"..:libdart_jit",
"../platform:libdart_platform_jit",
"//third_party/boringssl",
"//third_party/zlib",
]
sources = [
"builtin.cc",
"dart_embedder_api_impl.cc",
]
}
Этот пример компилируется и используется следующим образом:
# 基于 Dart 的构建系统,编译出 C++ 产物
$ ninja -C xcodebuild/ReleaseX64/ embedder_example_1
# 基于 Dart 的基础设施,将 hello.dart 编译成 hello.dill
$ dart pkg/vm/bin/gen_kernel.dart \
--platform xcodebuild/ReleaseX64/vm_platform_strong.dill \
-o /tmp/hello.dill \
/tmp/hello.dart
# 用编译出的可执行文件,执行 hello.dill
$ ./xcodebuild/ReleaseX64/embedder_example_1 \
out/ReleaseX64/vm_platform_strong.dill \
/tmp/hello.dill
Основываясь на системе сборки, поставляемой с Dart VM, этот процесс может быть легко реализован. Но если вы хотите скомпилировать приведенную выше логику C++ в стороннем проекте, то помимо ручного подбора артефактов сборки Dart VMlibdartvm_for_embedding_nosnapshot_jit.a
В дополнение к статической библиотеке вам также необходимо скопировать эти заголовочные файлы для нормальной компоновки:
-
dart/runtime/include
Все заголовочные файлы в каталоге. -
dart/runtime/platform
Эти файлы заголовков в каталоге:-
assert.h
(вызовет конфликты Xcode, может быть переименованdart_assert.h
) floating_point.h
globals.h
hashmap.h
memory_sanitizer.h
-
После первого шага естественно попробовать кросс-компилировать статическую библиотеку для iOS. На данный момент существует странная проблема: Dart VM не предоставляет элементы конфигурации компиляции для iOS (точнее, естьis_ios
конфигурации, но его установка приведет только к сбою компиляции), а соответствующая документация не предоставляется, что мне делать? Эта проблема беспокоила меня долгое время, поэтому я прочитал много материалов о системах сборки GN и Ninja и даже пытался напрямую модифицировать генерируемую ими конфигурацию сборки Xcode, но безуспешно. Позже Вячеслав Егоров показал мне путь: полагаться на среду сборки Flutter для компиляции Dart.
Лично я до сих пор считаю такой подход неразумным, потому что вы сказали, что если я хочу скомпилировать V8, зачем мне полагаться на среду компиляции Chromium? Но на данном этапе мы можем сделать это только пока, а именно:
Сначала перейдите в сторонний каталог зависимостей Flutter Engine, найдите каталог dart и добавьте следующую конфигурацию сборки.runtime/bin/BUILD.gn
В файле:
static_library("libdartvm_with_utils") {
complete_static_lib = true # XXX
deps = [
":standalone_dart_io",
"..:libdart_jit",
"../platform:libdart_platform_jit",
"//third_party/boringssl",
"//third_party/zlib",
]
defines = [ "DEBUG" ]
sources = [
"builtin.cc",
"dartutils.cc",
"dart_embedder_api_impl.cc",
]
}
Затем используйте конфигурацию компиляции Flutter Engine для выполнения сборки:
# 在 Flutter Engine 的工作目录执行构建
$ ninja -C out/ios_debug_sim_unopt libdartvm_with_utils
Чтобы мы могли получить от продукта сборки Flutter Enginelibdartvm_with_utils.a
файл, это статическая библиотека Dart VM, к которой можно получить доступ на iOS (все зависимости добавляются сюда через перебор настроек, поэтому объем будет очень большим. Но несложно вручную настроить правила для оптимизации потом).
Встраивайте и запускайте Hello World в Dart
Со статической библиотекой, заголовочными файлами и записью C++ мы можем запустить виртуальную машину Dart отдельно на iOS. Но тут тоже нужно получить платформу iOS.dill
Форматировать двоичный файл ядра. как нам это сделать?
Если в соответствии с вышеизложеннымgen_kernel.dart
образом, код платформы также будет упакован в.dill
файл, для создания простейшего Hello World требуется несколько МБ объема. Более «экстремальный» способ — использовать Flutter. При запуске команды Flutter Run сначала компилируется.dill
файл, получить все статические ресурсы, а затем выполнить сборку приложения Xcode для iOS. Здесь Flutter Tool запустит службу компиляции CFE, к которой он подключается, и соответствующий продукт компиляции будет помещен во временный каталог системы, а его путь будет передан в виде сообщений межпроцессного взаимодействия, которые можно найти вflutter run -v
искать в логе.dill
оказаться.
Таким образом, весь экспериментальный процесс самостоятельного доступа к Dart VM на iOS примерно включает следующие шаги:
- Создайте новый пустой проект Flutter.
- Измените запись проекта Flutter на пустую Dart-версию Hello World, перехватите каталог компиляции Flutter Tool и вытащите его.
app.dill
документ. - Скомпилируйте Flutter Engine в продукте
vm_platform_strong.dill
файл из. - с ОС
pathForResource
метод, объединив два вышеуказанных.dill
Файл формата открывается какchar *
сформировать буфер и ввести их в демо-версию C++, которая ранее интегрировала Dart VM. - Выполните ту же функцию ввода Embedding Example C++.
Тогда первая попытка оказалась неудачной. С тех порflutter run
извлечено из команды.dill
Файл, который не может работать на симуляторе iOS.
Пропустите извилистое психическое путешествие здесь, и окончательный результат расследования удивительно прост: при наращивании среды трепетара два репозитория трепетания двигателя и трепетания должны использовать то же самое ревизию, в противном случае они не будут совместимы друг с другом. Если вы скомпилируетесь и используете трепетный механизм самостоятельно, то будут две версии DART, которые могут работать на машине, один находится в хосте, скомпилированном продуктом трепетания двигателя, а другой выходит из коробки с третком. Нам просто нужно использоватьdart --version
Убедитесь, что их версии совпадают.
После решения проблемы с версией вы можете успешно выполнить соответствующий Hello World.dill
файл. Конечный эффект успешного встраивания очень прост, как показано на рисунке:
В отличие от QuickJS, который вообще не имеет возможностей ввода-вывода, после успешной интеграции Dart VM можно будет нормально использовать асинхронный ввод-вывод и другие возможности. Итак, пока вы выполняете этот шаг, этот пример имеет хоть какую-то практичность. Однако, поскольку эта статья представляет собой только технико-экономическое обоснование, готового исходного кода проекта «из коробки» нет. Я рекомендую, если вы заинтересованы в том, чтобы попробовать это,Embedding ExamplesПатч основной ветки разработки в качестве отправной точки был бы проще.
Суммировать
Благодаря отсутствию документации и небольшого меньшинства Дартса, эксперимент довольно много объемов, также считается, что испытывает горстку Google - это то, как сделать «Олигархи с открытым исходным кодом»:Сам материал действительно очень достаточен, но глубина интеграции с собственными продуктами действительно высока.. Если Google сможет управлять Dart способом, который будет ближе к модели сообщества, текущий типичный случай использования Dart может быть не таким, как сегодня, почти ограниченным Flutter.
Но все мы знаем, что именно попытка Райана Даля вывести V8 из Chromium привела сегодня к Node.js. Итак, если такое отделение Dart VM от Flutter открывает новые возможности для сообщества? Например, для ХунмэнLiteOSМожет ли Dart VM быть лучшим решением для разработки приложений для класса встроенных систем, чем встроенный интерпретатор JS? Конечно, не все инновации подходят для следующего Node.js, но, по крайней мере, это стоит того.
Эта статья — только начало, когда речь заходит об интеграции Dart VM. Возможности виртуальной машины Dart весьма широки, и такие возможности, как инкрементная компиляция, горячая перезагрузка, прогрев моментальных снимков, компиляция AOT и удаленная отладка, нуждаются в дальнейшем изучении. Если вы заинтересованы в этом, пожалуйста, обратите внимание.