Языки программирования

Запуск C из JavaScript

Запуск C из JavaScript

У серверного JavaScript-движка Bun появился новый трюк. Вы предоставляете ему файл на языке C, и он позволяет вам вызывать функции C из JavaScript. Гений или безумие?

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

Так называемые внешние функциональные интерфейсы (Foreign Function Interfaces или FFI) широко распространены, но не так часто встречаются в JavaScript из-за необходимости запускать его в браузере. Если JavaScript может выполнять код на C, то он может выйти из песочницы браузера и получить доступ к любой части системы.

Несколько лет назад Google добавила возможность запускать C/C++ в Chrome с помощью NaCl Native Client. Это было здорово для разработчиков, но ужасно для безопасности, и в 2017 году от этой возможности отказались, поскольку в качестве альтернативы была обещана WebAssembly. Идея заключалась в том, что WebAssembly - это язык более низкого уровня, который может работать наряду с JavaScript, который был быстрым, и обеспечивал бы такую же безопасность, работая в той же песочнице.

Как это поможет при выполнении кода на C? Легко, вы просто компилируете код на C в WebAssembly и используете его вместо настоящей библиотеки C. Проблема в том, что это дополнительная работа, и она не особенно стабильна.

Для браузерного JavaScript-движка это действительно единственный вариант, но для серверного движка, такого как Node или Bun, есть альтернатива. Движок V8 JavaScript, используемый в Chrome, имеет N-API (Native API), который позволяет вызывать функции C. Существует ряд проблем с использованием napi, но главное возражение, похоже, заключается в том, что он медленный и громоздкий.

Теперь Джарред Самнер, работающий над Bun, придумал идею получше. Расширение синтаксиса JavaScript для вызова компилятора C, который затем используется для компиляции функций C и создания оберток для обмена данными. Например, если у вас есть функция на Си, которая печатает «hello world», вы можете вызвать ее из JavaScript, используя:

import { cc } from "bun:ffi";

export const {
  symbols: { hello },
} = cc({
  source: "./hello.c",
  symbols: {
    hello: {
      returns: "void",
      args: [],
    },
  },
});

hello();

Это компилирует код на языке C в hello.c и предоставляет информацию о его параметрах и типе возврата. После компиляции функцию можно вызывать, как если бы она была функцией JavaScript.

В основе этого волшебства лежит программа, которую я не видел уже много лет - TinyCC. Этот удивительный маленький ранний компилятор для языка C был основой разработки до появления оптимизирующих компиляторов вроде GCC. TinyCC - это очень быстрый компилятор, который движок JavaScript использует для компиляции языка C в машинный код. Простая функция компилируется за миллисекунды. После компиляции C утверждается, что она быстро вызывается, а накладные расходы на вызов из C в C составляют всего 2 нс при 6 нс.

Код на C выполняется в том же пространстве памяти, что и движок JavaScript, и поэтому должен быть достаточно безопасным - тем более что он загружает код только из безопасного, как мы надеемся, локального источника. Но даже в этом случае, я думаю, C достаточно способен найти способы делать непристойные вещи, если представится такая возможность.

Я вижу, что это может быть полезно, если у вас есть несколько интерфейсных функций для более крупной реализации, но я не думаю, что перекомпиляция большого набора функций каждый раз, когда они нужны, будет хорошим способом работы. По сути, проблема заключается в том, чтобы обеспечить хороший способ работы JavaScript с предварительно скомпилированным кодом на C, и это не поможет.

Удивительно, как все крутится вокруг - TinyCC я никогда не думал, что увижу снова.

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