Tegyük fel, hogy felmerül az igény a ~-ra, azaz hogy egy programból kell tudnunk hívni WASM eljárásokat, és vice-versa, azoknak is kell hívniuk a mi natív függvényeinket. Na erre nem találtam épkézláb megoldást.
- Az egyik ilyen a wasm-micro-runtime, de hogy ez mennyire szar, azt semmi sem szemlélteni jobban, minthogy félezer fájl, 9 MEGA az nekik "micro"... Hát nekem hányadék bloatware.
- Ott volt aztán még a wasm3, ami méretét tekintve már inkább elfogadható, viszont agyfasz az API-ja, és sajnos nincs már karbantartva, mert a fejlesztőjének a házát szarrábombázták a ruszkik.
- Van még a Linux kernelbe ágyazható cervus, szintén elhalt, 7 éve bottal sem piszkálták, pedig nyitott jegyek vannak rajta.
- ...stb.
És akkor ott van még az is, hogy ezek egyike sem biztonságos, mert ezekkel BÁRMILYEN dll / so dinamikusan betölthető és futtatható, így a beágyazó programnak esélye sincs kordában tartani a benne futó szkriptet. Szóval nem az igazi.
Egy szónak is száz a vége, már megint az lett, hogy megírtam a libet magamnak, mert amit mindenki más használ, az nekem nem tetszik. Hát, ez van.
https://gitlab.com/bztsrc/wa
Előnyei:
- ha az első a micro, na akkor ez nano, merthogy 9.5 Mega helyett csak 116 Kilobájt az egész (kb. 1900 SLoC)
- mégis megvalósítja a teljes WASM MVP (Minimal Viable Product) szabványt
- egyetlen függőségmentes ANSI C / C++ fejlécfájl (na jó, libm kell neki, de a WA_NOLIBM define kikapcsol két utasítást és úgy már tényleg csak compiler built-inek elegek)
- pofonegyszerűen használható, faék egyszerű az API-ja
- maximális kontroll afelett, hogy mit csinálhatnak a szkriptek (mennyi memóriát foglalhatnak, milyen függvényeket hívhatnak stb.)
- bound check és hasonló nyalánságok (futás idejű típusellenőrzésen kívül szinte minden)
- baromi gyors, a többihez képest lobog a hajad tőle (két okból: bulk memóriafoglalás kev;s bufferre; és az értelmetlen típusellenőrzés helyett (ami O(N)) csak az elemszámot ellenőrzni (ami O(1))
- a többi megoldás memóriaigényének 10%-val is beéri (nem, nem írtam el, ugyanazt a wasm modult futtatni tized annyi memóriát fogyaszt)
- a többivel ellentétben memleak mentes (ez ráadásul garantált, mivel nem foglalgat agyba-főbe kis memóriablokkokat, hanem mindössze egy fél maréknyi buffert használ)
- nem támogat semmiféle emscripten, WASI meg hasonló 3rd party baromságot, így a szkript nem tud kitörni a sandboxából. Nincs mivel.
- fordítható debugger támogatással, példa wadbg debugger GUI applikáció is van hozzá, ennek demonstrálására (kicsi SDL-es, portolható cucc).
Példa használatra:
/* függvénykönyvtár behúzása, stb módra, nem kell linkelni semmivel */
#define WA_IMPLEMENTATION
#include "wa.h"
/* wasm bináris bitkolbásza, fordította "Clang --target=wasm32", yours truly */
uint8_t wasmbinary[1234] = { ... };
/* futás idejű linkelő tábla és kontextus */
RTLink link[] = {
/* név ki be proto */
{ "malloc", &malloc, 0, WA_ll }, /* host -> WASM */
{ "realloc", &realloc, 0, WA_lll }, /* host -> WASM */
{ "free", &free, 0, WA_vl }, /* host -> WASM */
{ "add", NULL, -1, WA_fff }, /* WASM -> host */
{ "sub", NULL, -1, WA_fff }, /* WASM -> host */
{ "globvar", &globvar, 0, WA_i }, /* megosztott változó */
{ 0 }
};
Module m;
/* betöltés, inicializálás */
wa_init(&m, wasmbinary, 1234, link);
/* egy WASM függvény hívása C-ből. Elöször kikeressük a szimbólumot (ha nem felejtettük el,
hogy a link[] tömb hányadik sora, akkor keresgélés helyett csak simán O(1) lekérés) */
int addfunc = cavinton_klub? wa_sym(&m, "add") : link[3].fidx;
/* bemeneti paraméterek átadása és hívás */
wa_push_f32(&m, 1.0);
wa_push_f32(&m, 2.0);
ret = wa_call(&m, addfunc);
printf("Eztetet adta vissza: %f\n", ret.f32);
/* többet nincs mit elmondani, kérem kapcsoljaki */
wa_free(&m);
Ennyi. Ennél faékebb egyszerűségű és könnyebben integráható API-t nem bírtam kiagyalni. Négy függvény init / push / call / free és kész (oké, a push-ból van több változat, mindenféle típushoz, de akkor is 12 függvény cakk-um-pakk). A valós idejű linkelés meg egy struct tömbbel zajlik, aminek négy mezője van:
- első a szimbólum (UTF-8 sztring)
- második egy memóriacím, ezt te adod meg akkor, ha a WASM-nek akarod átadni
- harmadik egy függvényindex, ezeket meg a WASM adja meg, hogy meg tudd hívni
- negyedik meg egy prototípust kódoló bitmaszk
Bármelyik globális WASM változó láthatóvá tehető host oldalon, ha felvesszük a táblába a WASM szimbólum nevét egy host memóriacímmel. Másik irány is működik, ekkor "extern"-nek kell definiálni a WASM modulban.
De ami biztonság szempontjából nagyon fontos, a WASM szkript nem tud semmi mást meghívni, csak azokat, amik szerepelnek a listán.
Viszont ott bármilyen függvénnyel működik, de azért lehetőség van saját dispatcher megadására is egy WA_DISPATCH define-nal.
Szabad és Nyílt Forráskódú, licensze a megengedő MIT.