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.
Files changed (56) hide show
  1. package/README.md +48 -11
  2. package/dist/playground/applicationAssets.js +18 -0
  3. package/dist/playground/assets.d.ts +70 -0
  4. package/dist/playground/assets.js +120 -0
  5. package/dist/playground/cachedClang.js +43 -5
  6. package/dist/playground/clang.d.ts +5 -0
  7. package/dist/playground/clang.js +96 -0
  8. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  9. package/dist/playground/fennel.d.ts +9 -0
  10. package/dist/playground/fennel.js +30 -0
  11. package/dist/playground/fennelBootstrap.d.ts +31 -0
  12. package/dist/playground/fennelBootstrap.js +118 -0
  13. package/dist/playground/grain.d.ts +9 -0
  14. package/dist/playground/grain.js +127 -0
  15. package/dist/playground/hy.d.ts +21 -0
  16. package/dist/playground/hy.js +24 -0
  17. package/dist/playground/index.d.ts +1 -1
  18. package/dist/playground/index.js +49 -0
  19. package/dist/playground/lua.d.ts +19 -0
  20. package/dist/playground/lua.js +64 -39
  21. package/dist/playground/objectivec.d.ts +3 -1
  22. package/dist/playground/objectivec.js +6 -2
  23. package/dist/playground/options.js +1 -1
  24. package/dist/playground/postgresql.d.ts +36 -0
  25. package/dist/playground/postgresql.js +543 -0
  26. package/dist/playground/python.d.ts +5 -0
  27. package/dist/playground/python.js +13 -0
  28. package/dist/playground/rescript.d.ts +5 -0
  29. package/dist/playground/rescript.js +58 -0
  30. package/dist/playground/rescriptPreflight.d.ts +26 -0
  31. package/dist/playground/rescriptPreflight.js +381 -0
  32. package/dist/playground/staticRuntimeModuleVersion.d.ts +1 -1
  33. package/dist/playground/staticRuntimeModuleVersion.js +1 -1
  34. package/dist/playground/staticRuntimePreflightExecute.d.ts +2 -1
  35. package/dist/playground/staticRuntimePreflightExecute.js +9 -0
  36. package/dist/playground/staticRuntimePreflightProtocol.d.ts +1 -1
  37. package/dist/playground/v.d.ts +46 -0
  38. package/dist/playground/v.js +703 -0
  39. package/dist/playground/wasmFennelVersion.d.ts +23 -0
  40. package/dist/playground/wasmFennelVersion.js +16 -0
  41. package/dist/playground/wasmGrainVersion.d.ts +24 -0
  42. package/dist/playground/wasmGrainVersion.js +25 -0
  43. package/dist/playground/wasmHyVersion.d.ts +7 -0
  44. package/dist/playground/wasmHyVersion.js +15 -0
  45. package/dist/playground/wasmReScriptVersion.d.ts +20 -0
  46. package/dist/playground/wasmReScriptVersion.js +20 -0
  47. package/dist/playground/worker/cachedClangCompile.js +37 -4
  48. package/dist/playground/worker/clang.js +35 -7
  49. package/dist/playground/worker/lua.js +48 -11
  50. package/dist/playground/worker/postgresql.d.ts +1 -0
  51. package/dist/playground/worker/postgresql.js +205 -0
  52. package/dist/playground/worker/python.js +106 -9
  53. package/dist/playground/worker/staticRuntimePreflight.js +2 -1
  54. package/dist/playground/worker/v.d.ts +1 -0
  55. package/dist/playground/worker/v.js +106 -0
  56. 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
- worker.terminate();
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;
@@ -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;