wasm-idle 0.6.1 → 0.7.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/README.md +77 -11
- package/dist/playground/assets.d.ts +43 -0
- package/dist/playground/assets.js +164 -0
- package/dist/playground/elixir.d.ts +32 -0
- package/dist/playground/elixir.js +173 -0
- package/dist/playground/go.d.ts +31 -0
- package/dist/playground/go.js +166 -0
- package/dist/playground/index.js +36 -1
- package/dist/playground/ocaml.d.ts +32 -0
- package/dist/playground/ocaml.js +341 -0
- package/dist/playground/options.d.ts +4 -0
- package/dist/playground/tinygo.d.ts +75 -0
- package/dist/playground/tinygo.js +510 -0
- package/dist/playground/wasmElixirVersion.d.ts +1 -0
- package/dist/playground/wasmElixirVersion.js +1 -0
- package/dist/playground/wasmGoVersion.d.ts +1 -0
- package/dist/playground/wasmGoVersion.js +1 -0
- package/dist/playground/wasmOcamlVersion.d.ts +1 -0
- package/dist/playground/wasmOcamlVersion.js +1 -0
- package/dist/playground/wasmRustVersion.d.ts +1 -1
- package/dist/playground/wasmRustVersion.js +1 -1
- package/dist/playground/wasmTinyGoVersion.d.ts +1 -0
- package/dist/playground/wasmTinyGoVersion.js +1 -0
- package/dist/playground/worker/elixir.d.ts +1 -0
- package/dist/playground/worker/elixir.js +317 -0
- package/dist/playground/worker/go.d.ts +1 -0
- package/dist/playground/worker/go.js +163 -0
- package/dist/playground/worker/ocaml.d.ts +1 -0
- package/dist/playground/worker/ocaml.js +449 -0
- package/dist/playground/worker/tinygo.d.ts +1 -0
- package/dist/playground/worker/tinygo.js +137 -0
- package/dist/terminal/Terminal.svelte +69 -29
- package/package.json +33 -21
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
2
|
+
let moduleUrl = '';
|
|
3
|
+
let manifestUrl = '';
|
|
4
|
+
let loadedModuleUrl = '';
|
|
5
|
+
let loadedManifestUrl = '';
|
|
6
|
+
let compilerPromise = null;
|
|
7
|
+
let manifestPromise = null;
|
|
8
|
+
let compiledResult = null;
|
|
9
|
+
let compiledCacheKey = '';
|
|
10
|
+
let stdinBufferOcaml = null;
|
|
11
|
+
function appendTrailingNewline(text) {
|
|
12
|
+
return text.endsWith('\n') ? text : `${text}\n`;
|
|
13
|
+
}
|
|
14
|
+
function normalizeAssetLoader(programSource, runtimePromiseKey) {
|
|
15
|
+
let normalizedSource = programSource.includes('($=>async a=>{')
|
|
16
|
+
? programSource.replace('($=>async a=>{', `globalThis.${runtimePromiseKey}=($=>async a=>{`)
|
|
17
|
+
: programSource;
|
|
18
|
+
const assetLoaderPattern = /function ([A-Za-z$_][\w$]*)\(([A-Za-z$_][\w$]*)\)\{const ([A-Za-z$_][\w$]*)=([A-Za-z$_][\w$]*)\?new URL\(\2,\4\):\2;return fetch\(\3\)\}/;
|
|
19
|
+
const matchedLoader = normalizedSource.match(assetLoaderPattern);
|
|
20
|
+
if (!matchedLoader) {
|
|
21
|
+
return normalizedSource;
|
|
22
|
+
}
|
|
23
|
+
const loaderName = matchedLoader[1] || 'loadAsset';
|
|
24
|
+
const argumentName = matchedLoader[2] || 'assetPath';
|
|
25
|
+
const resolvedName = matchedLoader[3] || 'resolvedUrl';
|
|
26
|
+
const baseName = matchedLoader[4] || 'scriptUrl';
|
|
27
|
+
return normalizedSource.replace(assetLoaderPattern, `function ${loaderName}(${argumentName}){if(globalThis.__wasm_of_js_of_ocaml_resolve_asset){const resolvedAsset=globalThis.__wasm_of_js_of_ocaml_resolve_asset(${argumentName});if(resolvedAsset)return fetch(resolvedAsset)}const ${resolvedName}=${baseName}?new URL(${argumentName},${baseName}):${argumentName};return fetch(${resolvedName})}`);
|
|
28
|
+
}
|
|
29
|
+
function rewriteAbsoluteBundleUrl(url, currentManifestUrl) {
|
|
30
|
+
if (!url || /^[a-z]+:/i.test(url)) {
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
const manifestLocation = new URL(currentManifestUrl, self.location.href);
|
|
34
|
+
const basePath = manifestLocation.pathname.replace(/\/wasm-of-js-of-ocaml\/browser-native-bundle\/browser-native-manifest\.v1\.json$/, '');
|
|
35
|
+
return new URL(`${basePath}${url}`, manifestLocation.origin).toString();
|
|
36
|
+
}
|
|
37
|
+
function rewriteManifest(manifest, currentManifestUrl) {
|
|
38
|
+
return {
|
|
39
|
+
...manifest,
|
|
40
|
+
findlibConf: rewriteAbsoluteBundleUrl(manifest.findlibConf, currentManifestUrl),
|
|
41
|
+
tools: {
|
|
42
|
+
ocamlc: rewriteAbsoluteBundleUrl(manifest.tools.ocamlc, currentManifestUrl),
|
|
43
|
+
js_of_ocaml: rewriteAbsoluteBundleUrl(manifest.tools.js_of_ocaml, currentManifestUrl),
|
|
44
|
+
wasm_of_ocaml: rewriteAbsoluteBundleUrl(manifest.tools.wasm_of_ocaml, currentManifestUrl)
|
|
45
|
+
},
|
|
46
|
+
...(manifest.binaryenTools
|
|
47
|
+
? {
|
|
48
|
+
binaryenTools: {
|
|
49
|
+
wasm_opt: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_opt, currentManifestUrl),
|
|
50
|
+
wasm_merge: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_merge, currentManifestUrl),
|
|
51
|
+
wasm_metadce: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_metadce, currentManifestUrl)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
: {}),
|
|
55
|
+
...(manifest.runtimePack
|
|
56
|
+
? {
|
|
57
|
+
runtimePack: {
|
|
58
|
+
...manifest.runtimePack,
|
|
59
|
+
asset: rewriteAbsoluteBundleUrl(manifest.runtimePack.asset, currentManifestUrl),
|
|
60
|
+
index: rewriteAbsoluteBundleUrl(manifest.runtimePack.index, currentManifestUrl)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
64
|
+
ocamlLibFiles: manifest.ocamlLibFiles.map((file) => ({
|
|
65
|
+
...file,
|
|
66
|
+
...(file.url ? { url: rewriteAbsoluteBundleUrl(file.url, currentManifestUrl) } : {})
|
|
67
|
+
})),
|
|
68
|
+
packages: manifest.packages.map((manifestPackage) => ({
|
|
69
|
+
...manifestPackage,
|
|
70
|
+
files: manifestPackage.files.map((file) => ({
|
|
71
|
+
...file,
|
|
72
|
+
...(file.url ? { url: rewriteAbsoluteBundleUrl(file.url, currentManifestUrl) } : {})
|
|
73
|
+
}))
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function loadCompiler(nextModuleUrl) {
|
|
78
|
+
if (!nextModuleUrl) {
|
|
79
|
+
throw new Error('OCaml runtime is not configured. Set runtimeAssets.ocaml.moduleUrl and runtimeAssets.ocaml.manifestUrl or sync the bundled wasm-of-js-of-ocaml assets.');
|
|
80
|
+
}
|
|
81
|
+
if (loadedModuleUrl === nextModuleUrl && compilerPromise) {
|
|
82
|
+
return await compilerPromise;
|
|
83
|
+
}
|
|
84
|
+
loadedModuleUrl = nextModuleUrl;
|
|
85
|
+
compiledResult = null;
|
|
86
|
+
compiledCacheKey = '';
|
|
87
|
+
compilerPromise = (async () => {
|
|
88
|
+
const module = (await import(/* @vite-ignore */ nextModuleUrl));
|
|
89
|
+
if (typeof module.compile !== 'function') {
|
|
90
|
+
throw new Error('wasm-of-js-of-ocaml bundle must export compile');
|
|
91
|
+
}
|
|
92
|
+
if (typeof module.createBrowserWorkerSystemDispatcher !== 'function') {
|
|
93
|
+
throw new Error('wasm-of-js-of-ocaml bundle must export createBrowserWorkerSystemDispatcher');
|
|
94
|
+
}
|
|
95
|
+
return module;
|
|
96
|
+
})();
|
|
97
|
+
return await compilerPromise;
|
|
98
|
+
}
|
|
99
|
+
async function loadManifest(nextManifestUrl) {
|
|
100
|
+
if (!nextManifestUrl) {
|
|
101
|
+
throw new Error('OCaml runtime is not configured. Set runtimeAssets.ocaml.moduleUrl and runtimeAssets.ocaml.manifestUrl or sync the bundled wasm-of-js-of-ocaml assets.');
|
|
102
|
+
}
|
|
103
|
+
if (loadedManifestUrl === nextManifestUrl && manifestPromise) {
|
|
104
|
+
return await manifestPromise;
|
|
105
|
+
}
|
|
106
|
+
loadedManifestUrl = nextManifestUrl;
|
|
107
|
+
compiledResult = null;
|
|
108
|
+
compiledCacheKey = '';
|
|
109
|
+
manifestPromise = (async () => {
|
|
110
|
+
const response = await fetch(nextManifestUrl, { cache: 'no-store' });
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`failed to fetch OCaml manifest: ${response.status}`);
|
|
113
|
+
}
|
|
114
|
+
return rewriteManifest((await response.json()), nextManifestUrl);
|
|
115
|
+
})();
|
|
116
|
+
return await manifestPromise;
|
|
117
|
+
}
|
|
118
|
+
function getJsArtifact(result) {
|
|
119
|
+
const programArtifact = result.artifacts.find((artifact) => artifact.kind === 'js' && typeof artifact.data === 'string');
|
|
120
|
+
if (!programArtifact || typeof programArtifact.data !== 'string') {
|
|
121
|
+
throw new Error('OCaml compile result did not include a JavaScript artifact');
|
|
122
|
+
}
|
|
123
|
+
return programArtifact;
|
|
124
|
+
}
|
|
125
|
+
async function executeCompileResult(result, log = false) {
|
|
126
|
+
const programArtifact = getJsArtifact(result);
|
|
127
|
+
const assetFiles = result.artifacts.filter((artifact) => artifact.kind === 'wasm' || artifact.kind === 'asset');
|
|
128
|
+
const assetResolverKey = '__wasm_of_js_of_ocaml_resolve_asset';
|
|
129
|
+
const runtimePromiseKey = '__wasm_of_js_of_ocaml_runtime_promise';
|
|
130
|
+
const stdinHookKey = '__wasmIdleOcamlReadStdin';
|
|
131
|
+
const sourceDir = programArtifact.path.replace(/\/[^/]+$/, '');
|
|
132
|
+
const createdObjectUrls = [];
|
|
133
|
+
const stdinEncoder = new TextEncoder();
|
|
134
|
+
let stdinChunkOcaml = new Uint8Array(0);
|
|
135
|
+
let stdinChunkOffsetOcaml = 0;
|
|
136
|
+
const originalConsole = globalThis.console;
|
|
137
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
138
|
+
const originalInstantiate = WebAssembly.instantiate.bind(WebAssembly);
|
|
139
|
+
const originalInstantiateStreaming = WebAssembly.instantiateStreaming
|
|
140
|
+
? WebAssembly.instantiateStreaming.bind(WebAssembly)
|
|
141
|
+
: undefined;
|
|
142
|
+
const runtimeGlobal = globalThis;
|
|
143
|
+
const hadProcess = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'process');
|
|
144
|
+
const hadRequire = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'require');
|
|
145
|
+
const hadModule = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'module');
|
|
146
|
+
const hadExports = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'exports');
|
|
147
|
+
const hadFs = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'fs');
|
|
148
|
+
const originalProcess = runtimeGlobal.process;
|
|
149
|
+
const originalRequire = runtimeGlobal.require;
|
|
150
|
+
const originalModule = runtimeGlobal.module;
|
|
151
|
+
const originalExports = runtimeGlobal.exports;
|
|
152
|
+
const originalFs = runtimeGlobal.fs;
|
|
153
|
+
const hadStdinHook = Object.prototype.hasOwnProperty.call(runtimeGlobal, stdinHookKey);
|
|
154
|
+
const originalStdinHook = runtimeGlobal[stdinHookKey];
|
|
155
|
+
const assetEntries = assetFiles
|
|
156
|
+
.filter((artifact) => artifact.data instanceof Uint8Array)
|
|
157
|
+
.map((assetFile) => {
|
|
158
|
+
const copiedAssetData = new Uint8Array(assetFile.data.byteLength);
|
|
159
|
+
copiedAssetData.set(assetFile.data);
|
|
160
|
+
const objectUrl = URL.createObjectURL(new Blob([copiedAssetData], {
|
|
161
|
+
type: assetFile.path.endsWith('.wasm')
|
|
162
|
+
? 'application/wasm'
|
|
163
|
+
: 'application/octet-stream'
|
|
164
|
+
}));
|
|
165
|
+
createdObjectUrls.push(objectUrl);
|
|
166
|
+
const relativeFromSourceDir = assetFile.path.startsWith(`${sourceDir}/`)
|
|
167
|
+
? assetFile.path.slice(sourceDir.length + 1)
|
|
168
|
+
: assetFile.path.replace(/^\/+/, '');
|
|
169
|
+
return {
|
|
170
|
+
path: assetFile.path,
|
|
171
|
+
basename: assetFile.path.split('/').at(-1) || assetFile.path,
|
|
172
|
+
relativeFromSourceDir,
|
|
173
|
+
objectUrl
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
const resolveAsset = (requestedAsset) => {
|
|
177
|
+
const candidates = [String(requestedAsset)];
|
|
178
|
+
try {
|
|
179
|
+
candidates.push(new URL(String(requestedAsset), self.location.href).pathname);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// ignore
|
|
183
|
+
}
|
|
184
|
+
for (const assetEntry of assetEntries) {
|
|
185
|
+
if (candidates.some((candidate) => candidate === assetEntry.path ||
|
|
186
|
+
candidate === assetEntry.relativeFromSourceDir ||
|
|
187
|
+
candidate.endsWith(`/${assetEntry.relativeFromSourceDir}`) ||
|
|
188
|
+
candidate.endsWith(`/${assetEntry.basename}`))) {
|
|
189
|
+
return assetEntry.objectUrl;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
};
|
|
194
|
+
const readOcamlStdinBytes = (requestedBytes) => {
|
|
195
|
+
if (!stdinBufferOcaml) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
if (requestedBytes <= 0) {
|
|
199
|
+
return new Uint8Array(0);
|
|
200
|
+
}
|
|
201
|
+
while (stdinChunkOffsetOcaml >= stdinChunkOcaml.length) {
|
|
202
|
+
const chunk = waitForBufferedStdin(stdinBufferOcaml, () => postMessage({ buffer: true }));
|
|
203
|
+
if (chunk == null) {
|
|
204
|
+
stdinChunkOcaml = new Uint8Array(0);
|
|
205
|
+
stdinChunkOffsetOcaml = 0;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
stdinChunkOcaml = stdinEncoder.encode(chunk);
|
|
209
|
+
stdinChunkOffsetOcaml = 0;
|
|
210
|
+
}
|
|
211
|
+
const end = Math.min(stdinChunkOffsetOcaml + requestedBytes, stdinChunkOcaml.length);
|
|
212
|
+
const bytes = stdinChunkOcaml.slice(stdinChunkOffsetOcaml, end);
|
|
213
|
+
stdinChunkOffsetOcaml = end;
|
|
214
|
+
return bytes;
|
|
215
|
+
};
|
|
216
|
+
globalThis.console = {
|
|
217
|
+
...originalConsole,
|
|
218
|
+
log: (...args) => {
|
|
219
|
+
postMessage({
|
|
220
|
+
output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
|
|
221
|
+
});
|
|
222
|
+
originalConsole.log(...args);
|
|
223
|
+
},
|
|
224
|
+
info: (...args) => {
|
|
225
|
+
postMessage({
|
|
226
|
+
output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
|
|
227
|
+
});
|
|
228
|
+
originalConsole.info(...args);
|
|
229
|
+
},
|
|
230
|
+
warn: (...args) => {
|
|
231
|
+
postMessage({
|
|
232
|
+
output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
|
|
233
|
+
});
|
|
234
|
+
originalConsole.warn(...args);
|
|
235
|
+
},
|
|
236
|
+
error: (...args) => {
|
|
237
|
+
postMessage({
|
|
238
|
+
output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
|
|
239
|
+
});
|
|
240
|
+
originalConsole.error(...args);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
WebAssembly.instantiate = (async (source, importObject) => {
|
|
244
|
+
return await originalInstantiate(source, importObject);
|
|
245
|
+
});
|
|
246
|
+
if (originalInstantiateStreaming) {
|
|
247
|
+
WebAssembly.instantiateStreaming = (async (source, importObject) => {
|
|
248
|
+
return await originalInstantiateStreaming(source, importObject);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
globalThis[assetResolverKey] = resolveAsset;
|
|
252
|
+
globalThis.fetch = (async (input, init) => {
|
|
253
|
+
const requestUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
254
|
+
const resolvedAssetUrl = resolveAsset(requestUrl);
|
|
255
|
+
if (resolvedAssetUrl) {
|
|
256
|
+
return await originalFetch(resolvedAssetUrl, init);
|
|
257
|
+
}
|
|
258
|
+
return await originalFetch(input, init);
|
|
259
|
+
});
|
|
260
|
+
runtimeGlobal.process = undefined;
|
|
261
|
+
runtimeGlobal[stdinHookKey] = (requestedBytes = Number.MAX_SAFE_INTEGER) => {
|
|
262
|
+
const encoded = readOcamlStdinBytes(requestedBytes);
|
|
263
|
+
if (encoded == null) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
let byteString = '';
|
|
267
|
+
for (const byte of encoded) {
|
|
268
|
+
byteString += String.fromCharCode(byte);
|
|
269
|
+
}
|
|
270
|
+
return byteString;
|
|
271
|
+
};
|
|
272
|
+
const stdinFsShim = {
|
|
273
|
+
readSync(fileDescriptor, buffer, offset = 0, length = buffer.byteLength - offset) {
|
|
274
|
+
if (fileDescriptor !== 0 || !stdinBufferOcaml) {
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
const encoded = readOcamlStdinBytes(length);
|
|
278
|
+
if (encoded == null) {
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
buffer.set(encoded, offset);
|
|
282
|
+
return encoded.byteLength;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
runtimeGlobal.fs = stdinFsShim;
|
|
286
|
+
runtimeGlobal.require = (specifier) => {
|
|
287
|
+
if (specifier === 'fs' || specifier === 'node:fs') {
|
|
288
|
+
return stdinFsShim;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`unsupported OCaml runtime require: ${specifier}`);
|
|
291
|
+
};
|
|
292
|
+
runtimeGlobal.module = { exports: {} };
|
|
293
|
+
runtimeGlobal.exports = runtimeGlobal.module.exports;
|
|
294
|
+
try {
|
|
295
|
+
let normalizedSource = normalizeAssetLoader(programArtifact.data, runtimePromiseKey);
|
|
296
|
+
const stdinReadReplacement = 'read($1,$2,$3,$4){var e=globalThis.__wasmIdleOcamlReadStdin?globalThis.__wasmIdleOcamlReadStdin($3):null;if(e==null)return 0;var h=e.length,f=new Uint8Array(h),i=0;for(;i<h;i++)f[i]=e.charCodeAt(i);var g=f.length<$3?f.length:$3;$1.set(f.subarray(0,g),$2);return g}';
|
|
297
|
+
const patchedSource = normalizedSource.replace(/read\(([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*)\)\{cz\(\4,mc,aha,ow\)\}/, stdinReadReplacement);
|
|
298
|
+
normalizedSource =
|
|
299
|
+
patchedSource !== normalizedSource
|
|
300
|
+
? patchedSource
|
|
301
|
+
: normalizedSource.replace(/read\(([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*)\)\{[^{}]+\}(?=seek\([A-Za-z$_][\w$]*,[A-Za-z$_][\w$]*,[A-Za-z$_][\w$]*\)\{[^{}]+\}pos\(\)\{return-1\}close\(\)\{this\.log=undefined\}check_stream_semantics\([A-Za-z$_][\w$]*\)\{\})/, stdinReadReplacement);
|
|
302
|
+
normalizedSource = normalizedSource
|
|
303
|
+
.replace(/\bfs\.readSync\(/g, 'globalThis.fs.readSync(')
|
|
304
|
+
.replace(/read:\(([^)]*)\)=>[A-Za-z$_][\w$]*\.readSync\(/g, 'read:($1)=>globalThis.fs.readSync(');
|
|
305
|
+
if (log && normalizedSource === programArtifact.data) {
|
|
306
|
+
console.warn('[wasm-idle:ocaml-worker] stdin runtime patch did not match generated JS');
|
|
307
|
+
}
|
|
308
|
+
new Function(`${normalizedSource}\n//# sourceURL=${programArtifact.path}`)();
|
|
309
|
+
const runtimePromise = globalThis[runtimePromiseKey];
|
|
310
|
+
if (runtimePromise instanceof Promise) {
|
|
311
|
+
await runtimePromise;
|
|
312
|
+
}
|
|
313
|
+
postMessage({ results: true });
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
globalThis.console = originalConsole;
|
|
317
|
+
globalThis.fetch = originalFetch;
|
|
318
|
+
WebAssembly.instantiate = originalInstantiate;
|
|
319
|
+
if (originalInstantiateStreaming) {
|
|
320
|
+
WebAssembly.instantiateStreaming = originalInstantiateStreaming;
|
|
321
|
+
}
|
|
322
|
+
delete globalThis[runtimePromiseKey];
|
|
323
|
+
delete globalThis[assetResolverKey];
|
|
324
|
+
if (!hadProcess) {
|
|
325
|
+
delete runtimeGlobal.process;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
runtimeGlobal.process = originalProcess;
|
|
329
|
+
}
|
|
330
|
+
if (!hadRequire) {
|
|
331
|
+
delete runtimeGlobal.require;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
runtimeGlobal.require = originalRequire;
|
|
335
|
+
}
|
|
336
|
+
if (!hadModule) {
|
|
337
|
+
delete runtimeGlobal.module;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
runtimeGlobal.module = originalModule;
|
|
341
|
+
}
|
|
342
|
+
if (!hadExports) {
|
|
343
|
+
delete runtimeGlobal.exports;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
runtimeGlobal.exports = originalExports;
|
|
347
|
+
}
|
|
348
|
+
if (!hadFs) {
|
|
349
|
+
delete runtimeGlobal.fs;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
runtimeGlobal.fs = originalFs;
|
|
353
|
+
}
|
|
354
|
+
if (!hadStdinHook) {
|
|
355
|
+
delete runtimeGlobal[stdinHookKey];
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
runtimeGlobal[stdinHookKey] = originalStdinHook;
|
|
359
|
+
}
|
|
360
|
+
for (const objectUrl of createdObjectUrls) {
|
|
361
|
+
URL.revokeObjectURL(objectUrl);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
self.onmessage = async (event) => {
|
|
366
|
+
const { load, moduleUrl: nextModuleUrl, manifestUrl: nextManifestUrl, code, prepare, target = 'wasm', log = true, buffer } = event.data;
|
|
367
|
+
try {
|
|
368
|
+
if (load) {
|
|
369
|
+
moduleUrl = nextModuleUrl;
|
|
370
|
+
manifestUrl = nextManifestUrl;
|
|
371
|
+
await Promise.all([loadCompiler(moduleUrl), loadManifest(manifestUrl)]);
|
|
372
|
+
postMessage({ load: true });
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
stdinBufferOcaml = buffer ? new Int32Array(buffer) : null;
|
|
376
|
+
if (log) {
|
|
377
|
+
console.log(`[wasm-idle:ocaml-worker] compile start prepare=${prepare} target=${target}`);
|
|
378
|
+
}
|
|
379
|
+
postMessage({ progress: { stage: 'compile-bootstrap', percent: 10 } });
|
|
380
|
+
const [compilerModule, manifest] = await Promise.all([
|
|
381
|
+
loadCompiler(moduleUrl),
|
|
382
|
+
loadManifest(manifestUrl)
|
|
383
|
+
]);
|
|
384
|
+
postMessage({ progress: { stage: 'compile-ready', percent: 25 } });
|
|
385
|
+
const compileKey = `${target}\n${code}`;
|
|
386
|
+
let compiledFresh = false;
|
|
387
|
+
if (!compiledResult || compiledCacheKey !== compileKey) {
|
|
388
|
+
const result = await compilerModule.compile({
|
|
389
|
+
files: {
|
|
390
|
+
'main.ml': code
|
|
391
|
+
},
|
|
392
|
+
entry: 'main.ml',
|
|
393
|
+
target,
|
|
394
|
+
effectsMode: 'cps'
|
|
395
|
+
}, {
|
|
396
|
+
system: compilerModule.createBrowserWorkerSystemDispatcher({
|
|
397
|
+
manifest
|
|
398
|
+
}),
|
|
399
|
+
toolchainRoot: '/static/toolchain'
|
|
400
|
+
});
|
|
401
|
+
compiledResult = result;
|
|
402
|
+
compiledCacheKey = compileKey;
|
|
403
|
+
compiledFresh = true;
|
|
404
|
+
}
|
|
405
|
+
const result = compiledResult;
|
|
406
|
+
if (log) {
|
|
407
|
+
console.log(`[wasm-idle:ocaml-worker] compile settled success=${result.success} diagnostics=${result.diagnostics?.length || 0}`);
|
|
408
|
+
}
|
|
409
|
+
if (compiledFresh && result.stdout) {
|
|
410
|
+
postMessage({ output: result.stdout });
|
|
411
|
+
}
|
|
412
|
+
if ((compiledFresh || !result.success) && result.stderr) {
|
|
413
|
+
postMessage({ output: result.stderr });
|
|
414
|
+
}
|
|
415
|
+
for (const diagnostic of result.diagnostics || []) {
|
|
416
|
+
postMessage({
|
|
417
|
+
diagnostic: {
|
|
418
|
+
fileName: diagnostic.file ?? null,
|
|
419
|
+
lineNumber: Math.max(1, Number(diagnostic.line || 1)),
|
|
420
|
+
columnNumber: typeof diagnostic.column === 'number'
|
|
421
|
+
? Math.max(1, diagnostic.column)
|
|
422
|
+
: undefined,
|
|
423
|
+
severity: diagnostic.severity === 'warning' || diagnostic.severity === 'other'
|
|
424
|
+
? diagnostic.severity
|
|
425
|
+
: 'error',
|
|
426
|
+
message: String(diagnostic.message || '')
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
postMessage({ progress: { stage: 'compile-finished', percent: 90 } });
|
|
431
|
+
if (!result.success) {
|
|
432
|
+
throw new Error(result.stderr ||
|
|
433
|
+
result.diagnostics?.map((diagnostic) => diagnostic.message || '').join('\n') ||
|
|
434
|
+
'OCaml compilation failed');
|
|
435
|
+
}
|
|
436
|
+
if (prepare) {
|
|
437
|
+
postMessage({ results: true });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
postMessage({ progress: { stage: 'runtime-start', percent: 95 } });
|
|
441
|
+
await executeCompileResult(result, log);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
if (log) {
|
|
445
|
+
console.error('[wasm-idle:ocaml-worker] failed', error);
|
|
446
|
+
}
|
|
447
|
+
postMessage({ error: error?.message || String(error) });
|
|
448
|
+
}
|
|
449
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ConsoleStdout, Fd, WASI, WASIProcExit, wasi } from '@bjorn3/browser_wasi_shim';
|
|
2
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
3
|
+
self.document = {
|
|
4
|
+
querySelectorAll() {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
let stdinBufferTinyGo = null;
|
|
11
|
+
let stdinChunkTinyGo = new Uint8Array(0);
|
|
12
|
+
let stdinChunkOffsetTinyGo = 0;
|
|
13
|
+
class TinyGoStdin extends Fd {
|
|
14
|
+
fd_fdstat_get() {
|
|
15
|
+
const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);
|
|
16
|
+
fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);
|
|
17
|
+
fdstat.fs_rights_inherited = 0n;
|
|
18
|
+
return { ret: wasi.ERRNO_SUCCESS, fdstat };
|
|
19
|
+
}
|
|
20
|
+
fd_filestat_get() {
|
|
21
|
+
return {
|
|
22
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
23
|
+
filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
fd_read(size) {
|
|
27
|
+
if (stdinBufferTinyGo === null) {
|
|
28
|
+
return {
|
|
29
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
30
|
+
data: new Uint8Array(0)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (stdinChunkOffsetTinyGo >= stdinChunkTinyGo.length) {
|
|
34
|
+
const nextChunk = waitForBufferedStdin(stdinBufferTinyGo, () => postMessage({ buffer: true }));
|
|
35
|
+
if (nextChunk === null) {
|
|
36
|
+
stdinChunkTinyGo = new Uint8Array(0);
|
|
37
|
+
stdinChunkOffsetTinyGo = 0;
|
|
38
|
+
return {
|
|
39
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
40
|
+
data: new Uint8Array(0)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
stdinChunkTinyGo = encoder.encode(nextChunk);
|
|
44
|
+
stdinChunkOffsetTinyGo = 0;
|
|
45
|
+
}
|
|
46
|
+
const end = Math.min(stdinChunkOffsetTinyGo + size, stdinChunkTinyGo.length);
|
|
47
|
+
const data = stdinChunkTinyGo.slice(stdinChunkOffsetTinyGo, end);
|
|
48
|
+
stdinChunkOffsetTinyGo = end;
|
|
49
|
+
return {
|
|
50
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
51
|
+
data
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
self.onmessage = async (event) => {
|
|
56
|
+
const { load, artifact, buffer, args = [], log } = event.data;
|
|
57
|
+
try {
|
|
58
|
+
if (load) {
|
|
59
|
+
postMessage({ load: true });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const bytes = artifact instanceof Uint8Array
|
|
63
|
+
? artifact
|
|
64
|
+
: artifact instanceof ArrayBuffer
|
|
65
|
+
? new Uint8Array(artifact)
|
|
66
|
+
: ArrayBuffer.isView(artifact)
|
|
67
|
+
? new Uint8Array(artifact.buffer, artifact.byteOffset, artifact.byteLength)
|
|
68
|
+
: null;
|
|
69
|
+
if (!bytes) {
|
|
70
|
+
throw new Error('TinyGo worker expected a wasm artifact');
|
|
71
|
+
}
|
|
72
|
+
stdinBufferTinyGo = new Int32Array(buffer);
|
|
73
|
+
stdinChunkTinyGo = new Uint8Array(0);
|
|
74
|
+
stdinChunkOffsetTinyGo = 0;
|
|
75
|
+
const stdout = new ConsoleStdout((chunk) => {
|
|
76
|
+
const text = decoder.decode(chunk);
|
|
77
|
+
if (text)
|
|
78
|
+
postMessage({ output: text });
|
|
79
|
+
});
|
|
80
|
+
const stderr = new ConsoleStdout((chunk) => {
|
|
81
|
+
const text = decoder.decode(chunk);
|
|
82
|
+
if (text)
|
|
83
|
+
postMessage({ output: text });
|
|
84
|
+
});
|
|
85
|
+
const wasiRuntime = new WASI(args, ['USER=jungol'], [new TinyGoStdin(), stdout, stderr]);
|
|
86
|
+
const wasmModule = (await WebAssembly.instantiate(bytes, {
|
|
87
|
+
wasi_snapshot_preview1: wasiRuntime.wasiImport,
|
|
88
|
+
wasi_unstable: wasiRuntime.wasiImport
|
|
89
|
+
}));
|
|
90
|
+
const instance = typeof WebAssembly.Instance === 'function' && wasmModule instanceof WebAssembly.Instance
|
|
91
|
+
? wasmModule
|
|
92
|
+
: 'instance' in wasmModule
|
|
93
|
+
? wasmModule.instance
|
|
94
|
+
: wasmModule;
|
|
95
|
+
const exportsObject = instance.exports;
|
|
96
|
+
let exitCode = 0;
|
|
97
|
+
try {
|
|
98
|
+
if (typeof exportsObject._start === 'function') {
|
|
99
|
+
exitCode = wasiRuntime.start(instance);
|
|
100
|
+
}
|
|
101
|
+
else if (typeof exportsObject.memory === 'object' &&
|
|
102
|
+
typeof exportsObject.main === 'function') {
|
|
103
|
+
wasiRuntime.initialize(instance);
|
|
104
|
+
if (typeof exportsObject._initialize !== 'function' &&
|
|
105
|
+
typeof exportsObject.__wasm_call_ctors === 'function') {
|
|
106
|
+
exportsObject.__wasm_call_ctors();
|
|
107
|
+
}
|
|
108
|
+
const mainResult = exportsObject.main();
|
|
109
|
+
exitCode = typeof mainResult === 'number' ? mainResult : 0;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
throw new Error('TinyGo worker expected a WASI artifact with _start or a reactor artifact with main');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error instanceof WASIProcExit) {
|
|
117
|
+
exitCode = error.code;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (log) {
|
|
124
|
+
console.log(`[wasm-idle:tinygo-worker] wasi run complete exitCode=${String(exitCode)}`);
|
|
125
|
+
}
|
|
126
|
+
if (exitCode !== 0) {
|
|
127
|
+
throw new Error(`TinyGo program exited with code ${exitCode}`);
|
|
128
|
+
}
|
|
129
|
+
postMessage({ results: true });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (log) {
|
|
133
|
+
console.error('[wasm-idle:tinygo-worker] failed', error);
|
|
134
|
+
}
|
|
135
|
+
postMessage({ error: error?.message || String(error) });
|
|
136
|
+
}
|
|
137
|
+
};
|