ndrean/zig-assembly-test
Zig compiled to WebAssembly rendered in Phoenix Liveview
Zig compiled to WebAssembly rendered in Phoenix Liveview and as a standalone app.
WebAssembly
code:cd zoomzig
zig build
cd mandelbrot
mix copy && mix phx.server
https://ndrean.github.io/zig-assembly-test/
The code will return a slice that corresponds to the RGBA values of each pixel.
In the "build.zig", we set .max_memory = std.wasm.page_size * 128
.
We set a variable global_colours
. The Zig
will populate this slice.
To compile to WebAssembly
, we:
export fn ...
try
. Use catch unreached
.void
or numbers
.getColoursPointer
, and another one with its length with getColoursSize
.var global_colours: ?[]u8 = null;
const allocator = std.heap.wasm_allocator;
export fn allocMemory(len: usize) ?[*]u8 {
return if (allocator.alloc(u8, len)) |slice|
slice.ptr
else |_|
null;
}
export fn getColoursPointer() *u8 {
// Expose the colours array to the host
return &global_colours.?.ptr[0];
}
export fn getColoursSize() usize {
return global_colours.?.len;
}
Run a Mix task to copy the "zoom.wasm" file into the assets folder.
The call WebAssembly.instantiateStreaming
asks for a content-type "application/wasm".
We serve the wasm file with an endpoint defined in the router.
pipeline :api do
plug :accepts, ["wasm"]
end
scope "/", MandelzoomWeb do
pipe_through :api
get "/wasm", WasmController, :load
end
Phoenix
appends by default sets "charset=utf8" to the Content-Type and WebAssembly
does not want this.
We overwrite the resp_headers
:
(https://elixirforum.com/t/content-type-for-custom-binary-format/60452)
conn =
%Plug.Conn{conn | resp_headers: [{"content-type", "application/wasm"} | conn.resp_headers]}
The code is call via a hook, MandelbrotViewer
.
The key points:
std.heap.wasm_alloator
).instance.exports.<function_name>
WebAssembly
. We named our main Zig
function "initilize" which receives only numbers and return void
.const WASM_PAGE_SIZE = 65536; // 64kB
const MAXIMUM_PAGES = 150;
const INITAIL_PAGES = 60;
const cols = this.canvas.width,
rows = this.canvas.height,
bytesNeeded = cols * rows * 4;
const pagesNeeded = Math.ceil(bytesNeeded / WASM_PAGE_SIZE);
this.memSize = pagesNeeded;
const initialPages = Math.max(pagesNeeded, INITIAL_PAGES);
memory = new WebAssembly.Memory({initial: initialPages, maximum: MAXIMUM_PAGES});
const {instance } = await WebAssembly.instantiateStreaming(fetch("/wasm"), { env: {memory}});
instance.exports.allocMemory(this.memSize);
instance.exports.initilize(eows, cols...);
To fill in the canvas, we:
new Uint8ClampedArray
that will receive the WebAssembly data from the memory address with a given length.ImageData
from this datacreateImageBitmap
The Elixir
library Orb can produce a WAT (text).
To compile to WASM binary, use WABT.
Wasmex can run Wasi in Elixir.
# /pages
ln -s ../zoomzig/zig-out/bin/zoom.wasm
Then:
git subtree push --prefix pages origin gh-pages
et voilà: