wasm-idle 1.0.8 → 1.0.9
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 +48 -11
- package/dist/playground/applicationAssets.js +18 -0
- package/dist/playground/assets.d.ts +70 -0
- package/dist/playground/assets.js +120 -0
- package/dist/playground/cachedClang.js +43 -5
- package/dist/playground/clang.d.ts +5 -0
- package/dist/playground/clang.js +96 -0
- package/dist/playground/clangWorkerProtocol.d.ts +12 -1
- package/dist/playground/fennel.d.ts +9 -0
- package/dist/playground/fennel.js +30 -0
- package/dist/playground/fennelBootstrap.d.ts +31 -0
- package/dist/playground/fennelBootstrap.js +118 -0
- package/dist/playground/grain.d.ts +9 -0
- package/dist/playground/grain.js +127 -0
- package/dist/playground/hy.d.ts +21 -0
- package/dist/playground/hy.js +24 -0
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +49 -0
- package/dist/playground/lua.d.ts +19 -0
- package/dist/playground/lua.js +64 -39
- package/dist/playground/objectivec.d.ts +3 -1
- package/dist/playground/objectivec.js +6 -2
- package/dist/playground/options.js +1 -1
- package/dist/playground/postgresql.d.ts +36 -0
- package/dist/playground/postgresql.js +543 -0
- package/dist/playground/python.d.ts +5 -0
- package/dist/playground/python.js +13 -0
- package/dist/playground/rescript.d.ts +5 -0
- package/dist/playground/rescript.js +58 -0
- package/dist/playground/rescriptPreflight.d.ts +26 -0
- package/dist/playground/rescriptPreflight.js +381 -0
- package/dist/playground/staticRuntimeModuleVersion.d.ts +1 -1
- package/dist/playground/staticRuntimeModuleVersion.js +1 -1
- package/dist/playground/staticRuntimePreflightExecute.d.ts +2 -1
- package/dist/playground/staticRuntimePreflightExecute.js +9 -0
- package/dist/playground/staticRuntimePreflightProtocol.d.ts +1 -1
- package/dist/playground/v.d.ts +46 -0
- package/dist/playground/v.js +703 -0
- package/dist/playground/wasmFennelVersion.d.ts +23 -0
- package/dist/playground/wasmFennelVersion.js +16 -0
- package/dist/playground/wasmGrainVersion.d.ts +24 -0
- package/dist/playground/wasmGrainVersion.js +25 -0
- package/dist/playground/wasmHyVersion.d.ts +7 -0
- package/dist/playground/wasmHyVersion.js +15 -0
- package/dist/playground/wasmReScriptVersion.d.ts +20 -0
- package/dist/playground/wasmReScriptVersion.js +20 -0
- package/dist/playground/worker/cachedClangCompile.js +37 -4
- package/dist/playground/worker/clang.js +35 -7
- package/dist/playground/worker/lua.js +48 -11
- package/dist/playground/worker/postgresql.d.ts +1 -0
- package/dist/playground/worker/postgresql.js +205 -0
- package/dist/playground/worker/python.js +106 -9
- package/dist/playground/worker/staticRuntimePreflight.js +2 -1
- package/dist/playground/worker/v.d.ts +1 -0
- package/dist/playground/worker/v.js +106 -0
- package/package.json +18 -7
|
@@ -9,8 +9,10 @@ import { WASM_FORTH_ASSET_VERSION, WASM_FORTH_RUNTIME_PROFILE, WASM_FORTH_RUNNER
|
|
|
9
9
|
import { WASM_BQN_ASSET_VERSION, WASM_BQN_RUNTIME_PROFILE, WASM_BQN_RUNNER_RECEIPT } from './wasmBqnVersion';
|
|
10
10
|
import { WASM_BASH_RUNTIME_BUNDLE } from './wasmBashVersion';
|
|
11
11
|
import { WASM_CLOJURESCRIPT_ASSET_VERSION, WASM_CLOJURESCRIPT_RUNNER_RECEIPT, WASM_CLOJURESCRIPT_RUNTIME_PROFILE } from './wasmClojureScriptVersion';
|
|
12
|
+
import { WASM_RESCRIPT_ASSET_VERSION, WASM_RESCRIPT_RUNNER_RECEIPT, WASM_RESCRIPT_RUNTIME_PROFILE } from './wasmReScriptVersion';
|
|
12
13
|
import { WASM_J_ASSET_VERSION, WASM_J_RUNNER_RECEIPT, WASM_J_RUNTIME_PROFILE } from './wasmJVersion';
|
|
13
14
|
import { WASM_JANET_RUNTIME_BUNDLE } from './wasmJanetVersion';
|
|
15
|
+
import { WASM_FENNEL_COMPILER_RECEIPT } from './wasmFennelVersion';
|
|
14
16
|
import { WASM_JULIA_RUNTIME_BUNDLE } from './wasmJuliaVersion';
|
|
15
17
|
import { WASM_LISP_ASSET_VERSION } from './wasmLispVersion';
|
|
16
18
|
import { WASM_NIM_RUNTIME_BUNDLE } from './wasmNimVersion';
|
|
@@ -482,6 +484,18 @@ export function resolveLuaModuleUrl(options, currentUrl = '') {
|
|
|
482
484
|
}
|
|
483
485
|
return '';
|
|
484
486
|
}
|
|
487
|
+
export function resolveFennelCompilerUrl(options, currentUrl = '') {
|
|
488
|
+
const configuredCompilerUrl = (typeof options === 'object' && options?.fennel?.compilerUrl) ||
|
|
489
|
+
(publicEnv.PUBLIC_WASM_FENNEL_COMPILER_URL || '').trim();
|
|
490
|
+
if (configuredCompilerUrl) {
|
|
491
|
+
return resolveConfiguredUrl(configuredCompilerUrl, currentUrl);
|
|
492
|
+
}
|
|
493
|
+
const rootUrl = typeof options === 'string' ? options : options?.rootUrl;
|
|
494
|
+
if (rootUrl) {
|
|
495
|
+
return resolveConfiguredUrl(`${normalizeRootUrl(rootUrl) || ''}/${WASM_FENNEL_COMPILER_RECEIPT.path}`, currentUrl);
|
|
496
|
+
}
|
|
497
|
+
return '';
|
|
498
|
+
}
|
|
485
499
|
export function resolveZigCompilerUrl(options, currentUrl = '') {
|
|
486
500
|
const configuredCompilerUrl = (typeof options === 'object' && options?.zig?.compilerUrl) ||
|
|
487
501
|
(publicEnv.PUBLIC_WASM_ZIG_COMPILER_URL || '').trim();
|
|
@@ -600,6 +614,20 @@ export function resolveCobolBaseUrl(options, currentUrl = '') {
|
|
|
600
614
|
}
|
|
601
615
|
return normalizeBaseUrl('/wasm-cobol/', currentUrl);
|
|
602
616
|
}
|
|
617
|
+
export function resolveVBaseUrl(options, currentUrl = '') {
|
|
618
|
+
const configuredBaseUrl = (typeof options === 'object' && options?.v?.baseUrl) ||
|
|
619
|
+
(publicEnv.PUBLIC_WASM_V_BASE_URL || '').trim();
|
|
620
|
+
if (configuredBaseUrl) {
|
|
621
|
+
return normalizeBaseUrl(configuredBaseUrl, currentUrl);
|
|
622
|
+
}
|
|
623
|
+
if (typeof options === 'string') {
|
|
624
|
+
return normalizeBaseUrl(`${normalizeRootUrl(options) || ''}/wasm-v/`, currentUrl);
|
|
625
|
+
}
|
|
626
|
+
if (options?.rootUrl) {
|
|
627
|
+
return normalizeBaseUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-v/`, currentUrl);
|
|
628
|
+
}
|
|
629
|
+
return normalizeBaseUrl('/wasm-v/', currentUrl);
|
|
630
|
+
}
|
|
603
631
|
export function resolveObjectiveCBaseUrl(options, currentUrl = '') {
|
|
604
632
|
const configuredBaseUrl = (typeof options === 'object' && options?.objectivec?.baseUrl) ||
|
|
605
633
|
(publicEnv.PUBLIC_WASM_OBJECTIVEC_BASE_URL || '').trim();
|
|
@@ -1589,6 +1617,77 @@ export function resolveClojureScriptRuntimeAssetConfig(options, currentUrl = '')
|
|
|
1589
1617
|
workerReceipt: configured?.workerReceipt || WASM_CLOJURESCRIPT_RUNNER_RECEIPT
|
|
1590
1618
|
};
|
|
1591
1619
|
}
|
|
1620
|
+
export function resolveReScriptBaseUrl(options, currentUrl = '') {
|
|
1621
|
+
const configuredBaseUrl = (typeof options === 'object' && options?.rescript?.baseUrl) ||
|
|
1622
|
+
(publicEnv.PUBLIC_WASM_RESCRIPT_BASE_URL || '').trim();
|
|
1623
|
+
if (configuredBaseUrl)
|
|
1624
|
+
return normalizeBaseUrl(configuredBaseUrl, currentUrl);
|
|
1625
|
+
if (typeof options === 'string') {
|
|
1626
|
+
return normalizeBaseUrl(`${normalizeRootUrl(options) || ''}/wasm-rescript/`, currentUrl);
|
|
1627
|
+
}
|
|
1628
|
+
if (options?.rootUrl) {
|
|
1629
|
+
return normalizeBaseUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-rescript/`, currentUrl);
|
|
1630
|
+
}
|
|
1631
|
+
return normalizeBaseUrl('/wasm-rescript/', currentUrl);
|
|
1632
|
+
}
|
|
1633
|
+
export function resolveReScriptWorkerUrl(options, currentUrl = '') {
|
|
1634
|
+
const configuredWorkerUrl = (typeof options === 'object' && options?.rescript?.workerUrl) ||
|
|
1635
|
+
(publicEnv.PUBLIC_WASM_RESCRIPT_WORKER_URL || '').trim();
|
|
1636
|
+
if (configuredWorkerUrl)
|
|
1637
|
+
return resolveConfiguredUrl(configuredWorkerUrl, currentUrl);
|
|
1638
|
+
if (typeof options === 'string') {
|
|
1639
|
+
return resolveConfiguredUrl(`${normalizeRootUrl(options) || ''}/wasm-rescript/runner-worker.js`, currentUrl);
|
|
1640
|
+
}
|
|
1641
|
+
if (options?.rootUrl) {
|
|
1642
|
+
return resolveConfiguredUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-rescript/runner-worker.js`, currentUrl);
|
|
1643
|
+
}
|
|
1644
|
+
return resolveConfiguredUrl('/wasm-rescript/runner-worker.js', currentUrl);
|
|
1645
|
+
}
|
|
1646
|
+
export function resolveReScriptManifestUrl(options, currentUrl = '') {
|
|
1647
|
+
const configuredManifestUrl = typeof options === 'object' ? options?.rescript?.manifestUrl : undefined;
|
|
1648
|
+
if (configuredManifestUrl) {
|
|
1649
|
+
return resolveConfiguredUrl(configuredManifestUrl, currentUrl);
|
|
1650
|
+
}
|
|
1651
|
+
return `${resolveReScriptBaseUrl(options, currentUrl)}runtime-manifest.v1.json`;
|
|
1652
|
+
}
|
|
1653
|
+
export function resolveReScriptRuntimeAssetConfig(options, currentUrl = '') {
|
|
1654
|
+
const configured = typeof options === 'object' ? options?.rescript : undefined;
|
|
1655
|
+
const manifestFingerprint = configured?.manifestFingerprint?.trim() || WASM_RESCRIPT_ASSET_VERSION;
|
|
1656
|
+
const usesBundledProfile = manifestFingerprint === WASM_RESCRIPT_ASSET_VERSION;
|
|
1657
|
+
const receipt = (configuredReceipt, bundledReceipt) => {
|
|
1658
|
+
const selected = configuredReceipt || (usesBundledProfile ? bundledReceipt : undefined);
|
|
1659
|
+
return selected
|
|
1660
|
+
? Object.freeze({
|
|
1661
|
+
bytes: selected.bytes,
|
|
1662
|
+
sha256: selected.sha256,
|
|
1663
|
+
...(selected.uncompressedBytes === undefined
|
|
1664
|
+
? {}
|
|
1665
|
+
: { uncompressedBytes: selected.uncompressedBytes }),
|
|
1666
|
+
...(selected.uncompressedSha256 === undefined
|
|
1667
|
+
? {}
|
|
1668
|
+
: { uncompressedSha256: selected.uncompressedSha256 })
|
|
1669
|
+
})
|
|
1670
|
+
: undefined;
|
|
1671
|
+
};
|
|
1672
|
+
const preflightProfile = Object.freeze({
|
|
1673
|
+
profileId: configured?.profileId?.trim() ||
|
|
1674
|
+
(usesBundledProfile ? WASM_RESCRIPT_RUNTIME_PROFILE.profileId : ''),
|
|
1675
|
+
sourceRevision: configured?.sourceRevision?.trim() ||
|
|
1676
|
+
(usesBundledProfile ? WASM_RESCRIPT_RUNTIME_PROFILE.sourceRevision : ''),
|
|
1677
|
+
manifestFingerprint,
|
|
1678
|
+
manifestReceipt: receipt(configured?.manifestReceipt, WASM_RESCRIPT_RUNTIME_PROFILE.manifestReceipt),
|
|
1679
|
+
compilerReceipt: receipt(configured?.compilerReceipt, WASM_RESCRIPT_RUNTIME_PROFILE.compilerReceipt)
|
|
1680
|
+
});
|
|
1681
|
+
return {
|
|
1682
|
+
baseUrl: resolveReScriptBaseUrl(options, currentUrl),
|
|
1683
|
+
workerUrl: resolveReScriptWorkerUrl(options, currentUrl),
|
|
1684
|
+
manifestUrl: resolveReScriptManifestUrl(options, currentUrl),
|
|
1685
|
+
manifestFingerprint,
|
|
1686
|
+
preflightKey: JSON.stringify(preflightProfile),
|
|
1687
|
+
preflightProfile,
|
|
1688
|
+
workerReceipt: configured?.workerReceipt || WASM_RESCRIPT_RUNNER_RECEIPT
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1592
1691
|
export function resolveForthBaseUrl(options, currentUrl = '') {
|
|
1593
1692
|
const configuredBaseUrl = (typeof options === 'object' && options?.forth?.baseUrl) ||
|
|
1594
1693
|
(publicEnv.PUBLIC_WASM_FORTH_BASE_URL || '').trim();
|
|
@@ -2268,6 +2367,9 @@ export function resolveDuckDbRuntimeModuleUrl(options, currentUrl = '') {
|
|
|
2268
2367
|
export function resolvePhpRuntimeModuleUrl(options, currentUrl = '') {
|
|
2269
2368
|
return resolveStaticRuntimeModuleUrl(options, typeof options === 'object' ? options?.php?.moduleUrl : undefined, publicEnv.PUBLIC_WASM_PHP_MODULE_URL || '', 'wasm-php', currentUrl);
|
|
2270
2369
|
}
|
|
2370
|
+
export function resolvePostgresqlRuntimeModuleUrl(options, currentUrl = '') {
|
|
2371
|
+
return resolveStaticRuntimeModuleUrl(options, typeof options === 'object' ? options?.postgresql?.moduleUrl : undefined, publicEnv.PUBLIC_WASM_POSTGRESQL_MODULE_URL || '', 'wasm-postgresql', currentUrl);
|
|
2372
|
+
}
|
|
2271
2373
|
export function resolveRubyRuntimeModuleUrl(options, currentUrl = '') {
|
|
2272
2374
|
return resolveRubyRuntimeAssetConfig(options, currentUrl).moduleUrl;
|
|
2273
2375
|
}
|
|
@@ -2283,3 +2385,21 @@ export function resolveC3BaseUrl(options, currentUrl = '') {
|
|
|
2283
2385
|
const root = typeof options === 'string' ? options : options?.rootUrl;
|
|
2284
2386
|
return normalizeBaseUrl(`${normalizeRootUrl(root || '') || ''}/wasm-c3/`, currentUrl);
|
|
2285
2387
|
}
|
|
2388
|
+
/** Grain uses one code-pinned compiler/stdlib/worker receipt, served from this base URL. */
|
|
2389
|
+
export function resolveGrainBaseUrl(options, currentUrl = '') {
|
|
2390
|
+
const configured = (typeof options === 'object' && options?.grain?.baseUrl) ||
|
|
2391
|
+
(publicEnv.PUBLIC_WASM_GRAIN_BASE_URL || '').trim();
|
|
2392
|
+
if (configured)
|
|
2393
|
+
return normalizeBaseUrl(configured, currentUrl);
|
|
2394
|
+
const root = typeof options === 'string' ? options : options?.rootUrl;
|
|
2395
|
+
return normalizeBaseUrl(`${normalizeRootUrl(root || '') || ''}/wasm-grain/`, currentUrl);
|
|
2396
|
+
}
|
|
2397
|
+
/** Hy wheels are code-pinned receipts served from this base URL and installed into Pyodide. */
|
|
2398
|
+
export function resolveHyBaseUrl(options, currentUrl = '') {
|
|
2399
|
+
const configured = (typeof options === 'object' && options?.hy?.baseUrl) ||
|
|
2400
|
+
(publicEnv.PUBLIC_WASM_HY_BASE_URL || '').trim();
|
|
2401
|
+
if (configured)
|
|
2402
|
+
return normalizeBaseUrl(configured, currentUrl);
|
|
2403
|
+
const root = typeof options === 'string' ? options : options?.rootUrl;
|
|
2404
|
+
return normalizeBaseUrl(`${normalizeRootUrl(root || '') || ''}/wasm-hy/`, currentUrl);
|
|
2405
|
+
}
|
|
@@ -32,6 +32,34 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
|
|
|
32
32
|
let pendingEof = false;
|
|
33
33
|
const pendingInput = [];
|
|
34
34
|
const encoder = new TextEncoder();
|
|
35
|
+
// Built in the background by the compile worker after a <bits/stdc++.h> source; it outlives
|
|
36
|
+
// runs and cancellations and is discarded only with the sandbox.
|
|
37
|
+
let precompiledHeader;
|
|
38
|
+
let headerWorker;
|
|
39
|
+
function adoptHeaderWorker(worker, bridge) {
|
|
40
|
+
headerWorker?.dispose();
|
|
41
|
+
const dispose = () => {
|
|
42
|
+
if (headerWorker?.worker === worker)
|
|
43
|
+
headerWorker = undefined;
|
|
44
|
+
bridge?.dispose();
|
|
45
|
+
worker.onmessage = null;
|
|
46
|
+
worker.onerror = null;
|
|
47
|
+
worker.onmessageerror = null;
|
|
48
|
+
worker.terminate();
|
|
49
|
+
};
|
|
50
|
+
headerWorker = { worker, dispose };
|
|
51
|
+
worker.onerror = dispose;
|
|
52
|
+
worker.onmessageerror = dispose;
|
|
53
|
+
worker.onmessage = (event) => {
|
|
54
|
+
if (bridge?.handleMessage(event))
|
|
55
|
+
return;
|
|
56
|
+
if (event.data?.type !== 'precompiled-header')
|
|
57
|
+
return;
|
|
58
|
+
if (event.data.header)
|
|
59
|
+
precompiledHeader = event.data.header;
|
|
60
|
+
dispose();
|
|
61
|
+
};
|
|
62
|
+
}
|
|
35
63
|
function resetInput() {
|
|
36
64
|
pendingInput.length = 0;
|
|
37
65
|
pendingEof = false;
|
|
@@ -90,17 +118,21 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
|
|
|
90
118
|
const abort = () => finish(signal?.reason instanceof Error
|
|
91
119
|
? signal.reason
|
|
92
120
|
: new Error('Process terminated'));
|
|
93
|
-
const finish = (error, value) => {
|
|
121
|
+
const finish = (error, value, keepForHeader = false) => {
|
|
94
122
|
if (activeWorker !== worker)
|
|
95
123
|
return;
|
|
96
124
|
activeWorker = undefined;
|
|
97
125
|
cancelWorker = undefined;
|
|
98
126
|
signal?.removeEventListener('abort', abort);
|
|
99
|
-
bridge?.dispose();
|
|
100
127
|
worker.onmessage = null;
|
|
101
128
|
worker.onerror = null;
|
|
102
129
|
worker.onmessageerror = null;
|
|
103
|
-
|
|
130
|
+
if (keepForHeader)
|
|
131
|
+
adoptHeaderWorker(worker, bridge);
|
|
132
|
+
else {
|
|
133
|
+
bridge?.dispose();
|
|
134
|
+
worker.terminate();
|
|
135
|
+
}
|
|
104
136
|
if (error)
|
|
105
137
|
reject(error);
|
|
106
138
|
else
|
|
@@ -142,10 +174,11 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
|
|
|
142
174
|
legacy.output?.(data.stdout);
|
|
143
175
|
if (data.stderr)
|
|
144
176
|
legacy.output?.(data.stderr);
|
|
177
|
+
const keepForHeader = data.precompiledHeader === 'building';
|
|
145
178
|
if (data.type === 'error')
|
|
146
|
-
finish(new Error(data.error));
|
|
179
|
+
finish(new Error(data.error), undefined, keepForHeader);
|
|
147
180
|
else
|
|
148
|
-
finish(undefined, data.artifact);
|
|
181
|
+
finish(undefined, data.artifact, keepForHeader);
|
|
149
182
|
}
|
|
150
183
|
else if (data.type === 'done') {
|
|
151
184
|
if (data.exitCode !== 0 && data.exitCode !== null)
|
|
@@ -247,6 +280,9 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
|
|
|
247
280
|
maxAssetBytes: limit,
|
|
248
281
|
languageSysroots,
|
|
249
282
|
log,
|
|
283
|
+
...(language === 'CPP' && precompiledHeader
|
|
284
|
+
? { precompiledHeader }
|
|
285
|
+
: {}),
|
|
250
286
|
assets: {
|
|
251
287
|
baseUrl: runtimeBaseUrl,
|
|
252
288
|
maxAssetBytes: limit,
|
|
@@ -315,6 +351,8 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
|
|
|
315
351
|
return disposal;
|
|
316
352
|
disposed = true;
|
|
317
353
|
stop();
|
|
354
|
+
headerWorker?.dispose();
|
|
355
|
+
precompiledHeader = undefined;
|
|
318
356
|
disposal = Promise.resolve(legacy.dispose ? legacy.dispose() : legacy.terminate());
|
|
319
357
|
return disposal;
|
|
320
358
|
}
|
|
@@ -36,6 +36,10 @@ declare class Clang implements Sandbox {
|
|
|
36
36
|
private debugMode;
|
|
37
37
|
private readonly lldbBreakpoints;
|
|
38
38
|
private debugEvaluationQueue;
|
|
39
|
+
private precompiledHeader?;
|
|
40
|
+
private precompiledHeaderSent?;
|
|
41
|
+
private headerBuilder?;
|
|
42
|
+
private loadedRuntime?;
|
|
39
43
|
private readonly workerSession;
|
|
40
44
|
private disposeWorkerAssetBridge;
|
|
41
45
|
constructor(language: 'C' | 'CPP');
|
|
@@ -60,6 +64,7 @@ declare class Clang implements Sandbox {
|
|
|
60
64
|
debugDataBreakpointInfo(arguments_: DebugDataBreakpointInfoArguments): Promise<import("@wasm-idle/core").DebugDataBreakpointInfo | null>;
|
|
61
65
|
debugSetDataBreakpoints(breakpoints: DebugDataBreakpoint[]): Promise<import("@wasm-idle/core").DebugResolvedDataBreakpoint[]>;
|
|
62
66
|
kill(): Promise<void>;
|
|
67
|
+
private buildPrecompiledHeader;
|
|
63
68
|
terminate(): Promise<void>;
|
|
64
69
|
clear(): Promise<void>;
|
|
65
70
|
setRuntimeAssetCache(cache: RuntimeAssetCache): void;
|
package/dist/playground/clang.js
CHANGED
|
@@ -48,6 +48,12 @@ class Clang {
|
|
|
48
48
|
debugMode = 'none';
|
|
49
49
|
lldbBreakpoints = new Map();
|
|
50
50
|
debugEvaluationQueue = Promise.resolve();
|
|
51
|
+
// A helper worker builds the precompiled <bits/stdc++.h> so the warm worker stays free for
|
|
52
|
+
// runs. The header is sent once to each warm worker, which keeps it for later compiles.
|
|
53
|
+
precompiledHeader;
|
|
54
|
+
precompiledHeaderSent;
|
|
55
|
+
headerBuilder;
|
|
56
|
+
loadedRuntime;
|
|
51
57
|
workerSession = new WorkerSession({
|
|
52
58
|
label: 'Clang',
|
|
53
59
|
onDispose: (worker) => {
|
|
@@ -102,6 +108,12 @@ class Clang {
|
|
|
102
108
|
const languageSysrootProfiles = bundledAssets &&
|
|
103
109
|
typeof runtimeAssets !== 'string' &&
|
|
104
110
|
runtimeAssets.clang?.bundledLanguageSysroots === true;
|
|
111
|
+
this.loadedRuntime = {
|
|
112
|
+
assetConfig,
|
|
113
|
+
maxAssetBytes: limits.maxAssetBytes,
|
|
114
|
+
languageSysrootProfiles,
|
|
115
|
+
verifiedStreaming: bundledAssets
|
|
116
|
+
};
|
|
105
117
|
const needsWorkerReset = !this.worker ||
|
|
106
118
|
!this.assetBridge ||
|
|
107
119
|
!this.assetBridge.matches(assetConfig, limits.maxAssetBytes, languageSysrootProfiles);
|
|
@@ -285,6 +297,16 @@ class Clang {
|
|
|
285
297
|
});
|
|
286
298
|
return;
|
|
287
299
|
}
|
|
300
|
+
if (event.data.precompiledHeaderKey)
|
|
301
|
+
void this.buildPrecompiledHeader(event.data.precompiledHeaderKey, {
|
|
302
|
+
code,
|
|
303
|
+
language: this.language,
|
|
304
|
+
compileArgs,
|
|
305
|
+
activePath: options.activePath,
|
|
306
|
+
cppVersion: options.cppVersion,
|
|
307
|
+
cVersion: options.cVersion,
|
|
308
|
+
debugMode
|
|
309
|
+
});
|
|
288
310
|
if (results) {
|
|
289
311
|
this.elapse = Date.now() - this.begin;
|
|
290
312
|
this.exit = true;
|
|
@@ -310,7 +332,15 @@ class Clang {
|
|
|
310
332
|
interrupt[0] = 0;
|
|
311
333
|
this.worker.onmessage = handler;
|
|
312
334
|
this.begin = Date.now();
|
|
335
|
+
const precompiledHeader = this.language === 'CPP' &&
|
|
336
|
+
(this.precompiledHeaderSent?.worker !== this.worker ||
|
|
337
|
+
this.precompiledHeaderSent.header !== this.precompiledHeader)
|
|
338
|
+
? this.precompiledHeader
|
|
339
|
+
: undefined;
|
|
340
|
+
if (precompiledHeader)
|
|
341
|
+
this.precompiledHeaderSent = { worker: this.worker, header: precompiledHeader };
|
|
313
342
|
this.worker?.postMessage({
|
|
343
|
+
...(precompiledHeader ? { precompiledHeader } : {}),
|
|
314
344
|
code,
|
|
315
345
|
prepare,
|
|
316
346
|
buffer: this.buffer,
|
|
@@ -412,6 +442,70 @@ class Clang {
|
|
|
412
442
|
kill() {
|
|
413
443
|
return this.terminate();
|
|
414
444
|
}
|
|
445
|
+
async buildPrecompiledHeader(key, request) {
|
|
446
|
+
const runtime = this.loadedRuntime;
|
|
447
|
+
if (!runtime ||
|
|
448
|
+
this.disposed ||
|
|
449
|
+
this.precompiledHeader?.key === key ||
|
|
450
|
+
this.headerBuilder?.key === key)
|
|
451
|
+
return;
|
|
452
|
+
this.headerBuilder?.dispose();
|
|
453
|
+
let cancelled = false;
|
|
454
|
+
const pending = {
|
|
455
|
+
key,
|
|
456
|
+
dispose: () => {
|
|
457
|
+
cancelled = true;
|
|
458
|
+
if (this.headerBuilder === pending)
|
|
459
|
+
this.headerBuilder = undefined;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
this.headerBuilder = pending;
|
|
463
|
+
const { default: ClangWorker } = await import('./worker/clang?worker');
|
|
464
|
+
if (cancelled)
|
|
465
|
+
return;
|
|
466
|
+
const worker = new ClangWorker();
|
|
467
|
+
const bridge = new WorkerAssetBridge(worker, 'clang', runtime.assetConfig, undefined, runtime.maxAssetBytes, runtime.languageSysrootProfiles, this.runtimeAssetCache);
|
|
468
|
+
const builder = {
|
|
469
|
+
key,
|
|
470
|
+
dispose: () => {
|
|
471
|
+
if (this.headerBuilder === builder)
|
|
472
|
+
this.headerBuilder = undefined;
|
|
473
|
+
bridge.dispose();
|
|
474
|
+
worker.onmessage = null;
|
|
475
|
+
worker.onerror = null;
|
|
476
|
+
worker.terminate();
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
this.headerBuilder = builder;
|
|
480
|
+
worker.onerror = builder.dispose;
|
|
481
|
+
worker.onmessage = (event) => {
|
|
482
|
+
if (bridge.handleMessage(event))
|
|
483
|
+
return;
|
|
484
|
+
const { data } = event;
|
|
485
|
+
if (data?.load)
|
|
486
|
+
worker.postMessage({ precompileHeader: true, ...request });
|
|
487
|
+
else if (data?.error)
|
|
488
|
+
builder.dispose();
|
|
489
|
+
else if (data && 'precompiledHeader' in data) {
|
|
490
|
+
if (data.precompiledHeader)
|
|
491
|
+
this.precompiledHeader = data.precompiledHeader;
|
|
492
|
+
builder.dispose();
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
worker.postMessage({
|
|
496
|
+
load: true,
|
|
497
|
+
verifiedStreaming: runtime.verifiedStreaming,
|
|
498
|
+
languageSysrootProfiles: runtime.languageSysrootProfiles,
|
|
499
|
+
log: false,
|
|
500
|
+
assets: {
|
|
501
|
+
baseUrl: runtime.assetConfig.baseUrl,
|
|
502
|
+
maxAssetBytes: runtime.maxAssetBytes,
|
|
503
|
+
useAssetBridge: runtime.assetConfig.useAssetBridge,
|
|
504
|
+
useModuleBridge: true
|
|
505
|
+
},
|
|
506
|
+
maxAssetBytes: runtime.maxAssetBytes
|
|
507
|
+
});
|
|
508
|
+
}
|
|
415
509
|
async terminate() {
|
|
416
510
|
const lldbSession = this.lldbSession;
|
|
417
511
|
this.lldbSession = undefined;
|
|
@@ -460,6 +554,8 @@ class Clang {
|
|
|
460
554
|
this.disposal = Promise.resolve()
|
|
461
555
|
.then(() => this.clear())
|
|
462
556
|
.finally(() => {
|
|
557
|
+
this.headerBuilder?.dispose();
|
|
558
|
+
this.precompiledHeader = undefined;
|
|
463
559
|
if (this.ownsRuntimeAssetCache)
|
|
464
560
|
this.runtimeAssetCache.dispose();
|
|
465
561
|
this.output = null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BrowserClangArtifact, BrowserClangCompileProgress, BrowserClangCompileRequest, BrowserClangWorkspaceFile } from '@wasm-idle/llvm-core/clang';
|
|
1
|
+
import type { BrowserClangArtifact, BrowserClangCompileProgress, BrowserClangCompileRequest, BrowserClangPrecompiledHeader, BrowserClangWorkspaceFile } from '@wasm-idle/llvm-core/clang';
|
|
2
2
|
import type { WorkerRuntimeAssetConfig } from './worker/assets';
|
|
3
3
|
export type ClangCompileWorkerRequest = {
|
|
4
4
|
request: Omit<BrowserClangCompileRequest, 'onProgress'>;
|
|
@@ -7,7 +7,13 @@ export type ClangCompileWorkerRequest = {
|
|
|
7
7
|
languageSysroots?: boolean;
|
|
8
8
|
log?: boolean;
|
|
9
9
|
assets: WorkerRuntimeAssetConfig;
|
|
10
|
+
/** The last precompiled <bits/stdc++.h>; the worker ignores it when its key differs. */
|
|
11
|
+
precompiledHeader?: BrowserClangPrecompiledHeader;
|
|
10
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* `precompiledHeader: 'building'` means the worker stays alive after the result and later posts
|
|
15
|
+
* one `precompiled-header` message, with no header when Clang could not build it.
|
|
16
|
+
*/
|
|
11
17
|
export type ClangCompileWorkerResponse = {
|
|
12
18
|
type: 'progress';
|
|
13
19
|
progress: BrowserClangCompileProgress;
|
|
@@ -16,11 +22,16 @@ export type ClangCompileWorkerResponse = {
|
|
|
16
22
|
artifact: BrowserClangArtifact;
|
|
17
23
|
stdout: string;
|
|
18
24
|
stderr: string;
|
|
25
|
+
precompiledHeader?: 'building';
|
|
19
26
|
} | {
|
|
20
27
|
type: 'error';
|
|
21
28
|
error: string;
|
|
22
29
|
stdout: string;
|
|
23
30
|
stderr: string;
|
|
31
|
+
precompiledHeader?: 'building';
|
|
32
|
+
} | {
|
|
33
|
+
type: 'precompiled-header';
|
|
34
|
+
header?: BrowserClangPrecompiledHeader;
|
|
24
35
|
};
|
|
25
36
|
export type ClangExecuteWorkerRequest = {
|
|
26
37
|
artifact: BrowserClangArtifact;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Lua from './lua';
|
|
2
|
+
/**
|
|
3
|
+
* Fennel runs the pinned official `fennel.lua` compiler on the shared wasm-lua (wasmoon)
|
|
4
|
+
* worker. The worker fetches and verifies the compiler against its receipt before use.
|
|
5
|
+
*/
|
|
6
|
+
declare class Fennel extends Lua {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export default Fennel;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveFennelCompilerUrl } from './assets';
|
|
2
|
+
import Lua from './lua';
|
|
3
|
+
import { WASM_FENNEL_COMPILER_RECEIPT } from './wasmFennelVersion';
|
|
4
|
+
import { RuntimeConfigurationError } from '@wasm-idle/core';
|
|
5
|
+
/**
|
|
6
|
+
* Fennel runs the pinned official `fennel.lua` compiler on the shared wasm-lua (wasmoon)
|
|
7
|
+
* worker. The worker fetches and verifies the compiler against its receipt before use.
|
|
8
|
+
*/
|
|
9
|
+
class Fennel extends Lua {
|
|
10
|
+
constructor() {
|
|
11
|
+
super({
|
|
12
|
+
label: 'Fennel',
|
|
13
|
+
runtimeId: 'FENNEL',
|
|
14
|
+
defaultActivePath: 'main.fnl',
|
|
15
|
+
resolveWorkerLoadExtras(runtimeAssets, currentUrl) {
|
|
16
|
+
const compilerUrl = resolveFennelCompilerUrl(runtimeAssets, currentUrl);
|
|
17
|
+
if (!compilerUrl) {
|
|
18
|
+
throw new RuntimeConfigurationError('Fennel compiler is not configured. Set PUBLIC_WASM_FENNEL_COMPILER_URL or runtimeAssets.fennel.compilerUrl.', { runtimeId: 'FENNEL' });
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
fennelCompiler: {
|
|
22
|
+
url: compilerUrl,
|
|
23
|
+
receipt: { ...WASM_FENNEL_COMPILER_RECEIPT }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export default Fennel;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { WasmFennelCompilerReceipt } from './wasmFennelVersion';
|
|
2
|
+
/**
|
|
3
|
+
* Fennel runs the official, receipt-verified `fennel.lua` compiler on the wasm-lua VM.
|
|
4
|
+
* These helpers build the Lua entry chunks that load that compiler and hand it the user's
|
|
5
|
+
* Fennel source; they never parse or translate Fennel themselves.
|
|
6
|
+
*/
|
|
7
|
+
export interface FennelCompilerAsset {
|
|
8
|
+
url: string;
|
|
9
|
+
receipt: WasmFennelCompilerReceipt;
|
|
10
|
+
}
|
|
11
|
+
export interface FennelDiagnostic {
|
|
12
|
+
fileName: string | null;
|
|
13
|
+
lineNumber: number;
|
|
14
|
+
columnNumber?: number;
|
|
15
|
+
severity: 'error';
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function luaQuotedString(value: string): string;
|
|
19
|
+
/** Lua chunk that only compiles the Fennel source, raising the compiler error on failure. */
|
|
20
|
+
export declare function buildFennelCompileChunk(fennelSource: string, code: string, fileName: string): string;
|
|
21
|
+
/** Lua chunk that evaluates the Fennel program through `fennel.eval`. */
|
|
22
|
+
export declare function buildFennelRunChunk(fennelSource: string, code: string, fileName: string): string;
|
|
23
|
+
/** Drops the host Lua traceback that wasmoon appends to errors raised by the entry chunk. */
|
|
24
|
+
export declare function stripHostLuaTraceback(message: string): string;
|
|
25
|
+
export declare function parseFennelDiagnostic(message: string, fileName: string): FennelDiagnostic;
|
|
26
|
+
/**
|
|
27
|
+
* Verifies the fetched fennel.lua asset against its pinned receipt. Accepts either the
|
|
28
|
+
* gzip delivery bytes or bytes a server already decoded via Content-Encoding.
|
|
29
|
+
*/
|
|
30
|
+
export declare function verifyFennelCompilerBytes(bytes: Uint8Array, receipt: WasmFennelCompilerReceipt): Promise<string>;
|
|
31
|
+
export declare function loadFennelCompilerSource(asset: FennelCompilerAsset): Promise<string>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const GZIP_MAGIC_0 = 0x1f;
|
|
2
|
+
const GZIP_MAGIC_1 = 0x8b;
|
|
3
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
4
|
+
export function luaQuotedString(value) {
|
|
5
|
+
let quoted = '"';
|
|
6
|
+
for (const character of value) {
|
|
7
|
+
const codePoint = character.codePointAt(0);
|
|
8
|
+
if (character === '\\')
|
|
9
|
+
quoted += '\\\\';
|
|
10
|
+
else if (character === '"')
|
|
11
|
+
quoted += '\\"';
|
|
12
|
+
else if (codePoint < 0x20 || codePoint === 0x7f)
|
|
13
|
+
quoted += `\\${String(codePoint).padStart(3, '0')}`;
|
|
14
|
+
else
|
|
15
|
+
quoted += character;
|
|
16
|
+
}
|
|
17
|
+
return `${quoted}"`;
|
|
18
|
+
}
|
|
19
|
+
function fennelLoaderChunk(fennelSource) {
|
|
20
|
+
// fennel.lua is a single chunk ending in `return mod`; wrapping it in a vararg function
|
|
21
|
+
// keeps it byte-for-byte unmodified while letting the entry chunk reuse the module.
|
|
22
|
+
return `local fennel = (function(...)\n${fennelSource}\nend)("fennel")\npackage.loaded.fennel = fennel\n`;
|
|
23
|
+
}
|
|
24
|
+
function fennelOptions(fileName) {
|
|
25
|
+
return `{ filename = ${luaQuotedString(fileName)}, ["error-pinpoint"] = false }`;
|
|
26
|
+
}
|
|
27
|
+
/** Lua chunk that only compiles the Fennel source, raising the compiler error on failure. */
|
|
28
|
+
export function buildFennelCompileChunk(fennelSource, code, fileName) {
|
|
29
|
+
return (fennelLoaderChunk(fennelSource) +
|
|
30
|
+
`local options = ${fennelOptions(fileName)}\n` +
|
|
31
|
+
// Match fennel.eval, which rejects unknown globals against the current environment.
|
|
32
|
+
`options.allowedGlobals = require("fennel.specials")["current-global-names"]()\n` +
|
|
33
|
+
`local ok, err = pcall(fennel.compileString, ${luaQuotedString(code)}, options)\n` +
|
|
34
|
+
`if not ok then error(tostring(err), 0) end\n`);
|
|
35
|
+
}
|
|
36
|
+
/** Lua chunk that evaluates the Fennel program through `fennel.eval`. */
|
|
37
|
+
export function buildFennelRunChunk(fennelSource, code, fileName) {
|
|
38
|
+
return (fennelLoaderChunk(fennelSource) +
|
|
39
|
+
`local ok, err = xpcall(fennel.eval, function(e) return tostring(e) end, ${luaQuotedString(code)}, ${fennelOptions(fileName)})\n` +
|
|
40
|
+
`if not ok then error(err, 0) end\n`);
|
|
41
|
+
}
|
|
42
|
+
/** Drops the host Lua traceback that wasmoon appends to errors raised by the entry chunk. */
|
|
43
|
+
export function stripHostLuaTraceback(message) {
|
|
44
|
+
const index = message.indexOf('\nstack traceback:');
|
|
45
|
+
return (index === -1 ? message : message.slice(0, index)).trimEnd();
|
|
46
|
+
}
|
|
47
|
+
export function parseFennelDiagnostic(message, fileName) {
|
|
48
|
+
const clean = stripHostLuaTraceback(message.replace(/^Error:\s*/u, ''));
|
|
49
|
+
const firstLine = clean.split('\n', 1)[0] ?? '';
|
|
50
|
+
const match = /^([^:\n]+):(\d+):(?:(\d+):)?\s*([\s\S]*)$/u.exec(firstLine);
|
|
51
|
+
return {
|
|
52
|
+
fileName: match?.[1] || fileName,
|
|
53
|
+
lineNumber: Math.max(1, Number(match?.[2] || 1)),
|
|
54
|
+
columnNumber: match?.[3] ? Math.max(1, Number(match[3]) + 1) : undefined,
|
|
55
|
+
severity: 'error',
|
|
56
|
+
message: (match?.[4] || clean || message).trim()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function sha256Hex(bytes) {
|
|
60
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
61
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
62
|
+
}
|
|
63
|
+
async function gunzip(bytes) {
|
|
64
|
+
const stream = new Blob([bytes])
|
|
65
|
+
.stream()
|
|
66
|
+
.pipeThrough(new DecompressionStream('gzip'));
|
|
67
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
68
|
+
}
|
|
69
|
+
function assertReceipt(receipt) {
|
|
70
|
+
if (!SHA256_PATTERN.test(receipt.sha256) ||
|
|
71
|
+
!SHA256_PATTERN.test(receipt.uncompressedSha256) ||
|
|
72
|
+
!Number.isSafeInteger(receipt.bytes) ||
|
|
73
|
+
!Number.isSafeInteger(receipt.uncompressedBytes)) {
|
|
74
|
+
throw new Error('Fennel compiler receipt is malformed');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Verifies the fetched fennel.lua asset against its pinned receipt. Accepts either the
|
|
79
|
+
* gzip delivery bytes or bytes a server already decoded via Content-Encoding.
|
|
80
|
+
*/
|
|
81
|
+
export async function verifyFennelCompilerBytes(bytes, receipt) {
|
|
82
|
+
assertReceipt(receipt);
|
|
83
|
+
let decoded = bytes;
|
|
84
|
+
if (bytes[0] === GZIP_MAGIC_0 && bytes[1] === GZIP_MAGIC_1) {
|
|
85
|
+
if (bytes.byteLength !== receipt.bytes) {
|
|
86
|
+
throw new Error(`Fennel compiler asset is ${bytes.byteLength} bytes; receipt expects ${receipt.bytes}`);
|
|
87
|
+
}
|
|
88
|
+
if ((await sha256Hex(bytes)) !== receipt.sha256) {
|
|
89
|
+
throw new Error('Fennel compiler asset SHA-256 does not match its pinned receipt');
|
|
90
|
+
}
|
|
91
|
+
decoded = await gunzip(bytes);
|
|
92
|
+
}
|
|
93
|
+
if (decoded.byteLength !== receipt.uncompressedBytes) {
|
|
94
|
+
throw new Error(`Fennel compiler source is ${decoded.byteLength} bytes; receipt expects ${receipt.uncompressedBytes}`);
|
|
95
|
+
}
|
|
96
|
+
if ((await sha256Hex(decoded)) !== receipt.uncompressedSha256) {
|
|
97
|
+
throw new Error('Fennel compiler source SHA-256 does not match its pinned receipt');
|
|
98
|
+
}
|
|
99
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(decoded);
|
|
100
|
+
}
|
|
101
|
+
export async function loadFennelCompilerSource(asset) {
|
|
102
|
+
if (!asset?.url)
|
|
103
|
+
throw new Error('Fennel compiler URL is not configured');
|
|
104
|
+
const response = await fetch(asset.url);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error(`Failed to fetch Fennel compiler (${response.status})`);
|
|
107
|
+
}
|
|
108
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
109
|
+
const maxBytes = Math.max(asset.receipt.bytes, asset.receipt.uncompressedBytes);
|
|
110
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
111
|
+
throw new Error(`Fennel compiler asset exceeds its ${maxBytes} byte receipt`);
|
|
112
|
+
}
|
|
113
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
114
|
+
if (bytes.byteLength > maxBytes) {
|
|
115
|
+
throw new Error(`Fennel compiler asset exceeds its ${maxBytes} byte receipt`);
|
|
116
|
+
}
|
|
117
|
+
return await verifyFennelCompilerBytes(bytes, asset.receipt);
|
|
118
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StaticWorkerRuntimeSandbox } from './staticWorkerRuntime';
|
|
2
|
+
/** Upstream Grain compiler (js_of_ocaml build) plus a WASI host for the program it emits. */
|
|
3
|
+
declare class Grain extends StaticWorkerRuntimeSandbox {
|
|
4
|
+
readonly memoryEvidence: {
|
|
5
|
+
current: unknown;
|
|
6
|
+
};
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export default Grain;
|