{"id":21699528,"url":"https://github.com/ndrean/zig-assembly-test","last_synced_at":"2026-05-02T23:34:50.208Z","repository":{"id":264536717,"uuid":"893619884","full_name":"ndrean/zig-assembly-test","owner":"ndrean","description":"Zig compiled to WebAssembly rendered in Phoenix Liveview","archived":false,"fork":false,"pushed_at":"2024-11-28T05:02:31.000Z","size":344,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-25T14:42:32.923Z","etag":null,"topics":["mandelbrot-viewer","phoenix","phoenix-liveview","webassembly","zig"],"latest_commit_sha":null,"homepage":"https://ndrean.github.io/zig-assembly-test/","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndrean.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-24T22:25:19.000Z","updated_at":"2024-11-28T05:02:34.000Z","dependencies_parsed_at":"2024-11-24T23:36:15.605Z","dependency_job_id":null,"html_url":"https://github.com/ndrean/zig-assembly-test","commit_stats":null,"previous_names":["ndrean/zig-assembly-test"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fzig-assembly-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fzig-assembly-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fzig-assembly-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fzig-assembly-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndrean","download_url":"https://codeload.github.com/ndrean/zig-assembly-test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244643472,"owners_count":20486628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mandelbrot-viewer","phoenix","phoenix-liveview","webassembly","zig"],"created_at":"2024-11-25T20:09:57.842Z","updated_at":"2025-10-17T12:46:19.443Z","avatar_url":"https://github.com/ndrean.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zig-assembly-test\nZig code compiled to WebAssembly and rendered in Phoenix Liveview and as a standalone app.\n\nThe Zig code will compute some RGBA values for each point of a quantitized area of the 2D-plan. We have a canvas whose pixels  `(i,j)` are in correspondance with a point `(x,y)` of the 2D-plane.\n\n- Build the `WebAssembly` code:\n\n```sh\ncd zoomzig\nzig build\n```\n\n- Render in Phoenix LiveView:\n\n```sh\ncd mandelbrot\nmix copy \u0026\u0026 mix phx.server\n```\n\n- Render by GitHub pages as a standalone app:\n\n\u003chttps://ndrean.github.io/zig-assembly-test/\u003e\n\n## Zig\n\nThe code will return a slice that corresponds to the RGBA values of each pixel.\n\nIn the \"build.zig\", we set `.max_memory = std.wasm.page_size * 128`. \n\nWe set a variable `global_colours`. The `Zig`  will populate this slice.\n\nTo compile to `WebAssembly`, we:\n- use `export fn ...`\n- pass only numbers as arguments\n- can't return error union, thus no `try`. Use `catch unreached`.\n- function returns `void` or `numbers`.\n- to return the \"colours slice\", we build a function to return the address of the first element of this memory block with `getColoursPointer`, and another one with its length with `getColoursSize`.\n\n```wasm\nvar global_colours: ?[]u8 = null;\nconst allocator = std.heap.wasm_allocator;\n\nexport fn allocMemory(len: usize) ?[*]u8 {\n    return if (allocator.alloc(u8, len)) |slice|\n        slice.ptr\n    else |_|\n        null;\n}\n\n\nexport fn getColoursPointer() *u8 {\n    // Expose the colours array to the host\n    return \u0026global_colours.?.ptr[0];\n}\n\nexport fn getColoursSize() usize {\n    return global_colours.?.len;\n}\n```\n\n## Elixir\n\nRun a Mix task to copy the \"zoom.wasm\" file into the assets folder.\n\nThe call `WebAssembly.instantiateStreaming` asks for a content-type \"application/wasm\".\n\n### Serve the \"wasm\" file to the client\n\nWe serve the _wasm_ file with an endpoint defined in the router.\n\n```elixir\npipeline :api do\n    plug :accepts, [\"wasm\"]\nend\n\nscope \"/\", MandelzoomWeb do\n    pipe_through :api\n    get \"/wasm\", WasmController, :load\nend\n```\n\n`Phoenix` appends by default sets \"charset=utf8\" to the Content-Type and `WebAssembly` does not want this.\n\nWe overwrite the `resp_headers`:\n\n(\u003chttps://elixirforum.com/t/content-type-for-custom-binary-format/60452\u003e)\n\n```elixir\n conn = \n    %Plug.Conn{conn | resp_headers: [{\"content-type\", \"application/wasm\"} | conn.resp_headers]}\n```\n\n## Javascript - WebAssembly\n\nThe code is call via a hook, `MandelbrotViewer`.\n\nThe key points:\n- instantiate a \"memory\" for WebAssembly,\n- we let Javascript pass the quantity of memory to allocate to WebAssembly as we provided a function in Zig to allocate memory for the slice (with `std.heap.wasm_alloator`).\n- fetch the wasm file (Phoenix will serve it)\n- we called the WebAssembly module \"instance\" here\". Call the WebAssembly functions with `instance.exports.\u003cfunction_name\u003e`\n- pass only numbers (integers, floats) to `WebAssembly`. We named our main `Zig` function \"initilize\" which receives only numbers and return `void`. \n\n```js\nconst WASM_PAGE_SIZE = 65536; // 64kB\nconst MAXIMUM_PAGES = 150;\nconst INITAIL_PAGES = 60;\n\nconst cols = this.canvas.width,\n      rows = this.canvas.height,\n      bytesNeeded = cols * rows * 4;\n\nconst pagesNeeded = Math.ceil(bytesNeeded / WASM_PAGE_SIZE);\nthis.memSize = pagesNeeded;\nconst initialPages = Math.max(pagesNeeded, INITIAL_PAGES);\n\nmemory = new WebAssembly.Memory({initial: initialPages, maximum: MAXIMUM_PAGES});\n\nconst {instance }  = await WebAssembly.instantiateStreaming(fetch(\"/wasm\"), { env: {memory}});\n\n\ninstance.exports.allocMemory(this.memSize);\n\ninstance.exports.initilize(eows, cols...);\n```\n\nTo fill in the canvas, we:\n- instanttiate a Javascript `new Uint8ClampedArray` that will receive the WebAssembly data from the memory  address with a given length.\n- create an `ImageData` from this data\n- draw into the Canvas with `createImageBitmap`\n\n\n## Related\n\nThe `Elixir` library [Orb](https://useorb.dev/) can produce a WAT (text).\n\nTo compile to WASM binary, use [WABT](https://github.com/WebAssembly/wabt).\n\n[Wasmex](https://github.com/tessi/wasmex) can run Wasi in Elixir.\n\n## Create a Github pages from a subfolder\n\n\n \n+ Create a folder, \"pages\" here.\n+ create an \"index.html\" which calls a (eg) \"scriptjs\"\n+ put your JS in it\n+ copy \"zoom.wasm\" in the same folder or keep it sync with:\n\n\n```sh\n# /pages\nln -s ../zoomzig/zig-out/bin/zoom.wasm\n```\n\nThen: \n\n```sh\ngit subtree push --prefix pages origin gh-pages\n```\n\net voilà:\n\n\u003chttps://ndrean.github.io/zig-assembly-test/\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fzig-assembly-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndrean%2Fzig-assembly-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fzig-assembly-test/lists"}