Могу ли я каким-то образом создать код веб-сборки * без * клея emscripten?

Могу ли я каким-то образом создать файл wasm, который будет работать самостоятельно, как описано в MDN здесь (путем установки объектов и вызывающих функций на их)?

Все направляющие, которые я могу найти (например, этот в MDN), рекомендуем использовать emscripten; что, однако, также включает ~ клейкий код ~ 70 КБ (с эмуляцией дополнительной эмуляции ~ 50 кбайт), которая имеет дополнительную логику (например, обнаружение node/среда браузера и автоматическое извлечение и т.д.) и, возможно, некоторые другие эмуляции.

Что делать, если я не хочу, чтобы этот "код клея" и хотел просто создать WASM напрямую (возможно, из кода C, но, возможно, что-то еще)? Возможно ли это сейчас?

Ответ 1

Вы можете использовать emscripten для генерации минимального вывода кода.

Рассмотрим следующий тривиальный файл adder.c:

int adder (int a, int b) {
    return a + b;
}

Скомпилируйте его следующим образом (требуется довольно свежая версия):

emcc -O2 -s WASM=1 -s SIDE_MODULE=1 -o adder.wasm

Чтобы увидеть, что он сгенерировал, разберите его для обработки текстовой формы с помощью двоичного кода wasm-dis (вы также можете использовать wasm2wast из wabt):

wasm-dis adder.wasm -o adder.wast

Разобранный источник должен выглядеть примерно так:

(module
 (type $0 (func (param i32 i32) (result i32)))
 (type $1 (func))
 (import "env" "memoryBase" (global $import$0 i32))
 (import "env" "memory" (memory $0 256))
 (import "env" "table" (table 0 anyfunc))
 (import "env" "tableBase" (global $import$3 i32))
 (global $global$0 (mut i32) (i32.const 0))
 (global $global$1 (mut i32) (i32.const 0))
 (export "__post_instantiate" (func $2))
 (export "runPostSets" (func $1))
 (export "_adder" (func $0))
 (func $0 (type $0) (param $var$0 i32) (param $var$1 i32) (result i32)
  (i32.add
   (get_local $var$1)
   (get_local $var$0)
  )
 )
 (func $1 (type $1)
  (nop)
 )
 (func $2 (type $1)
  (block $label$0
   (set_global $global$0
    (get_global $import$0)
   )
   (set_global $global$1
    (i32.add
     (get_global $global$0)
     (i32.const 5242880)
    )
   )
   (call $1)
  )
 )
 ;; custom section "dylink", size 5
)

Затем вы можете запустить это на узле (v8.X или новее) следующим образом:

const WA = WebAssembly,
      env = {memoryBase: 0,
             tableBase: 0,
             memory: new WA.Memory({initial: 256}),
             table: new WA.Table({initial: 0, element: 'anyfunc'})},
      code = new Uint8Array(require('fs').readFileSync('adder.wasm'))
WA.compile(code).then(m => {
    return new WA.Instance(m, {env: env})
}).then(i => {
    console.log(i.exports._adder(7, 8))
})

Обратите внимание, что если вы хотите поддерживать код, который использует стек и/или кучу памяти, все усложняется. То есть по крайней мере вам нужно будет установить memoryBase и вызвать __post_instantiate из среды вашего хоста, прежде чем вызывать любой другой экспорт.

Если вы хотите интерпретировать код WebAssembly без среды JavaScript, вы можете запустить его с помощью wac/wace (полное описание: я создал этот проект). Обратите внимание, что wace предполагает, что у вас определена функция "_main" или "main".

Ответ 2

Вы можете, и со временем это станет легче!

Если вы хотите полностью исключить С++, возможно создать модули WebAssembly, например, как это сделано в spec tests или набор тестов WebKit.

Даже с С++ вы можете, без Emscripten. wasm-stat.us делает это, например. тесты GCC по пыткам. Проверьте выход сборки или посмотрите на его источник.

Например, для компиляции/ссылки/сборки будет выполнено следующее:

# Get a .o file:
src/work/wasm-install/bin/clang src/work/gcc/gcc/testsuite/gcc.c-torture/execute/20020227-1.c -o src/work/torture-o/20020227-1.c.o --std=gnu89 -DSTACK_SIZE=1044480 -w -Wno-implicit-function-declaration --target=wasm32-unknown-unknown-wasm -c -O2 --sysroot=src/work/wasm-install/sysroot
# Link with libc:
src/work/wasm-install/bin/lld -flavor wasm -entry=main --allow-undefined-file=src/work/wasm-install/sysroot/lib/wasm.syms -o src/work/torture-lld-musl/20020510-1.c.o.wasm src/work/torture-o/20020510-1.c.o src/work/wasm-install/sysroot/lib/libc.a
# Or without a libc (you need to provide one somehow):
src/work/wasm-install/bin/lld -flavor wasm -entry=main --allow-undefined-file=src/work/wasm-install/sysroot/lib/wasm.syms -o src/work/torture-lld/20020510-1.c.o.wasm src/work/torture-o/20020510-1.c.o

# Or, if you want an assembly file instead:
src/work/wasm-install/bin/clang src/work/gcc/gcc/testsuite/gcc.c-torture/execute/20020227-1.c -o src/work/torture-s/20020227-1.c.s --std=gnu89 -DSTACK_SIZE=1044480 -w -Wno-implicit-function-declaration --target=wasm32-unknown-unknown -S -O2 --sysroot=src/work/wasm-install/sysroot
# And get the binary file:
src/work/wasm-install/bin/wast2wasm src/work/torture-s2wasm/loop-6.c.s.wast -o src/work/torture-wast2wasm/loop-6.c.s.wast.wasm

Вы даже можете загрузить все артефакты для создания водопада, включая полные инструментальные цепочки. Просто нажмите на зеленую рамку и найдите нужную загрузку.

Если вы чувствуете смелость, вы можете даже написать свой libc в JavaScript вместо ссылки на существующую реализацию, написанную на C.

Когда вы говорите "самостоятельно", помните, что WebAssembly в настоящее время не может ничего сделать, не связываясь с его вложением (то есть JavaScript). Модель Emscripten следует (и я ожидаю, что другие будут делать то же самое) заключается в том, что JavaScript является микроядром и предоставляет системные вызовы.

Ответ 3

Вы можете использовать флаг ONLY_MY_CODE, это будет генерировать только модуль wasm без glue.js, например.

emcc -O1 ./src/foo.cpp -o release/foo.wasm -s WASM=1 -s ONLY_MY_CODE=1

Из Settings.js https://github.com/kripken/emscripten/blob/master/src/settings.js#L583:

var ONLY_MY_CODE = 0; // This disables linking and other causes of adding extra code
                      // automatically, and as a result, your output compiled code
                      // (in the .asm.js file, if you emit with --separate-asm) will
                      //  contain only the functions you provide.

Ответ 4

LLVM теперь поддерживает прямую компиляцию C в wasm с использованием WASI. Emscripten больше не нужен.

Если libc не требуется, вы можете использовать LLVM прямо из коробки. Например, файл foo.c можно скомпилировать с помощью:

clang --target=wasm32 --no-standard-libraries -Wl,--export-all -Wl,--no-entry -o foo.wasm foo.c

В противном случае проект WASI-libc имеет автономный libc, который можно использовать.

Полная процедура для компиляции C в WebAssembly с LLVM и запуска ее в браузере доступна в этом посте.