Zig SDK
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.errProxy 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.
Подводные камни
-
Не делайте
arena_fba.reset()в начале handle — invocation bytes лежат в той же arena, и сброс уничтожит их. Хост перезатирает invocation при следующем вызове, так что arena плавно “перезаписывается” без явного reset. -
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 уже скопирован -
64 KiB arena cap — поднять можно через
LAMPAC_ARENA_SIZEв SDK; OOM сейчас вызывает@panic(плагин крэшнется, host запишет dump). -
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 КБ |