Компиляция TypeScript в нативный код

Static Hermes (shermes), следующая итерация JavaScript-движка hermes, добавляет поддержку компиляции TypeScript/flow в нативный код. Хотя shermes еще не выпущен, код доступен в репозитории на GitHub. В этой статье я скомпилирую простую программу на языке TypeScript в код на языке C. Для получения дополнительной информации смотрите это видео.

Компиляция shermes

На момент написания статьи для static hermes невозможно получить собранные двоичные файлы. Для компиляции из исходника на macos выполните следующие действия.

Получите исходник из репозитория GitHub и переключитесь на ветку static_h.

mkdir hermes_workspace
cd hermes_workspace
git clone [email protected]:facebook/hermes.git
cd hermes
git checkout static_h
cd ..

Убедитесь, что у вас есть указанные здесь зависимости для сборки. Затем в папке hermes_workspace выполните приведенные ниже команды для сборки. Обратите внимание, что эти команды выполняют сборку hermes в режиме отладки, который работает медленнее. Инструкции по сборке в режиме релиза приведены в конце статьи.

cmake -S hermes -B build -G Ninja
cmake --build ./build

Теперь в каталоге hermes_workspace/build/bin должен находиться двоичный файл shermes.

Компиляция файла TypeScript в C

Теперь создайте файл TypeScript в каталоге hermes_workspace. Его можно назвать main.ts.

function sum(args: number[]) {
let result: number = 0;
for (let index = 0; index < args.length; ++index) {
result += args[index];
}
}
t
const result = sum([1, 2, 3, 4, 5.1]);
print("=======");
print(result);

Выполните этот файл с помощью следующей команды:

./build/bin/shermes -typed -exec ./main.ts

Дайте указание shermes вывести скомпилированный код на языке C для этого файла TypeScript следующим образом:

./build/bin/shermes -typed -emit-c ./main.ts

Вывод на языке C, сгенерированный shermes, вполне читабелен, поскольку я (не являясь разработчиком на языке C) смог найти место, где создается массив и происходит добавление значений.

Массив, созданный в скомпилированном выводе на языке C:

np1 = _sh_ljs_double(0);
np4 = _sh_ljs_double(1);
locals.t0 = _sh_new_fastarray(shr, 5);
_sh_fastarray_push(shr, &np4, &locals.t0);
np0 = _sh_ljs_double(2);
_sh_fastarray_push(shr, &np0, &locals.t0);
np0 = _sh_ljs_double(3);
_sh_fastarray_push(shr, &np0, &locals.t0);
np0 = _sh_ljs_double(4);
_sh_fastarray_push(shr, &np0, &locals.t0);
np0 = _sh_ljs_double(((struct HermesValueBase){.raw = 4617428107952285286u}).f64);
_sh_fastarray_push(shr, &np0, &locals.t0);

Добавление значений, выполняемое в языке C:

L1:
;
// PhiInst
// PhiInst
np0 = _sh_fastarray_load(shr, &locals.t0, _sh_ljs_get_double(np2));
np3 = _sh_ljs_double(_sh_ljs_get_double(np3) + _sh_ljs_get_double(np0));
np2 = _sh_ljs_double(_sh_ljs_get_double(np2) + _sh_ljs_get_double(np4));
np0 = _sh_fastarray_length(shr, &locals.t0);
np0 = _sh_ljs_bool(_sh_ljs_get_double(np2) < _sh_ljs_get_double(np0));
np1 = np3;
if(_sh_ljs_get_bool(np0)) goto L1;
goto L2;
L2:
;
// PhiInst
goto L3;

Можно также дать указание shermes создать двоичный исполняемый файл, выполнив следующее:

./build/bin/shermes -typed -o a.out ./main.ts

Вызов C-функции из TypeScript

Вы можете скомпилировать собственные C-функции в библиотеку и затем связать их с программой на TypeScript с помощью shermes. Для этого сначала нужно написать определенную функцию на языке C.

Создайте следующие файлы:

- hermesworkspace
- build
- hermes
+ mydir
+ include
myfunc.h
+ lib
myfunc.c

В myfunc.h добавьте следующее:

#ifndef MYFUNC_H
#define MYFUNC_H
int exportedmethod(int);
#endif

В myfunc.c добавьте следующее:

#include <myfunc.h>
int exportedmethod(int a) {
return a + 2;
}

Теперь установите следующие переменные среды:

export CPATH=<your_filesystem>/workingdir_hermes/mydir/include
export LIBRARY_PATH=<your_filesystem>/workingdir_hermes/mydir/lib

Скомпилируйте программу на языке C из папки workingdir_hermes/mydir следующим образом:

cc -c -o lib/myfunc.o myfunc.c

В результате в папке workingdir_hermes/mydir/lib будет создан файл myfunc.o.

Теперь все готово, чтобы написать программу на языке TypeScript, которая будет вызывать функцию exportedmethod. Для этого создайте файл TypeScript со следующим содержанием. Замечу, что я не понимаю, зачем нужно { throw 0; } в конце объявления функции. Я попробовал это сделать, и у меня все получилось.

const _myfunc = $SHBuiltin.extern_c({ include: "myfunc.h" }, function exportedmethod(input: c_int): c_int {
throw 0;
});
const result = _myfunc(40);
print(result);

Для запуска необходимо использовать флаг l, чтобы shermes образовал связку с библиотекой. Запуск произведите следующим образом:

./build/bin/shermes -typed -exec -lmyfunc.o ./yourfile.ts

В результате должно быть выведено 42.

Дополнительные примеры

Дополнительные примеры использования можно найти в папке hermes/examples/ffi. Там есть кейсы, которые были показаны в ролике в начале статьи.

Как вы уже, вероятно, догадались, я не имею никакого отношения к проекту hermes. Видеоролик с анонсом вызвал у меня интерес к static hermes, и мне захотелось поделиться шагами, которые я выполнил, чтобы его протестировать.

Обновления

Мне стало известно, что команды, которые я использовал выше для сборки hermes, собирают его в режиме отладки, что медленнее, чем в режиме релиза. Для сборки в режиме релиза можно использовать следующее:

cmake -S hermes -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build ./build_release
  • Опция -emit-c  —  это временная возможность, предназначенная для облегчения разработки hermes.
  • Поддержка TypeScript все еще находится на экспериментальном этапе и не всегда способна ответить на все вопросы, но в настоящее время ее качество улучшается.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Gaurav Gautam: Compiling Typescript to native code

Предыдущая статьяГлубокое погружение в Java: рефлексия и загрузчик классов. Часть 2
Следующая статьяГлубокое погружение в режим Copy-on-Write в pandas. Часть 3