@live-codes/odin-wasm 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -2
- package/THIRD-PARTY-NOTICES.md +41 -0
- package/assets/odin-src.pack.gz +0 -0
- package/assets/odin.js +2302 -0
- package/assets/odin.wasm +0 -0
- package/bin/copy-assets.mjs +47 -0
- package/dist/odin-wasm.global.js +6 -0
- package/package.json +49 -4
- package/scripts/build-iife.mjs +33 -0
- package/scripts/check-package.mjs +43 -0
- package/scripts/sync-assets.mjs +63 -0
- package/src/api.js +140 -0
- package/src/asset-receipts.js +13 -0
- package/src/assets.js +149 -0
- package/src/engine.js +220 -0
- package/src/host-js.js +112 -0
- package/src/index.js +9 -0
- package/src/index.node.js +9 -0
- package/src/pack.js +40 -0
- package/src/packaged.node.js +13 -0
- package/src/runtime.js +40 -0
- package/src/wasi-host.js +564 -0
package/src/wasi-host.js
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
// A minimal WASI preview 1 host over an in-memory file system — enough to run
|
|
2
|
+
// the Odin compiler (odin.wasm) and the programs it produces, in a page.
|
|
3
|
+
//
|
|
4
|
+
// Only what Odin actually uses is implemented; everything else returns
|
|
5
|
+
// ENOSYS. There is no SharedArrayBuffer anywhere, so a page that embeds this
|
|
6
|
+
// needs no cross-origin isolation (no COOP/COEP).
|
|
7
|
+
//
|
|
8
|
+
// The file system is a flat map of absolute paths. The single preopened
|
|
9
|
+
// directory is the root ("/"), so the compiler sees "/odin/base/...",
|
|
10
|
+
// "/src/main.odin" and the program it compiles sees "/out/main.wasm".
|
|
11
|
+
|
|
12
|
+
const ESUCCESS = 0;
|
|
13
|
+
const EBADF = 8;
|
|
14
|
+
const EEXIST = 20;
|
|
15
|
+
const EINVAL = 28;
|
|
16
|
+
const EIO = 29;
|
|
17
|
+
const EISDIR = 31;
|
|
18
|
+
const ENOENT = 44;
|
|
19
|
+
const ENOTDIR = 54;
|
|
20
|
+
const ENOTEMPTY = 55;
|
|
21
|
+
const ENOSYS = 52;
|
|
22
|
+
|
|
23
|
+
const FILETYPE_UNKNOWN = 0;
|
|
24
|
+
const FILETYPE_CHARACTER_DEVICE = 2;
|
|
25
|
+
const FILETYPE_DIRECTORY = 3;
|
|
26
|
+
const FILETYPE_REGULAR_FILE = 4;
|
|
27
|
+
|
|
28
|
+
const OFLAGS_CREAT = 1;
|
|
29
|
+
const OFLAGS_DIRECTORY = 2;
|
|
30
|
+
const OFLAGS_EXCL = 4;
|
|
31
|
+
const OFLAGS_TRUNC = 8;
|
|
32
|
+
|
|
33
|
+
const FDFLAGS_APPEND = 1;
|
|
34
|
+
|
|
35
|
+
export class WasiExit extends Error {
|
|
36
|
+
constructor(code) {
|
|
37
|
+
super(`WASI process exited with code ${code}`);
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Paths are normalised: no leading or trailing slash; the root is "".
|
|
43
|
+
export const normalizePath = (path) => {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const part of String(path).split('/')) {
|
|
46
|
+
if (part === '' || part === '.') continue;
|
|
47
|
+
if (part === '..') {
|
|
48
|
+
out.pop();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out.push(part);
|
|
52
|
+
}
|
|
53
|
+
return out.join('/');
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const parentPath = (path) => {
|
|
57
|
+
const i = path.lastIndexOf('/');
|
|
58
|
+
return i < 0 ? '' : path.slice(0, i);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const decoder = new TextDecoder();
|
|
63
|
+
|
|
64
|
+
/** An in-memory, flat file system rooted at "/". */
|
|
65
|
+
export class WasiFileSystem {
|
|
66
|
+
constructor() {
|
|
67
|
+
this.files = new Map(); // path -> { data: Uint8Array, size: number }
|
|
68
|
+
this.dirs = new Set(['']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
mkdirAll(path) {
|
|
72
|
+
const parts = normalizePath(path) === '' ? [] : normalizePath(path).split('/');
|
|
73
|
+
let cur = '';
|
|
74
|
+
for (const part of parts) {
|
|
75
|
+
cur = cur === '' ? part : `${cur}/${part}`;
|
|
76
|
+
this.dirs.add(cur);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
writeFile(path, bytes) {
|
|
81
|
+
const p = normalizePath(path);
|
|
82
|
+
this.mkdirAll(parentPath(p));
|
|
83
|
+
const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
84
|
+
this.files.set(p, { data, size: data.length });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
readFile(path) {
|
|
88
|
+
const f = this.files.get(normalizePath(path));
|
|
89
|
+
return f ? f.data.subarray(0, f.size) : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
exists(path) {
|
|
93
|
+
const p = normalizePath(path);
|
|
94
|
+
return this.files.has(p) || this.dirs.has(p);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Files and subdirectories directly under `path`, sorted by name. */
|
|
98
|
+
listDir(path) {
|
|
99
|
+
const p = normalizePath(path);
|
|
100
|
+
const prefix = p === '' ? '' : `${p}/`;
|
|
101
|
+
const entries = [];
|
|
102
|
+
for (const d of this.dirs) {
|
|
103
|
+
if (d !== '' && d.startsWith(prefix) && d.indexOf('/', prefix.length) < 0 && d !== p) {
|
|
104
|
+
entries.push({ name: d.slice(prefix.length), isDir: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const f of this.files.keys()) {
|
|
108
|
+
if (f.startsWith(prefix) && f.indexOf('/', prefix.length) < 0) {
|
|
109
|
+
entries.push({ name: f.slice(prefix.length), isDir: false });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const toUint8 = (data) => (data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {WasiFileSystem} fs
|
|
121
|
+
* @param {object} [options]
|
|
122
|
+
* @param {string[]} [options.args] argv (argv[0] is the program name)
|
|
123
|
+
* @param {string[]} [options.env] "KEY=VALUE" entries
|
|
124
|
+
* @param {string} [options.stdin] the finite standard input
|
|
125
|
+
* @param {(text: string) => void} [options.stdout]
|
|
126
|
+
* @param {(text: string) => void} [options.stderr]
|
|
127
|
+
*/
|
|
128
|
+
export const createWasi = (fs, options = {}) => {
|
|
129
|
+
const args = options.args ?? [];
|
|
130
|
+
const env = options.env ?? [];
|
|
131
|
+
const stdout = options.stdout ?? (() => {});
|
|
132
|
+
const stderr = options.stderr ?? (() => {});
|
|
133
|
+
|
|
134
|
+
const stdin = toUint8(encoder.encode(options.stdin ?? ''));
|
|
135
|
+
let stdinOffset = 0;
|
|
136
|
+
|
|
137
|
+
const fds = new Map();
|
|
138
|
+
let nextFd = 4;
|
|
139
|
+
fds.set(0, { kind: 'stdin' });
|
|
140
|
+
fds.set(1, { kind: 'stdout' });
|
|
141
|
+
fds.set(2, { kind: 'stderr' });
|
|
142
|
+
fds.set(3, { kind: 'dir', path: '' });
|
|
143
|
+
|
|
144
|
+
// The compiler prints diagnostics in pieces; hold partial lines and emit
|
|
145
|
+
// whole lines, flushing any remainder when the run ends.
|
|
146
|
+
const lineBuffers = ['', '', ''];
|
|
147
|
+
|
|
148
|
+
let memory = null;
|
|
149
|
+
const bytes = () => new Uint8Array(memory.buffer);
|
|
150
|
+
const view = () => new DataView(memory.buffer);
|
|
151
|
+
const readString = (ptr, len) => decoder.decode(bytes().subarray(ptr, ptr + len));
|
|
152
|
+
|
|
153
|
+
const writeStd = (fd, text) => {
|
|
154
|
+
let buf = lineBuffers[fd] + text;
|
|
155
|
+
const i = buf.lastIndexOf('\n');
|
|
156
|
+
if (i >= 0) {
|
|
157
|
+
(fd === 1 ? stdout : stderr)(buf.slice(0, i + 1));
|
|
158
|
+
buf = buf.slice(i + 1);
|
|
159
|
+
}
|
|
160
|
+
lineBuffers[fd] = buf;
|
|
161
|
+
};
|
|
162
|
+
const flush = () => {
|
|
163
|
+
for (const fd of [1, 2]) {
|
|
164
|
+
if (lineBuffers[fd] !== '') {
|
|
165
|
+
(fd === 1 ? stdout : stderr)(lineBuffers[fd]);
|
|
166
|
+
lineBuffers[fd] = '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const writeFilestat = (buf, filetype, size) => {
|
|
172
|
+
const v = view();
|
|
173
|
+
v.setBigUint64(buf + 0, 0n, true); // dev
|
|
174
|
+
v.setBigUint64(buf + 8, 0n, true); // ino
|
|
175
|
+
v.setUint8(buf + 16, filetype);
|
|
176
|
+
v.setBigUint64(buf + 24, 1n, true); // nlink
|
|
177
|
+
v.setBigUint64(buf + 32, BigInt(size), true);
|
|
178
|
+
v.setBigUint64(buf + 40, 0n, true); // atim
|
|
179
|
+
v.setBigUint64(buf + 48, 0n, true); // mtim
|
|
180
|
+
v.setBigUint64(buf + 56, 0n, true); // ctim
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const statPath = (path, buf) => {
|
|
184
|
+
if (fs.dirs.has(path)) {
|
|
185
|
+
writeFilestat(buf, FILETYPE_DIRECTORY, 0);
|
|
186
|
+
return ESUCCESS;
|
|
187
|
+
}
|
|
188
|
+
const f = fs.files.get(path);
|
|
189
|
+
if (f) {
|
|
190
|
+
writeFilestat(buf, FILETYPE_REGULAR_FILE, f.size);
|
|
191
|
+
return ESUCCESS;
|
|
192
|
+
}
|
|
193
|
+
return ENOENT;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const grow = (f, size) => {
|
|
197
|
+
if (size <= f.data.length) return;
|
|
198
|
+
let cap = Math.max(f.data.length * 2, 4096);
|
|
199
|
+
while (cap < size) cap *= 2;
|
|
200
|
+
const data = new Uint8Array(cap);
|
|
201
|
+
data.set(f.data.subarray(0, f.size));
|
|
202
|
+
f.data = data;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const readIovs = (f, iovs, iovsLen, offset) => {
|
|
206
|
+
const v = view();
|
|
207
|
+
const mem = bytes();
|
|
208
|
+
let total = 0;
|
|
209
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
210
|
+
const ptr = v.getUint32(iovs + i * 8, true);
|
|
211
|
+
const len = v.getUint32(iovs + i * 8 + 4, true);
|
|
212
|
+
const n = Math.max(0, Math.min(len, f.size - offset));
|
|
213
|
+
mem.set(f.data.subarray(offset, offset + n), ptr);
|
|
214
|
+
offset += n;
|
|
215
|
+
total += n;
|
|
216
|
+
if (n < len) break;
|
|
217
|
+
}
|
|
218
|
+
return total;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const writeIovs = (f, iovs, iovsLen, offset) => {
|
|
222
|
+
const v = view();
|
|
223
|
+
const mem = bytes();
|
|
224
|
+
let total = 0;
|
|
225
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
226
|
+
const ptr = v.getUint32(iovs + i * 8, true);
|
|
227
|
+
const len = v.getUint32(iovs + i * 8 + 4, true);
|
|
228
|
+
grow(f, offset + len);
|
|
229
|
+
f.data.set(mem.subarray(ptr, ptr + len), offset);
|
|
230
|
+
offset += len;
|
|
231
|
+
f.size = Math.max(f.size, offset);
|
|
232
|
+
total += len;
|
|
233
|
+
}
|
|
234
|
+
return total;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const writeStringList = (list, ptrs, buf) => {
|
|
238
|
+
const v = view();
|
|
239
|
+
const mem = bytes();
|
|
240
|
+
let pos = buf;
|
|
241
|
+
for (let i = 0; i < list.length; i++) {
|
|
242
|
+
const b = encoder.encode(list[i]);
|
|
243
|
+
v.setUint32(ptrs + i * 4, pos, true);
|
|
244
|
+
mem.set(b, pos);
|
|
245
|
+
mem[pos + b.length] = 0;
|
|
246
|
+
pos += b.length + 1;
|
|
247
|
+
}
|
|
248
|
+
return ESUCCESS;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const resolvePath = (dirFd, ptr, len) => {
|
|
252
|
+
const d = fds.get(dirFd);
|
|
253
|
+
if (!d || d.kind !== 'dir') return null;
|
|
254
|
+
const rel = readString(ptr, len);
|
|
255
|
+
return normalizePath(rel.startsWith('/') ? rel : `${d.path}/${rel}`);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const imports = {
|
|
259
|
+
args_sizes_get(argcPtr, bufSizePtr) {
|
|
260
|
+
const v = view();
|
|
261
|
+
v.setUint32(argcPtr, args.length, true);
|
|
262
|
+
v.setUint32(bufSizePtr, args.reduce((n, a) => n + encoder.encode(a).length + 1, 0), true);
|
|
263
|
+
return ESUCCESS;
|
|
264
|
+
},
|
|
265
|
+
args_get(argvPtr, argvBuf) {
|
|
266
|
+
return writeStringList(args, argvPtr, argvBuf);
|
|
267
|
+
},
|
|
268
|
+
environ_sizes_get(countPtr, bufSizePtr) {
|
|
269
|
+
const v = view();
|
|
270
|
+
v.setUint32(countPtr, env.length, true);
|
|
271
|
+
v.setUint32(bufSizePtr, env.reduce((n, e) => n + encoder.encode(e).length + 1, 0), true);
|
|
272
|
+
return ESUCCESS;
|
|
273
|
+
},
|
|
274
|
+
environ_get(envPtr, envBuf) {
|
|
275
|
+
return writeStringList(env, envPtr, envBuf);
|
|
276
|
+
},
|
|
277
|
+
clock_res_get(id, resPtr) {
|
|
278
|
+
view().setBigUint64(resPtr, 1000n, true);
|
|
279
|
+
return ESUCCESS;
|
|
280
|
+
},
|
|
281
|
+
clock_time_get(id, precision, timePtr) {
|
|
282
|
+
// 0 = realtime (since the epoch), 1 = monotonic (process start)
|
|
283
|
+
const ns = id === 0
|
|
284
|
+
? BigInt(Date.now()) * 1000000n
|
|
285
|
+
: BigInt(Math.round((globalThis.performance?.now() ?? Date.now()) * 1000)) * 1000n;
|
|
286
|
+
view().setBigUint64(timePtr, ns, true);
|
|
287
|
+
return ESUCCESS;
|
|
288
|
+
},
|
|
289
|
+
fd_close(fd) {
|
|
290
|
+
if (!fds.has(fd)) return EBADF;
|
|
291
|
+
if (fd > 3) fds.delete(fd);
|
|
292
|
+
return ESUCCESS;
|
|
293
|
+
},
|
|
294
|
+
fd_fdstat_get(fd, buf) {
|
|
295
|
+
const d = fds.get(fd);
|
|
296
|
+
if (!d) return EBADF;
|
|
297
|
+
const v = view();
|
|
298
|
+
const filetype = d.kind === 'dir'
|
|
299
|
+
? FILETYPE_DIRECTORY
|
|
300
|
+
: d.kind === 'file' ? FILETYPE_REGULAR_FILE : FILETYPE_CHARACTER_DEVICE;
|
|
301
|
+
v.setUint8(buf, filetype);
|
|
302
|
+
v.setUint16(buf + 2, 0, true);
|
|
303
|
+
v.setBigUint64(buf + 8, 0xffffffffffffffffn, true);
|
|
304
|
+
v.setBigUint64(buf + 16, 0xffffffffffffffffn, true);
|
|
305
|
+
return ESUCCESS;
|
|
306
|
+
},
|
|
307
|
+
fd_filestat_get(fd, buf) {
|
|
308
|
+
const d = fds.get(fd);
|
|
309
|
+
if (!d) return EBADF;
|
|
310
|
+
if (d.kind === 'dir') writeFilestat(buf, FILETYPE_DIRECTORY, 0);
|
|
311
|
+
else if (d.kind === 'file') writeFilestat(buf, FILETYPE_REGULAR_FILE, d.file.size);
|
|
312
|
+
else writeFilestat(buf, FILETYPE_CHARACTER_DEVICE, 0);
|
|
313
|
+
return ESUCCESS;
|
|
314
|
+
},
|
|
315
|
+
fd_filestat_set_size(fd, size) {
|
|
316
|
+
const d = fds.get(fd);
|
|
317
|
+
if (!d || d.kind !== 'file') return EBADF;
|
|
318
|
+
const n = Number(size);
|
|
319
|
+
grow(d.file, n);
|
|
320
|
+
if (n > d.file.size) d.file.data.fill(0, d.file.size, n);
|
|
321
|
+
d.file.size = n;
|
|
322
|
+
return ESUCCESS;
|
|
323
|
+
},
|
|
324
|
+
fd_prestat_get(fd, buf) {
|
|
325
|
+
if (fd !== 3) return EBADF;
|
|
326
|
+
const v = view();
|
|
327
|
+
v.setUint8(buf, 0); // preopentype: dir
|
|
328
|
+
v.setUint32(buf + 4, 1, true); // name length ("/")
|
|
329
|
+
return ESUCCESS;
|
|
330
|
+
},
|
|
331
|
+
fd_prestat_dir_name(fd, pathPtr, pathLen) {
|
|
332
|
+
if (fd !== 3) return EBADF;
|
|
333
|
+
if (pathLen >= 1) bytes()[pathPtr] = 0x2f; // "/"
|
|
334
|
+
return ESUCCESS;
|
|
335
|
+
},
|
|
336
|
+
fd_read(fd, iovs, iovsLen, nreadPtr) {
|
|
337
|
+
const d = fds.get(fd);
|
|
338
|
+
if (!d) return EBADF;
|
|
339
|
+
if (d.kind === 'stdin') {
|
|
340
|
+
const n = readIovs({ data: stdin, size: stdin.length }, iovs, iovsLen, stdinOffset);
|
|
341
|
+
stdinOffset += n;
|
|
342
|
+
view().setUint32(nreadPtr, n, true);
|
|
343
|
+
return ESUCCESS;
|
|
344
|
+
}
|
|
345
|
+
if (d.kind !== 'file') return EBADF;
|
|
346
|
+
const n = readIovs(d.file, iovs, iovsLen, d.offset);
|
|
347
|
+
d.offset += n;
|
|
348
|
+
view().setUint32(nreadPtr, n, true);
|
|
349
|
+
return ESUCCESS;
|
|
350
|
+
},
|
|
351
|
+
fd_pread(fd, iovs, iovsLen, offset, nreadPtr) {
|
|
352
|
+
const d = fds.get(fd);
|
|
353
|
+
if (!d || d.kind !== 'file') return EBADF;
|
|
354
|
+
const n = readIovs(d.file, iovs, iovsLen, Number(offset));
|
|
355
|
+
view().setUint32(nreadPtr, n, true);
|
|
356
|
+
return ESUCCESS;
|
|
357
|
+
},
|
|
358
|
+
fd_write(fd, iovs, iovsLen, nwrittenPtr) {
|
|
359
|
+
const d = fds.get(fd);
|
|
360
|
+
if (!d) return EBADF;
|
|
361
|
+
if (d.kind === 'stdout' || d.kind === 'stderr') {
|
|
362
|
+
const v = view();
|
|
363
|
+
let total = 0;
|
|
364
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
365
|
+
const ptr = v.getUint32(iovs + i * 8, true);
|
|
366
|
+
const len = v.getUint32(iovs + i * 8 + 4, true);
|
|
367
|
+
writeStd(fd, readString(ptr, len));
|
|
368
|
+
total += len;
|
|
369
|
+
}
|
|
370
|
+
v.setUint32(nwrittenPtr, total, true);
|
|
371
|
+
return ESUCCESS;
|
|
372
|
+
}
|
|
373
|
+
if (d.kind !== 'file') return EBADF;
|
|
374
|
+
const n = writeIovs(d.file, iovs, iovsLen, d.offset);
|
|
375
|
+
d.offset += n;
|
|
376
|
+
view().setUint32(nwrittenPtr, n, true);
|
|
377
|
+
return ESUCCESS;
|
|
378
|
+
},
|
|
379
|
+
fd_pwrite(fd, iovs, iovsLen, offset, nwrittenPtr) {
|
|
380
|
+
const d = fds.get(fd);
|
|
381
|
+
if (!d || d.kind !== 'file') return EBADF;
|
|
382
|
+
const n = writeIovs(d.file, iovs, iovsLen, Number(offset));
|
|
383
|
+
view().setUint32(nwrittenPtr, n, true);
|
|
384
|
+
return ESUCCESS;
|
|
385
|
+
},
|
|
386
|
+
fd_seek(fd, offset, whence, newOffsetPtr) {
|
|
387
|
+
const d = fds.get(fd);
|
|
388
|
+
if (!d || d.kind !== 'file') return EBADF;
|
|
389
|
+
const off = Number(offset);
|
|
390
|
+
let base = 0;
|
|
391
|
+
if (whence === 1) base = d.offset;
|
|
392
|
+
else if (whence === 2) base = d.file.size;
|
|
393
|
+
else if (whence !== 0) return EINVAL;
|
|
394
|
+
if (base + off < 0) return EINVAL;
|
|
395
|
+
d.offset = base + off;
|
|
396
|
+
view().setBigUint64(newOffsetPtr, BigInt(d.offset), true);
|
|
397
|
+
return ESUCCESS;
|
|
398
|
+
},
|
|
399
|
+
fd_readdir(fd, buf, bufLen, cookie, bufUsedPtr) {
|
|
400
|
+
const d = fds.get(fd);
|
|
401
|
+
if (!d || d.kind !== 'dir') return EBADF;
|
|
402
|
+
if (!d.entries) d.entries = fs.listDir(d.path);
|
|
403
|
+
const v = view();
|
|
404
|
+
const mem = bytes();
|
|
405
|
+
let pos = 0;
|
|
406
|
+
for (let i = Number(cookie); i < d.entries.length; i++) {
|
|
407
|
+
const e = d.entries[i];
|
|
408
|
+
const name = encoder.encode(e.name);
|
|
409
|
+
const entry = new Uint8Array(24 + name.length);
|
|
410
|
+
const ev = new DataView(entry.buffer);
|
|
411
|
+
ev.setBigUint64(0, BigInt(i + 1), true); // d_next
|
|
412
|
+
ev.setBigUint64(8, BigInt(i + 1), true); // d_ino
|
|
413
|
+
ev.setUint32(16, name.length, true);
|
|
414
|
+
ev.setUint8(20, e.isDir ? FILETYPE_DIRECTORY : FILETYPE_REGULAR_FILE);
|
|
415
|
+
entry.set(name, 24);
|
|
416
|
+
const n = Math.min(entry.length, bufLen - pos);
|
|
417
|
+
mem.set(entry.subarray(0, n), buf + pos);
|
|
418
|
+
pos += n;
|
|
419
|
+
if (n < entry.length) break;
|
|
420
|
+
}
|
|
421
|
+
v.setUint32(bufUsedPtr, pos, true);
|
|
422
|
+
return ESUCCESS;
|
|
423
|
+
},
|
|
424
|
+
path_create_directory(fd, pathPtr, pathLen) {
|
|
425
|
+
const path = resolvePath(fd, pathPtr, pathLen);
|
|
426
|
+
if (path === null) return EBADF;
|
|
427
|
+
if (fs.exists(path)) return EEXIST;
|
|
428
|
+
if (!fs.dirs.has(parentPath(path))) return ENOENT;
|
|
429
|
+
fs.dirs.add(path);
|
|
430
|
+
return ESUCCESS;
|
|
431
|
+
},
|
|
432
|
+
path_filestat_get(fd, flags, pathPtr, pathLen, buf) {
|
|
433
|
+
const path = resolvePath(fd, pathPtr, pathLen);
|
|
434
|
+
if (path === null) return EBADF;
|
|
435
|
+
return statPath(path, buf);
|
|
436
|
+
},
|
|
437
|
+
path_open(dirFd, dirFlags, pathPtr, pathLen, oflags, rightsBase, rightsInheriting, fdFlags, fdPtr) {
|
|
438
|
+
const path = resolvePath(dirFd, pathPtr, pathLen);
|
|
439
|
+
if (path === null) return EBADF;
|
|
440
|
+
const v = view();
|
|
441
|
+
if (fs.dirs.has(path)) {
|
|
442
|
+
if ((oflags & OFLAGS_EXCL) && (oflags & OFLAGS_CREAT)) return EEXIST;
|
|
443
|
+
const fd = nextFd++;
|
|
444
|
+
fds.set(fd, { kind: 'dir', path });
|
|
445
|
+
v.setUint32(fdPtr, fd, true);
|
|
446
|
+
return ESUCCESS;
|
|
447
|
+
}
|
|
448
|
+
if (oflags & OFLAGS_DIRECTORY) return fs.files.has(path) ? ENOTDIR : ENOENT;
|
|
449
|
+
let file = fs.files.get(path);
|
|
450
|
+
if (file) {
|
|
451
|
+
if ((oflags & OFLAGS_EXCL) && (oflags & OFLAGS_CREAT)) return EEXIST;
|
|
452
|
+
if (oflags & OFLAGS_TRUNC) {
|
|
453
|
+
file.data = new Uint8Array(0);
|
|
454
|
+
file.size = 0;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
if (!(oflags & OFLAGS_CREAT)) return ENOENT;
|
|
458
|
+
if (!fs.dirs.has(parentPath(path))) return ENOENT;
|
|
459
|
+
file = { data: new Uint8Array(0), size: 0 };
|
|
460
|
+
fs.files.set(path, file);
|
|
461
|
+
}
|
|
462
|
+
const fd = nextFd++;
|
|
463
|
+
fds.set(fd, { kind: 'file', path, file, offset: (fdFlags & FDFLAGS_APPEND) ? file.size : 0 });
|
|
464
|
+
v.setUint32(fdPtr, fd, true);
|
|
465
|
+
return ESUCCESS;
|
|
466
|
+
},
|
|
467
|
+
path_remove_directory(fd, pathPtr, pathLen) {
|
|
468
|
+
const path = resolvePath(fd, pathPtr, pathLen);
|
|
469
|
+
if (path === null) return EBADF;
|
|
470
|
+
if (!fs.dirs.has(path)) return fs.files.has(path) ? ENOTDIR : ENOENT;
|
|
471
|
+
if (fs.listDir(path).length > 0) return ENOTEMPTY;
|
|
472
|
+
fs.dirs.delete(path);
|
|
473
|
+
return ESUCCESS;
|
|
474
|
+
},
|
|
475
|
+
path_unlink_file(fd, pathPtr, pathLen) {
|
|
476
|
+
const path = resolvePath(fd, pathPtr, pathLen);
|
|
477
|
+
if (path === null) return EBADF;
|
|
478
|
+
if (!fs.files.has(path)) return fs.dirs.has(path) ? EISDIR : ENOENT;
|
|
479
|
+
fs.files.delete(path);
|
|
480
|
+
return ESUCCESS;
|
|
481
|
+
},
|
|
482
|
+
// Nothing is durable here, so syncing is a no-op.
|
|
483
|
+
fd_sync() {
|
|
484
|
+
return ESUCCESS;
|
|
485
|
+
},
|
|
486
|
+
fd_datasync() {
|
|
487
|
+
return ESUCCESS;
|
|
488
|
+
},
|
|
489
|
+
fd_advise() {
|
|
490
|
+
return ESUCCESS;
|
|
491
|
+
},
|
|
492
|
+
fd_tell(fd, offsetPtr) {
|
|
493
|
+
const d = fds.get(fd);
|
|
494
|
+
if (!d) return EBADF;
|
|
495
|
+
if (d.kind !== 'file') return EBADF;
|
|
496
|
+
view().setBigUint64(offsetPtr, BigInt(d.offset), true);
|
|
497
|
+
return ESUCCESS;
|
|
498
|
+
},
|
|
499
|
+
fd_renumber(from, to) {
|
|
500
|
+
if (!fds.has(from) || !fds.has(to)) return EBADF;
|
|
501
|
+
fds.set(to, fds.get(from));
|
|
502
|
+
fds.delete(from);
|
|
503
|
+
return ESUCCESS;
|
|
504
|
+
},
|
|
505
|
+
// Stubs: a program that reaches for these gets ENOSYS, not a link error.
|
|
506
|
+
fd_allocate: () => ENOSYS,
|
|
507
|
+
fd_fdstat_set_flags: () => ENOSYS,
|
|
508
|
+
fd_fdstat_set_rights: () => ENOSYS,
|
|
509
|
+
fd_filestat_set_times: () => ENOSYS,
|
|
510
|
+
path_filestat_set_times: () => ENOSYS,
|
|
511
|
+
path_link: () => ENOSYS,
|
|
512
|
+
path_readlink: () => ENOSYS,
|
|
513
|
+
path_rename: () => ENOSYS,
|
|
514
|
+
path_symlink: () => ENOSYS,
|
|
515
|
+
proc_raise: () => ENOSYS,
|
|
516
|
+
sock_accept: () => ENOSYS,
|
|
517
|
+
sock_recv: () => ENOSYS,
|
|
518
|
+
sock_send: () => ENOSYS,
|
|
519
|
+
sock_shutdown: () => ENOSYS,
|
|
520
|
+
proc_exit(code) {
|
|
521
|
+
throw new WasiExit(code);
|
|
522
|
+
},
|
|
523
|
+
random_get(ptr, len) {
|
|
524
|
+
globalThis.crypto.getRandomValues(bytes().subarray(ptr, ptr + len));
|
|
525
|
+
return ESUCCESS;
|
|
526
|
+
},
|
|
527
|
+
sched_yield() {
|
|
528
|
+
return ESUCCESS;
|
|
529
|
+
},
|
|
530
|
+
poll_oneoff() {
|
|
531
|
+
return ENOSYS;
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
imports,
|
|
537
|
+
setMemory(mem) {
|
|
538
|
+
memory = mem;
|
|
539
|
+
},
|
|
540
|
+
flush,
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Instantiate `module` with the WASI imports and run its `_start`.
|
|
546
|
+
* @returns {number} the program's exit code
|
|
547
|
+
*/
|
|
548
|
+
export const runWasi = (module, wasi) => {
|
|
549
|
+
const instance = new WebAssembly.Instance(module, { wasi_snapshot_preview1: wasi.imports });
|
|
550
|
+
wasi.setMemory(instance.exports.memory);
|
|
551
|
+
let code = 0;
|
|
552
|
+
try {
|
|
553
|
+
instance.exports._start();
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (error instanceof WasiExit) {
|
|
556
|
+
code = error.code;
|
|
557
|
+
} else {
|
|
558
|
+
wasi.flush();
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
wasi.flush();
|
|
563
|
+
return code;
|
|
564
|
+
};
|