Zig SDK

0.4 NEW — пишем плагин на Zig, компилируем через zig build. Самые компактные .wasm в нашем стеке (~12 КБ для echo).

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

# precompiled бинарь (быстрее, чем brew install zig — тот собирает llvm)
curl -L -o zig.tar.xz https://ziglang.org/download/0.13.0/zig-macos-aarch64-0.13.0.tar.xz
mkdir -p ~/zig && tar -xf zig.tar.xz -C ~/zig --strip-components=1
~/zig/zig version    # 0.13.0

Поддерживаются 0.13+. На Linux замените macos-aarch64 на linux-x86_64 / linux-aarch64.

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

wasm_modules/hello-zig/manifest.json:

{
  "id": "hello_zig",
  "name": "Hello (Zig)",
  "version": "0.1.0",
  "target": "server",
  "language": "zig",
  "abi_version": 1
}

wasm_modules/hello-zig/main.zig:

const std = @import("std");
const lampac = @import("lampac");

export fn alloc(size: u32) [*]u8 { return lampac.alloc(size); }
export fn abi_version() u32 { return 1; }

export fn handle(ptr: u32, length: u32) u64 {
    const inv_ptr: [*]const u8 = @ptrFromInt(@as(usize, @intCast(ptr)));
    const inv_raw = inv_ptr[0..length];
    lampac.info("hello from zig");

    var out = std.ArrayList(u8).init(lampac.arena());
    defer out.deinit();
    out.appendSlice("{\"type\":\"movie\",\"data\":[{\"name\":\"Hello, ") catch return 0;
    const q = lampac.queryGet(inv_raw, "q");
    out.appendSlice(q) catch return 0;
    out.appendSlice("\"}]}") catch return 0;
    return lampac.writeResponse(out.items);
}

wasm_modules/hello-zig/build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi });
    const exe = b.addExecutable(.{
        .name = "plugin",
        .root_source_file = b.path("main.zig"),
        .target = target,
        .optimize = .ReleaseSmall,
    });
    exe.root_module.addAnonymousImport("lampac", .{
        .root_source_file = b.path("../../wasm_sdk/zig/lampac.zig"),
    });
    exe.entry = .disabled;
    exe.rdynamic = true;
    b.installArtifact(exe);
}

Сборка:

~/zig/zig build
cp zig-out/bin/plugin.wasm ./

API SDK

wasm_sdk/zig/lampac.zig экспортирует:

Логирование

lampac.debug("...");
lampac.info("...");
lampac.warn("...");
lampac.err("...");

Аллокатор

lampac.alloc(size: u32) [*]u8     // обязательный экспорт плагина — обёртывает arena().alloc(...)
lampac.arena() std.mem.Allocator  // 64 KiB FixedBufferAllocator для всех аллокаций в одном handle()

HTTP

const resp = lampac.http(.{
    .url = "https://api.example.com/x",
    .method = "GET",        // optional
    .timeout_ms = 5000,
});
// resp.status, resp.body, resp.err

Proxy URL

const url = lampac.proxyURL("https://cdn.example.com/m.m3u8", "hello_zig");

Кеш

lampac.cacheSet("key", "value", 60);
if (lampac.cacheGet("key")) |val| {
    // val: []const u8
}

Response

return lampac.writeResponse(bytes);    // []const u8

Размер

-O ReleaseSmall + exe.entry = .disabled даёт ~12 КБ для echo-плагина. Это самый компактный плагин в нашем сравнении — Zig не тащит ни runtime, ни heap allocator.

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

  1. Не делайте arena_fba.reset() в начале handle — invocation bytes лежат в той же arena, и сброс уничтожит их. Хост перезатирает invocation при следующем вызове, так что arena плавно “перезаписывается” без явного reset.

  2. Slice через arena живут только до следующего host_ вызова*. Если вы извлекли строку из inv_raw и потом вызвали lampac.http(), slice может стать невалидным — копируйте в стек/локальный буфер ДО host-вызовов:

    var greet_buf: [128]u8 = undefined;
    var greet: []const u8 = "default";
    const g = lampac.parseTopLevelString(inv_raw, "greet");
    if (g.len > 0 and g.len < greet_buf.len) {
        @memcpy(greet_buf[0..g.len], g);
        greet = greet_buf[0..g.len];
    }
    const resp = lampac.http(...);  // <-- только теперь, greet уже скопирован
  3. 64 KiB arena cap — поднять можно через LAMPAC_ARENA_SIZE в SDK; OOM сейчас вызывает @panic (плагин крэшнется, host запишет dump).

  4. Zig 0.14+ — API b.resolveTargetQuery мог измениться; build.zig нужно будет адаптировать.

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

См. wasm_modules/echo_zig — все host-импорты, тот же контракт что у echo (TinyGo) / echo_as / echo_c.

Сравнение размеров

Тулчейн echo плагин
TinyGo 416 КБ
Rust release ~150 КБ
C (wasi-sdk) 123 КБ
AssemblyScript 15 КБ
Zig 12 КБ