C SDK

0.4 NEW — пишем плагин на C, компилируем через clang из wasi-sdk. Single-header SDK + одна .c реализация.

Установка тулчейна

# wasi-sdk 25 для arm64 macOS:
curl -L -o wasi-sdk.tar.gz \
  https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz
mkdir -p ~/wasi-sdk
tar -xzf wasi-sdk.tar.gz -C ~/wasi-sdk --strip-components=1
~/wasi-sdk/bin/clang --version

Для Linux/x86_64 — другой архив; см. WebAssembly/wasi-sdk releases.

Apple clang (Xcode) не подойдёт — у него нет wasi-libc и поддержки wasm32-wasi.

Минимальный плагин

wasm_modules/hello-c/manifest.json:

{
  "id": "hello_c",
  "name": "Hello (C)",
  "version": "0.1.0",
  "target": "server",
  "language": "c",
  "abi_version": 1
}

wasm_modules/hello-c/main.c:

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "lampac.h"

__attribute__((export_name("alloc")))
void *alloc(uint32_t size) { return lampac_alloc(size); }

__attribute__((export_name("abi_version")))
uint32_t abi_version(void) { return 1; }

__attribute__((export_name("handle")))
uint64_t handle(uint32_t ptr, uint32_t length) {
    lampac_invocation inv = lampac_read_invocation(ptr, length);
    lampac_info("hello from C");

    uint32_t qlen = 0;
    const char *q = lampac_query_get(&inv, "q", &qlen);

    char body[256];
    int n = snprintf(body, sizeof(body),
        "{\"type\":\"movie\",\"data\":[{\"name\":\"Hello, %.*s\"}]}",
        (int)qlen, q ? q : "");
    return lampac_write_response(body, (uint32_t)n);
}

wasm_modules/hello-c/Makefile:

WASI_SDK ?= $(HOME)/wasi-sdk
CLANG    := $(WASI_SDK)/bin/clang
SDK_DIR  := ../../wasm_sdk/c

CFLAGS  := --target=wasm32-wasi -mexec-model=reactor -O2 -nostartfiles -I$(SDK_DIR)
LDFLAGS := -Wl,--no-entry \
           -Wl,--export=alloc \
           -Wl,--export=handle \
           -Wl,--export=abi_version

build:
	$(CLANG) $(CFLAGS) $(LDFLAGS) -o plugin.wasm $(SDK_DIR)/lampac.c main.c

Сборка:

WASI_SDK=$HOME/wasi-sdk make build
# Output: plugin.wasm — ~120 КБ

Что есть в SDK

wasm_sdk/c/lampac.h объявляет, lampac.c реализует:

Логирование

lampac_debug("…");
lampac_info("…");
lampac_warn("…");
lampac_error("…");
// или с явной длиной:
lampac_log(LAMPAC_INFO, ptr, len);

HTTP

lampac_http_request req = {
    .method = "GET",          // optional, default "GET"
    .url = "https://api.example.com/x",
    .body = NULL,             // optional
    .timeout_ms = 5000,
};
lampac_http_response resp = lampac_http(req);
// resp.status, resp.body (uint8_t*, body_len), resp.err

Proxy URL

const char *url = lampac_proxy_url("https://cdn.example.com/m.m3u8", "hello_c");
// pointer valid for the rest of this handle() call

Кеш

const char *value = "hello";
lampac_cache_set("key", 3, (const uint8_t *)value, 5, 60);

uint32_t out_len = 0;
const uint8_t *cached = lampac_cache_get("key", 3, &out_len);

Invocation

lampac_invocation inv = lampac_read_invocation(ptr, length);
// inv.raw, inv.path, inv.request_ip, inv.config_json
uint32_t qlen = 0;
const char *q = lampac_query_get(&inv, "id", &qlen);
const char *url = lampac_json_string(inv.config_json, inv.config_len, "apiHost", &qlen);

Ответ

return lampac_write_response(json_bytes, json_len);

Размер

C-плагин уровня echo собирается в ~120 КБ с -O2. Сравнимо с Rust release; больше Zig из-за wasi-libc’овых деталей (snprintf, memcpy и т.д. тащат немного кода).

Можно сократить ещё:

  • -Oz вместо -O2 (~85 КБ)
  • --no-standard-libraries если откажешься от libc и напишешь свои memcpy/snprintf (~25 КБ)

Подводные камни

  1. -mexec-model=reactor обязателен — это говорит clang эмитить _initialize вместо _start, иначе host вызовет _start и модуль закроется после первого вызова.
  2. __attribute__((export_name("…"))) для каждого экспорта — иначе clang эмитит имена с __ префиксом, host их не найдёт.
  3. memmem отсутствует в wasi-libc — SDK реализует свой lampac_memmem. Если копируете код из glibc-окружения, заменяйте.
  4. Bump arena 64 КиБ — при переполнении SDK падает в malloc(), но это медленно и тащит лишний код. Если плагин делает много мелких аллокаций, поднимите LAMPAC_ARENA_SIZE в lampac.c.

Полный пример

См. wasm_modules/echo_c — все host-импорты разом, тот же контракт что у других echo-плагинов.

Browser Studio

WASM Studio поддерживает C из коробки — выбирай c в dropdown’е и кодь в Monaco. Бэкенд дёргает clang из $WASI_SDK (по умолчанию $HOME/wasi-sdk).