├── .gitignore ├── LICENSE ├── README.md ├── biome.json ├── examples └── browser.js ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasi-zigc 2 | Run zigc.wasm in a browser/nodejs 3 | 4 | ```npm 5 | npm install wasi-zigc 6 | ``` 7 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.6.4/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "formatter": { 7 | "indentStyle": "space", 8 | "indentWidth": 4, 9 | "lineWidth": 100 10 | }, 11 | "linter": { 12 | "enabled": true, 13 | "rules": { 14 | "recommended": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/browser.js: -------------------------------------------------------------------------------- 1 | import { initZigWASI, runZigCompiler, runZigOutput } from "../dist/index.js"; 2 | 3 | const zigCode = String.raw` 4 | const std = @import("std"); 5 | 6 | pub fn main() !void { 7 | std.debug.print("Hello, World!\n", .{}); 8 | } 9 | `; 10 | 11 | const zigCodeModified = String.raw` 12 | const std = @import("std"); 13 | 14 | pub fn main() !void { 15 | std.debug.print("你好,世界!\n", .{}); 16 | } 17 | `; 18 | 19 | (async () => { 20 | const bytes = await fetch( 21 | "https://github.com/Afirium/zigc-wasm/releases/download/v0.11.0/std.zip", 22 | ).then((r) => r.blob()); 23 | const buf = await bytes.arrayBuffer(); 24 | const std = new Uint8Array(buf); 25 | 26 | const wasi = await initZigWASI(std, zigCode); 27 | const zigCompiler = await fetch( 28 | "https://github.com/Afirium/zigc-wasm/releases/download/v0.11.0/zig_small.wasm", 29 | ); 30 | 31 | const zigCompilerArray = await zigCompiler.arrayBuffer(); 32 | 33 | // Hello world #1 34 | await runZigCompiler(zigCompilerArray, wasi); 35 | await runZigOutput(Buffer.from(wasi.fds[3].dir.contents.get("input.wasm").data)); 36 | 37 | // Hello world #2 38 | wasi.fds[3].dir.contents.get("input.zig").data = new TextEncoder().encode(zigCodeModified); 39 | 40 | await runZigCompiler(zigCompilerArray, wasi); 41 | await runZigOutput(Buffer.from(wasi.fds[3].dir.contents.get("input.wasm").data)); 42 | })(); 43 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasi-zigc", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "wasi-zigc", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@bjorn3/browser_wasi_shim": "^0.3.0", 13 | "but-unzip": "^0.1.4" 14 | }, 15 | "devDependencies": { 16 | "@biomejs/biome": "1.6.4", 17 | "typescript": "^5.4.5" 18 | } 19 | }, 20 | "node_modules/@biomejs/biome": { 21 | "version": "1.6.4", 22 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.6.4.tgz", 23 | "integrity": "sha512-3groVd2oWsLC0ZU+XXgHSNbq31lUcOCBkCcA7sAQGBopHcmL+jmmdoWlY3S61zIh+f2mqQTQte1g6PZKb3JJjA==", 24 | "dev": true, 25 | "hasInstallScript": true, 26 | "bin": { 27 | "biome": "bin/biome" 28 | }, 29 | "engines": { 30 | "node": ">=14.21.3" 31 | }, 32 | "funding": { 33 | "type": "opencollective", 34 | "url": "https://opencollective.com/biome" 35 | }, 36 | "optionalDependencies": { 37 | "@biomejs/cli-darwin-arm64": "1.6.4", 38 | "@biomejs/cli-darwin-x64": "1.6.4", 39 | "@biomejs/cli-linux-arm64": "1.6.4", 40 | "@biomejs/cli-linux-arm64-musl": "1.6.4", 41 | "@biomejs/cli-linux-x64": "1.6.4", 42 | "@biomejs/cli-linux-x64-musl": "1.6.4", 43 | "@biomejs/cli-win32-arm64": "1.6.4", 44 | "@biomejs/cli-win32-x64": "1.6.4" 45 | } 46 | }, 47 | "node_modules/@biomejs/cli-darwin-arm64": { 48 | "version": "1.6.4", 49 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.6.4.tgz", 50 | "integrity": "sha512-2WZef8byI9NRzGajGj5RTrroW9BxtfbP9etigW1QGAtwu/6+cLkdPOWRAs7uFtaxBNiKFYA8j/BxV5zeAo5QOQ==", 51 | "cpu": [ 52 | "arm64" 53 | ], 54 | "dev": true, 55 | "optional": true, 56 | "os": [ 57 | "darwin" 58 | ], 59 | "engines": { 60 | "node": ">=14.21.3" 61 | } 62 | }, 63 | "node_modules/@biomejs/cli-darwin-x64": { 64 | "version": "1.6.4", 65 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.6.4.tgz", 66 | "integrity": "sha512-uo1zgM7jvzcoDpF6dbGizejDLCqNpUIRkCj/oEK0PB0NUw8re/cn1EnxuOLZqDpn+8G75COLQTOx8UQIBBN/Kg==", 67 | "cpu": [ 68 | "x64" 69 | ], 70 | "dev": true, 71 | "optional": true, 72 | "os": [ 73 | "darwin" 74 | ], 75 | "engines": { 76 | "node": ">=14.21.3" 77 | } 78 | }, 79 | "node_modules/@biomejs/cli-linux-arm64": { 80 | "version": "1.6.4", 81 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.6.4.tgz", 82 | "integrity": "sha512-wAOieaMNIpLrxGc2/xNvM//CIZg7ueWy3V5A4T7gDZ3OL/Go27EKE59a+vMKsBCYmTt7jFl4yHz0TUkUbodA/w==", 83 | "cpu": [ 84 | "arm64" 85 | ], 86 | "dev": true, 87 | "optional": true, 88 | "os": [ 89 | "linux" 90 | ], 91 | "engines": { 92 | "node": ">=14.21.3" 93 | } 94 | }, 95 | "node_modules/@biomejs/cli-linux-arm64-musl": { 96 | "version": "1.6.4", 97 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.4.tgz", 98 | "integrity": "sha512-Hp8Jwt6rjj0wCcYAEN6/cfwrrPLLlGOXZ56Lei4Pt4jy39+UuPeAVFPeclrrCfxyL1wQ2xPrhd/saTHSL6DoJg==", 99 | "cpu": [ 100 | "arm64" 101 | ], 102 | "dev": true, 103 | "optional": true, 104 | "os": [ 105 | "linux" 106 | ], 107 | "engines": { 108 | "node": ">=14.21.3" 109 | } 110 | }, 111 | "node_modules/@biomejs/cli-linux-x64": { 112 | "version": "1.6.4", 113 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.6.4.tgz", 114 | "integrity": "sha512-qTWhuIw+/ePvOkjE9Zxf5OqSCYxtAvcTJtVmZT8YQnmY2I62JKNV2m7tf6O5ViKZUOP0mOQ6NgqHKcHH1eT8jw==", 115 | "cpu": [ 116 | "x64" 117 | ], 118 | "dev": true, 119 | "optional": true, 120 | "os": [ 121 | "linux" 122 | ], 123 | "engines": { 124 | "node": ">=14.21.3" 125 | } 126 | }, 127 | "node_modules/@biomejs/cli-linux-x64-musl": { 128 | "version": "1.6.4", 129 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.4.tgz", 130 | "integrity": "sha512-wqi0hr8KAx5kBO0B+m5u8QqiYFFBJOSJVSuRqTeGWW+GYLVUtXNidykNqf1JsW6jJDpbkSp2xHKE/bTlVaG2Kg==", 131 | "cpu": [ 132 | "x64" 133 | ], 134 | "dev": true, 135 | "optional": true, 136 | "os": [ 137 | "linux" 138 | ], 139 | "engines": { 140 | "node": ">=14.21.3" 141 | } 142 | }, 143 | "node_modules/@biomejs/cli-win32-arm64": { 144 | "version": "1.6.4", 145 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.6.4.tgz", 146 | "integrity": "sha512-Wp3FiEeF6v6C5qMfLkHwf4YsoNHr/n0efvoC8jCKO/kX05OXaVExj+1uVQ1eGT7Pvx0XVm/TLprRO0vq/V6UzA==", 147 | "cpu": [ 148 | "arm64" 149 | ], 150 | "dev": true, 151 | "optional": true, 152 | "os": [ 153 | "win32" 154 | ], 155 | "engines": { 156 | "node": ">=14.21.3" 157 | } 158 | }, 159 | "node_modules/@biomejs/cli-win32-x64": { 160 | "version": "1.6.4", 161 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.6.4.tgz", 162 | "integrity": "sha512-mz183Di5hTSGP7KjNWEhivcP1wnHLGmOxEROvoFsIxMYtDhzJDad4k5gI/1JbmA0xe4n52vsgqo09tBhrMT/Zg==", 163 | "cpu": [ 164 | "x64" 165 | ], 166 | "dev": true, 167 | "optional": true, 168 | "os": [ 169 | "win32" 170 | ], 171 | "engines": { 172 | "node": ">=14.21.3" 173 | } 174 | }, 175 | "node_modules/@bjorn3/browser_wasi_shim": { 176 | "version": "0.3.0", 177 | "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.3.0.tgz", 178 | "integrity": "sha512-FlRBYttPRLcWORzBe6g8nmYTafBkOEFeOqMYM4tAHJzFsQy4+xJA94z85a9BCs8S+Uzfh9LrkpII7DXr2iUVFg==" 179 | }, 180 | "node_modules/but-unzip": { 181 | "version": "0.1.4", 182 | "resolved": "https://registry.npmjs.org/but-unzip/-/but-unzip-0.1.4.tgz", 183 | "integrity": "sha512-Q5/55MTk0PHjxtYyZBbhIVMJP0+FNc/AOKBrrnqaxnbJR4I7w+R4CMRNYMxUQrKmCLrih7D1p4/nwZHMn7IToA==" 184 | }, 185 | "node_modules/typescript": { 186 | "version": "5.4.5", 187 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", 188 | "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", 189 | "dev": true, 190 | "bin": { 191 | "tsc": "bin/tsc", 192 | "tsserver": "bin/tsserver" 193 | }, 194 | "engines": { 195 | "node": ">=14.17" 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasi-zigc", 3 | "version": "0.1.0", 4 | "description": "WASI support for zigc.wasm", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "scripts": { 9 | "build": "tsc" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Afirium/wasi-zigc.git" 14 | }, 15 | "keywords": [ 16 | "wasi", 17 | "zig" 18 | ], 19 | "author": "afirium", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/Afirium/wasi-zigc/issues" 23 | }, 24 | "homepage": "https://github.com/Afirium/wasi-zigc#readme", 25 | "devDependencies": { 26 | "@biomejs/biome": "1.6.4", 27 | "typescript": "^5.4.5" 28 | }, 29 | "dependencies": { 30 | "@bjorn3/browser_wasi_shim": "^0.3.0", 31 | "but-unzip": "^0.1.4" 32 | } 33 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | WASI, 3 | File, 4 | OpenFile, 5 | ConsoleStdout, 6 | PreopenDirectory, 7 | Directory, 8 | type Fd, 9 | } from "@bjorn3/browser_wasi_shim"; 10 | import { type ZipItem, iter } from "but-unzip"; 11 | 12 | async function buildDirectoryTree(files: Generator): Promise { 13 | const root = new Directory([]); 14 | 15 | for (const { filename, read } of files) { 16 | const parts = filename.split("/"); 17 | let currentDirectory = root; 18 | 19 | parts.forEach(async (part, index) => { 20 | if (part !== "") { 21 | if (index === parts.length - 1) { 22 | if (!currentDirectory.contents.has(part)) { 23 | currentDirectory.contents.set(part, new File(await read())); 24 | } 25 | } else { 26 | if (!currentDirectory.contents.has(parts[index])) { 27 | currentDirectory.contents.set(parts[index], new Directory([])); 28 | } 29 | currentDirectory = currentDirectory.contents.get(parts[index]) as Directory; 30 | } 31 | } 32 | }); 33 | } 34 | 35 | return root.contents.get("std") as Directory; 36 | } 37 | 38 | export async function unZipStdLib(source: Uint8Array): Promise { 39 | return await buildDirectoryTree(iter(source)); 40 | } 41 | 42 | export async function initZigWASI(std: Uint8Array, zigCode: string, debug = false): Promise { 43 | const args: string[] = [ 44 | "zigc.wasm", 45 | "build-exe", 46 | "input.zig", 47 | "-Dtarget=wasm64-wasi", 48 | "-fno-llvm", 49 | "-fno-lld", 50 | "-O", 51 | "ReleaseSmall", 52 | ]; 53 | const env: string[] = []; 54 | const fds: Fd[] = [ 55 | new OpenFile(new File([])), // stdin 56 | new OpenFile(new File([])), // stdout 57 | ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stderr] ${msg}`)), // stderr 58 | new PreopenDirectory( 59 | ".", 60 | new Map([["input.zig", new File(new TextEncoder().encode(zigCode))]]), 61 | ), 62 | new PreopenDirectory("/lib", new Map([["std", await unZipStdLib(std)]])), 63 | new PreopenDirectory("/cache", new Map()), 64 | ]; 65 | 66 | return new WASI(args, env, fds, { debug }); 67 | } 68 | 69 | export async function runZigCompiler(zigc: BufferSource, wasi: WASI): Promise { 70 | const wasm = await WebAssembly.compile(zigc); 71 | const instance = await WebAssembly.instantiate(wasm, { 72 | wasi_snapshot_preview1: wasi.wasiImport, 73 | }); 74 | 75 | // @ts-ignore 76 | wasi.start(instance); 77 | 78 | return wasi; 79 | } 80 | 81 | export async function runZigOutput(output: ArrayBuffer, wasi?: WASI): Promise { 82 | const wasmComp = await WebAssembly.compile(output); 83 | 84 | let wasiObject: WASI; 85 | 86 | if (wasi) { 87 | wasiObject = wasi; 88 | } else { 89 | const args = ["output.wasm"]; 90 | const env: string[] = []; 91 | const fds = [ 92 | new OpenFile(new File([])), // stdin 93 | ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stdout] ${msg}`)), // stdout 94 | ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stderr] ${msg}`)), // stderr 95 | ]; 96 | wasiObject = new WASI(args, env, fds, { debug: false }); 97 | } 98 | 99 | const instInput = await WebAssembly.instantiate(wasmComp, { 100 | wasi_snapshot_preview1: wasiObject.wasiImport, 101 | }); 102 | 103 | // @ts-ignore 104 | wasiObject.start(instInput); 105 | 106 | return wasiObject; 107 | } 108 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "outDir": "./dist", 9 | "strict": true, 10 | "declaration": true 11 | }, 12 | "include": [ 13 | "src/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "examples" 18 | ] 19 | } --------------------------------------------------------------------------------