wasm-idle 1.0.12 → 1.1.2

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 (161) hide show
  1. package/README.md +150 -37
  2. package/dist/playground/applicationAssets.js +2 -0
  3. package/dist/playground/assetBridge.js +106 -31
  4. package/dist/playground/assets.d.ts +2 -2
  5. package/dist/playground/c3.js +22 -0
  6. package/dist/playground/c3Memory.d.ts +19 -0
  7. package/dist/playground/c3Memory.js +117 -0
  8. package/dist/playground/cachedClang.js +143 -34
  9. package/dist/playground/clangAssetIntegrity.js +6 -6
  10. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  11. package/dist/playground/cobol.d.ts +3 -0
  12. package/dist/playground/cobol.js +10 -1
  13. package/dist/playground/commonlisp.d.ts +8 -0
  14. package/dist/playground/commonlisp.js +33 -0
  15. package/dist/playground/commonlispAssets.d.ts +30 -0
  16. package/dist/playground/commonlispAssets.js +184 -0
  17. package/dist/playground/compiledArtifactCache.d.ts +3 -1
  18. package/dist/playground/compiledArtifactCache.js +31 -5
  19. package/dist/playground/container/ContainerExplorer.svelte +307 -0
  20. package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
  21. package/dist/playground/container/binding.d.ts +26 -0
  22. package/dist/playground/container/binding.js +247 -0
  23. package/dist/playground/container/clang-runtime.d.ts +38 -0
  24. package/dist/playground/container/clang-runtime.js +343 -0
  25. package/dist/playground/container/client.d.ts +15 -0
  26. package/dist/playground/container/client.js +189 -0
  27. package/dist/playground/container/explorer.d.ts +31 -0
  28. package/dist/playground/container/explorer.js +112 -0
  29. package/dist/playground/container/owner.worker.d.ts +1 -0
  30. package/dist/playground/container/owner.worker.js +390 -0
  31. package/dist/playground/container/protocol.d.ts +73 -0
  32. package/dist/playground/container/protocol.js +28 -0
  33. package/dist/playground/container/release.d.ts +31 -0
  34. package/dist/playground/container/release.js +612 -0
  35. package/dist/playground/container/runtime-adapter.d.ts +73 -0
  36. package/dist/playground/container/runtime-adapter.js +474 -0
  37. package/dist/playground/container/shell-control.d.ts +49 -0
  38. package/dist/playground/container/shell-control.js +303 -0
  39. package/dist/playground/container/workspace.d.ts +8 -0
  40. package/dist/playground/container/workspace.js +29 -0
  41. package/dist/playground/d.d.ts +6 -1
  42. package/dist/playground/d.js +83 -5
  43. package/dist/playground/dotnet.js +6 -8
  44. package/dist/playground/fortran.d.ts +3 -0
  45. package/dist/playground/fortran.js +10 -1
  46. package/dist/playground/go.js +14 -3
  47. package/dist/playground/goDebugInstrumentation.js +184 -54
  48. package/dist/playground/index.d.ts +1 -1
  49. package/dist/playground/index.js +14 -0
  50. package/dist/playground/java.js +3 -2
  51. package/dist/playground/javaRuntimeErrors.d.ts +9 -0
  52. package/dist/playground/javaRuntimeErrors.js +60 -0
  53. package/dist/playground/javaRuntimeStdin.d.ts +9 -0
  54. package/dist/playground/javaRuntimeStdin.js +145 -0
  55. package/dist/playground/lean4.d.ts +13 -0
  56. package/dist/playground/lean4.js +50 -0
  57. package/dist/playground/lean4Assets.d.ts +25 -0
  58. package/dist/playground/lean4Assets.js +123 -0
  59. package/dist/playground/lfortran.js +10 -0
  60. package/dist/playground/lfortranAssets.d.ts +2 -2
  61. package/dist/playground/loadingProgress.js +4 -1
  62. package/dist/playground/nim.js +22 -0
  63. package/dist/playground/objectivec.d.ts +3 -0
  64. package/dist/playground/objectivec.js +11 -2
  65. package/dist/playground/options.d.ts +2 -0
  66. package/dist/playground/python.js +8 -0
  67. package/dist/playground/runtimeAssetCache.d.ts +1 -32
  68. package/dist/playground/runtimeAssetCache.js +1 -193
  69. package/dist/playground/runtimeSession.d.ts +2 -2
  70. package/dist/playground/runtimeSession.js +11 -11
  71. package/dist/playground/rust.d.ts +6 -0
  72. package/dist/playground/rust.js +103 -6
  73. package/dist/playground/staticStdinRing.d.ts +2 -1
  74. package/dist/playground/staticStdinRing.js +21 -10
  75. package/dist/playground/staticWorkerRuntime.d.ts +15 -1
  76. package/dist/playground/staticWorkerRuntime.js +119 -17
  77. package/dist/playground/tinygo.d.ts +3 -0
  78. package/dist/playground/tinygo.js +81 -6
  79. package/dist/playground/v.d.ts +3 -0
  80. package/dist/playground/v.js +10 -1
  81. package/dist/playground/wasmAwkVersion.d.ts +13 -13
  82. package/dist/playground/wasmAwkVersion.js +4 -4
  83. package/dist/playground/wasmBqnVersion.d.ts +2 -2
  84. package/dist/playground/wasmBqnVersion.js +2 -2
  85. package/dist/playground/wasmC3Version.d.ts +2 -2
  86. package/dist/playground/wasmC3Version.js +2 -2
  87. package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
  88. package/dist/playground/wasmClojureScriptVersion.js +2 -2
  89. package/dist/playground/wasmCommonLispVersion.d.ts +32 -0
  90. package/dist/playground/wasmCommonLispVersion.js +33 -0
  91. package/dist/playground/wasmDIntegrity.d.ts +5 -5
  92. package/dist/playground/wasmDIntegrity.js +5 -5
  93. package/dist/playground/wasmDVersion.d.ts +16 -1
  94. package/dist/playground/wasmDVersion.js +16 -1
  95. package/dist/playground/wasmDebugVersion.d.ts +1 -1
  96. package/dist/playground/wasmDebugVersion.js +1 -1
  97. package/dist/playground/wasmForthVersion.d.ts +2 -2
  98. package/dist/playground/wasmForthVersion.js +2 -2
  99. package/dist/playground/wasmGleamVersion.d.ts +2 -2
  100. package/dist/playground/wasmGleamVersion.js +2 -2
  101. package/dist/playground/wasmGrainVersion.d.ts +2 -2
  102. package/dist/playground/wasmGrainVersion.js +2 -2
  103. package/dist/playground/wasmJVersion.d.ts +2 -2
  104. package/dist/playground/wasmJVersion.js +2 -2
  105. package/dist/playground/wasmJanetVersion.js +2 -2
  106. package/dist/playground/wasmJuliaVersion.js +2 -2
  107. package/dist/playground/wasmLean4Version.d.ts +110 -0
  108. package/dist/playground/wasmLean4Version.js +111 -0
  109. package/dist/playground/wasmLfortranVersion.d.ts +2 -2
  110. package/dist/playground/wasmLfortranVersion.js +2 -2
  111. package/dist/playground/wasmNimVersion.d.ts +9 -9
  112. package/dist/playground/wasmNimVersion.js +6 -6
  113. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  114. package/dist/playground/wasmOcamlVersion.js +1 -1
  115. package/dist/playground/wasmPascalVersion.js +2 -2
  116. package/dist/playground/wasmPerlVersion.js +2 -2
  117. package/dist/playground/wasmPrologVersion.d.ts +2 -2
  118. package/dist/playground/wasmPrologVersion.js +2 -2
  119. package/dist/playground/wasmReScriptVersion.d.ts +2 -2
  120. package/dist/playground/wasmReScriptVersion.js +2 -2
  121. package/dist/playground/wasmRustVersion.d.ts +8 -8
  122. package/dist/playground/wasmRustVersion.js +7 -7
  123. package/dist/playground/wasmTclVersion.d.ts +4 -4
  124. package/dist/playground/wasmTclVersion.js +2 -2
  125. package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
  126. package/dist/playground/wasmTinyGoVersion.js +14 -14
  127. package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
  128. package/dist/playground/wasmTypeScriptVersion.js +5 -5
  129. package/dist/playground/worker/cachedClangCompile.js +52 -4
  130. package/dist/playground/worker/cachedClangExecute.js +6 -5
  131. package/dist/playground/worker/clang.js +32 -10
  132. package/dist/playground/worker/cobol.js +2 -2
  133. package/dist/playground/worker/d.js +12 -5
  134. package/dist/playground/worker/fortran.js +2 -2
  135. package/dist/playground/worker/go.js +16 -5
  136. package/dist/playground/worker/haskell.js +10 -7
  137. package/dist/playground/worker/java.js +92 -39
  138. package/dist/playground/worker/lua.js +2 -3
  139. package/dist/playground/worker/php.js +15 -9
  140. package/dist/playground/worker/python.js +131 -131
  141. package/dist/playground/worker/pythonDebugPreview.d.ts +2 -0
  142. package/dist/playground/worker/pythonDebugPreview.js +55 -0
  143. package/dist/playground/worker/pythonExecution.d.ts +8 -0
  144. package/dist/playground/worker/pythonExecution.js +183 -0
  145. package/dist/playground/worker/pythonStdio.d.ts +32 -0
  146. package/dist/playground/worker/pythonStdio.js +253 -0
  147. package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
  148. package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
  149. package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
  150. package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
  151. package/dist/playground/worker/r.js +13 -6
  152. package/dist/playground/worker/rust.js +34 -2
  153. package/dist/playground/worker/sqlite.js +4 -3
  154. package/dist/playground/worker/tinygo.js +11 -3
  155. package/dist/playground/worker/typescript.js +2 -3
  156. package/dist/playground/worker/v.js +2 -2
  157. package/dist/playground/worker/wat.js +2 -3
  158. package/dist/playground/worker/zig.js +20 -6
  159. package/dist/playground/workerSession.d.ts +1 -28
  160. package/dist/playground/workerSession.js +1 -164
  161. package/package.json +30 -15
@@ -201,20 +201,22 @@ const expectedAssetsForRuntime = (runtime, languageSysroots = false) => {
201
201
  }
202
202
  return assets;
203
203
  };
204
- const integrityKey = (config) => JSON.stringify(Object.entries(config.integrity || {})
205
- .sort(([left], [right]) => left.localeCompare(right))
206
- .map(([asset, entry]) => [
207
- asset,
208
- typeof entry === 'string'
209
- ? entry
210
- : {
211
- sha256: entry.sha256,
212
- bytes: entry.bytes,
213
- mediaType: entry.mediaType,
214
- uncompressedSha256: entry.uncompressedSha256,
215
- uncompressedBytes: entry.uncompressedBytes
216
- }
217
- ]));
204
+ // Read the contract fields directly: valid receipts can use inherited or hidden getters.
205
+ const snapshotIntegrityEntry = (receipt) => typeof receipt === 'string'
206
+ ? receipt
207
+ : Object.freeze({
208
+ sha256: receipt.sha256,
209
+ bytes: receipt.bytes,
210
+ mediaType: receipt.mediaType,
211
+ uncompressedSha256: receipt.uncompressedSha256,
212
+ uncompressedBytes: receipt.uncompressedBytes
213
+ });
214
+ const integrityKey = (config) => {
215
+ const integrity = config.integrity || {};
216
+ return JSON.stringify(Object.getOwnPropertyNames(integrity)
217
+ .sort((left, right) => left.localeCompare(right))
218
+ .map((asset) => [asset, snapshotIntegrityEntry(integrity[asset])]));
219
+ };
218
220
  const allowedBaseUrlsKey = (config) => JSON.stringify([...(config.allowedBaseUrls || [])].sort());
219
221
  class RuntimeLoadProgress {
220
222
  samples = new Map();
@@ -550,7 +552,6 @@ export class WorkerAssetBridge {
550
552
  const generation = this.generation;
551
553
  const controller = new AbortController();
552
554
  const progress = this.progress;
553
- const context = this.captureLoadContext();
554
555
  const receiveProgress = (event) => {
555
556
  if (controller.signal.aborted || generation !== this.generation)
556
557
  return;
@@ -561,15 +562,33 @@ export class WorkerAssetBridge {
561
562
  progress.activity(event.phase, event.asset);
562
563
  }
563
564
  };
564
- const loadContext = (report) => ({
565
- ...context,
566
- progress: {
567
- update: (asset, loaded, total) => report({ kind: 'download', asset, loaded, total }, `download:${asset}`),
568
- activity: (phase, asset) => report({ kind: 'activity', asset, phase }, `activity:${asset}`)
565
+ // A rebind to the same worker aborts this load, but that worker still awaits a reply.
566
+ const replySuperseded = () => {
567
+ if (this.state !== 'active' || this.worker !== worker)
568
+ return;
569
+ try {
570
+ worker.postMessage({
571
+ assetResponse: {
572
+ id: request.id,
573
+ ok: false,
574
+ error: `Runtime asset ${request.asset} request was superseded by a rebind`
575
+ }
576
+ });
569
577
  }
570
- });
578
+ catch {
579
+ // The worker may already be terminated.
580
+ }
581
+ };
571
582
  this.activeLoads.add(controller);
572
583
  try {
584
+ const context = this.captureLoadContext();
585
+ const loadContext = (report) => ({
586
+ ...context,
587
+ progress: {
588
+ update: (asset, loaded, total) => report({ kind: 'download', asset, loaded, total }, `download:${asset}`),
589
+ activity: (phase, asset) => report({ kind: 'activity', asset, phase }, `activity:${asset}`)
590
+ }
591
+ });
573
592
  this.validateAssetRequest(loadContext(receiveProgress), request.asset);
574
593
  const identity = [
575
594
  this.runtime,
@@ -650,7 +669,7 @@ export class WorkerAssetBridge {
650
669
  });
651
670
  }
652
671
  if (controller.signal.aborted || generation !== this.generation)
653
- return;
672
+ return replySuperseded();
654
673
  worker.postMessage({ assetResponse: { id: request.id, ok: true, module } });
655
674
  return;
656
675
  }
@@ -659,7 +678,7 @@ export class WorkerAssetBridge {
659
678
  this.pythonPackageAssets = readPythonPackageAssets(loaded.bytes);
660
679
  }
661
680
  if (controller.signal.aborted || generation !== this.generation)
662
- return;
681
+ return replySuperseded();
663
682
  const buffer = transferBuffer(loaded.bytes, this.cache ? false : loaded.transferOwnership);
664
683
  worker.postMessage({
665
684
  assetResponse: {
@@ -672,7 +691,7 @@ export class WorkerAssetBridge {
672
691
  }
673
692
  catch (error) {
674
693
  if (controller.signal.aborted || generation !== this.generation)
675
- return;
694
+ return replySuperseded();
676
695
  try {
677
696
  worker.postMessage({
678
697
  assetResponse: {
@@ -691,8 +710,24 @@ export class WorkerAssetBridge {
691
710
  }
692
711
  }
693
712
  captureLoadContext() {
713
+ const config = { ...this.config };
714
+ const integrity = config.integrity;
715
+ // Cache identity and verification must observe the same receipt, even if a caller
716
+ // updates its configuration while an asynchronous asset load is in progress.
717
+ // The built-in Clang map and its receipts are frozen; its identity also selects
718
+ // verified streaming, so only caller-owned maps need a new snapshot.
719
+ if (integrity && integrity !== BUNDLED_CLANG_ASSET_INTEGRITY) {
720
+ config.integrity = Object.freeze(Object.fromEntries(
721
+ // Authorization accepts own map entries regardless of enumerability.
722
+ Object.getOwnPropertyNames(integrity).map((asset) => [
723
+ asset,
724
+ snapshotIntegrityEntry(integrity[asset])
725
+ ])));
726
+ }
727
+ if (config.allowedBaseUrls)
728
+ config.allowedBaseUrls = [...config.allowedBaseUrls];
694
729
  return {
695
- config: { ...this.config },
730
+ config,
696
731
  maxAssetBytes: this.maxAssetBytes,
697
732
  expectedAssets: new Set(this.expectedAssets),
698
733
  pythonPackageAssets: new Set(this.pythonPackageAssets),
@@ -743,6 +778,7 @@ export class WorkerAssetBridge {
743
778
  throw runtimeAssetAbortReason(signal);
744
779
  if (this.runtime === 'python' && asset === 'pyodide-lock.json')
745
780
  readPythonPackageAssets(runtimeBytes);
781
+ await loaded.persist?.();
746
782
  return {
747
783
  bytes: runtimeBytes,
748
784
  mimeType: loaded.mimeType,
@@ -768,8 +804,10 @@ export class WorkerAssetBridge {
768
804
  if (!signal.aborted)
769
805
  context.progress.update(asset, loaded, total);
770
806
  };
771
- if (context.config.loader) {
772
- const pendingResult = Promise.resolve(context.config.loader({
807
+ // Do not expose the private verification context as a custom loader's receiver.
808
+ const loader = context.config.loader;
809
+ if (loader) {
810
+ const pendingResult = Promise.resolve(loader({
773
811
  runtime: this.runtime,
774
812
  asset,
775
813
  reportProgress,
@@ -930,7 +968,8 @@ export class WorkerAssetBridge {
930
968
  'bridge-v1',
931
969
  context.config.baseUrl,
932
970
  allowedBaseUrlsKey(context.config),
933
- maxAssetBytes
971
+ maxAssetBytes,
972
+ expected?.mediaType
934
973
  ])
935
974
  }
936
975
  : undefined;
@@ -943,13 +982,44 @@ export class WorkerAssetBridge {
943
982
  if (bytes) {
944
983
  requireRuntimeAssetSize(asset, bytes.byteLength, maxAssetBytes);
945
984
  context.progress.update(asset, bytes.byteLength, bytes.byteLength);
946
- return { bytes, mimeType: receipt?.mediaType, transferOwnership: true };
985
+ // A hit was validated against this same expected MIME type before it was stored.
986
+ return {
987
+ bytes,
988
+ mimeType: expected?.mediaType ?? receipt?.mediaType,
989
+ transferOwnership: true
990
+ };
947
991
  }
948
992
  }
949
993
  const loaded = await this.fetchAssetFromNetwork(context, url, asset, signal, maxAssetBytes);
994
+ if (receipt) {
995
+ // Verify even when storage is disabled, unavailable or skipped. A body that the browser
996
+ // HTTP-decoded and that lost its gzip header is checked against the logical receipt.
997
+ const decoded = !!loaded.contentEncoding && !(loaded.bytes[0] === 0x1f && loaded.bytes[1] === 0x8b);
998
+ if (decoded && receipt.uncompressedSha256 !== undefined) {
999
+ await verifyRuntimeAssetIntegrity({
1000
+ asset,
1001
+ bytes: loaded.bytes,
1002
+ expected: {
1003
+ sha256: receipt.sha256,
1004
+ uncompressedSha256: receipt.uncompressedSha256,
1005
+ uncompressedBytes: receipt.uncompressedBytes
1006
+ },
1007
+ stage: 'uncompressed'
1008
+ });
1009
+ }
1010
+ else if (!decoded || !asset.endsWith('.gz')) {
1011
+ // Unpaired receipts for other assets already describe their logical bytes.
1012
+ await verifyRuntimeAssetIntegrity({
1013
+ asset,
1014
+ bytes: loaded.bytes,
1015
+ expected: { sha256: receipt.sha256, bytes: receipt.bytes }
1016
+ });
1017
+ }
1018
+ // A decoded .gz body with an unpaired receipt is left to runtime validation.
1019
+ }
950
1020
  if (identity && !loaded.contentEncoding) {
951
- // Only checksum-matching bytes are published. Existing runtime validation still runs.
952
- await writePersistentRuntimeAsset({
1021
+ // A hit skips the response MIME check, so publish only bytes that passed it.
1022
+ loaded.persist = () => writePersistentRuntimeAsset({
953
1023
  identity,
954
1024
  cache: persistentCache,
955
1025
  signal,
@@ -1177,6 +1247,11 @@ export class WorkerAssetBridge {
1177
1247
  if (url.hash) {
1178
1248
  throw new Error(`Runtime asset ${asset} URL must not include a fragment`);
1179
1249
  }
1250
+ // The prefix check below sees `/base/..%2fother` as inside `/base/`, but a server that
1251
+ // decodes the separator before resolving dot segments serves `/other`.
1252
+ if (/%2f|%5c/iu.test(url.pathname)) {
1253
+ throw new Error(`Runtime asset ${asset} URL must not include encoded path separators`);
1254
+ }
1180
1255
  const allowed = [context.config.baseUrl, ...(context.config.allowedBaseUrls || [])].some((baseUrl) => {
1181
1256
  let base;
1182
1257
  try {
@@ -663,12 +663,12 @@ export interface ResolvedZigRuntimeAssetConfig {
663
663
  integrity: ZigExecutionAssetReceipts;
664
664
  }
665
665
  export declare const PYTHON_RUNTIME_LOAD_ASSETS: readonly ["pyodide.mjs", "pyodide.asm.mjs", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
666
- export declare const JAVA_RUNTIME_LOAD_ASSETS: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
666
+ export declare const JAVA_RUNTIME_LOAD_ASSETS: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compiler.wasm-deobfuscator.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
667
667
  export declare const CLANG_RUNTIME_LOAD_ASSETS: readonly ["runtime-manifest.v1.json", "bin/memfs.wasm.gz", "bin/clang.wasm.gz", "bin/lld.wasm.gz", "bin/sysroot.tar.gz"];
668
668
  export declare const CLANGD_RUNTIME_LOAD_ASSETS: readonly ["clangd.js", "clangd.wasm.gz", "clangd.headers.json.gz"];
669
669
  export declare const RUNTIME_LOAD_ASSETS: {
670
670
  python: readonly ["pyodide.mjs", "pyodide.asm.mjs", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
671
- java: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
671
+ java: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compiler.wasm-deobfuscator.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
672
672
  clang: readonly ["runtime-manifest.v1.json", "bin/memfs.wasm.gz", "bin/clang.wasm.gz", "bin/lld.wasm.gz", "bin/sysroot.tar.gz"];
673
673
  clangd: readonly ["clangd.js", "clangd.wasm.gz", "clangd.headers.json.gz"];
674
674
  };
@@ -2,6 +2,7 @@ import { resolveC3BaseUrl } from './assets';
2
2
  import { StaticWorkerRuntimeSandbox } from './staticWorkerRuntime';
3
3
  import { bundledC3Profile as profile, bundledC3WorkerReceipt } from './wasmC3Version';
4
4
  import { preflightRuntimeAssets, RuntimeConfigurationError } from '@wasm-idle/core';
5
+ import { limitC3WasmMemory } from './c3Memory.js';
5
6
  export const C3_DEFAULT_WASM_MEMORY_BYTES = 1024 ** 3;
6
7
  function c3ExecutionOptions(options = {}) {
7
8
  if (options.compileArgs?.length)
@@ -114,6 +115,27 @@ class C3 extends StaticWorkerRuntimeSandbox {
114
115
  workerReceipt: bundledC3WorkerReceipt
115
116
  };
116
117
  },
118
+ compilerModules(_urls, limits) {
119
+ const totalCap = Math.min(limits.maxWasmMemoryBytes, 2 * 1024 ** 3);
120
+ const guestCap = Math.min(64 * 1024 ** 2, Math.floor(totalCap / 8 / 65536) * 65536);
121
+ const compilerCap = totalCap - guestCap;
122
+ return {
123
+ compiler: {
124
+ receipt: profile.compilerWasmReceipt,
125
+ compileKey: `c3-defined-memory-cap-v1:${compilerCap}`,
126
+ async compile(bytes) {
127
+ const limited = await limitC3WasmMemory(bytes, compilerCap, {
128
+ verifyModules: false
129
+ });
130
+ const module = await WebAssembly.compile(limited.bytes);
131
+ if (WebAssembly.Module.imports(module).some((item) => item.kind === 'memory')) {
132
+ throw new Error('Imported C3 Wasm memories are unsupported');
133
+ }
134
+ return module;
135
+ }
136
+ }
137
+ };
138
+ },
117
139
  async preflightRuntimeAssets(urls, context) {
118
140
  memoryEvidence.current = undefined;
119
141
  const result = await preflightRuntimeAssets({
@@ -0,0 +1,19 @@
1
+ /** @param {Uint8Array<ArrayBuffer>} bytes */
2
+ export function c3Digest(bytes: Uint8Array<ArrayBuffer>): Promise<string>;
3
+ /**
4
+ * @param {Uint8Array<ArrayBuffer>} bytes
5
+ * @param {number} maximumBytes
6
+ * @param {{verifyModules?: boolean}} [options]
7
+ */
8
+ export function limitC3WasmMemory(bytes: Uint8Array<ArrayBuffer>, maximumBytes: number, { verifyModules }?: {
9
+ verifyModules?: boolean;
10
+ }): Promise<{
11
+ bytes: Uint8Array<ArrayBuffer>;
12
+ evidence: {
13
+ originalSha256: string;
14
+ limitedSha256: string;
15
+ initialBytes: number;
16
+ maximumBytes: number;
17
+ originalMaximumBytes: number | null;
18
+ };
19
+ }>;
@@ -0,0 +1,117 @@
1
+ const C3_WASM_PAGE_BYTES = 65536;
2
+
3
+ /** @param {Uint8Array<ArrayBuffer>} bytes */
4
+ export async function c3Digest(bytes) {
5
+ return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), (value) =>
6
+ value.toString(16).padStart(2, '0')
7
+ ).join('');
8
+ }
9
+
10
+ // Only lower the limit of one defined, unshared wasm32 memory. Instructions and data stay intact.
11
+ /**
12
+ * @param {Uint8Array<ArrayBuffer>} bytes
13
+ * @param {number} maximumBytes
14
+ * @param {{verifyModules?: boolean}} [options]
15
+ */
16
+ export async function limitC3WasmMemory(bytes, maximumBytes, { verifyModules = true } = {}) {
17
+ if (
18
+ !(bytes instanceof Uint8Array) ||
19
+ bytes.length < 8 ||
20
+ !Number.isSafeInteger(maximumBytes) ||
21
+ maximumBytes < 0
22
+ ) {
23
+ throw new Error('Invalid C3 Wasm memory limit request');
24
+ }
25
+ const header = [0, 97, 115, 109, 1, 0, 0, 0];
26
+ if (!header.every((byte, index) => bytes[index] === byte))
27
+ throw new Error('Invalid Wasm header');
28
+ let offset = 8;
29
+ const u32 = (end = bytes.length) => {
30
+ let result = 0;
31
+ for (let index = 0; index < 5; index++) {
32
+ if (offset >= end) throw new Error('Truncated Wasm integer');
33
+ const byte = bytes[offset++];
34
+ if (index === 4 && byte > 15) throw new Error('Invalid Wasm u32');
35
+ result += (byte & 127) * 2 ** (7 * index);
36
+ if (!(byte & 128)) return result;
37
+ }
38
+ throw new Error('Invalid Wasm integer');
39
+ };
40
+ /** @param {number} value */
41
+ const encode = (value) => {
42
+ const result = [];
43
+ do {
44
+ const low = value % 128;
45
+ value = Math.floor(value / 128);
46
+ result.push(low | (value ? 128 : 0));
47
+ } while (value);
48
+ return result;
49
+ };
50
+ let memory;
51
+ while (offset < bytes.length) {
52
+ const start = offset;
53
+ const id = bytes[offset++];
54
+ const size = u32();
55
+ const end = offset + size;
56
+ if (end > bytes.length) throw new Error('Truncated Wasm section');
57
+ if (id === 5) {
58
+ if (memory || u32(end) !== 1)
59
+ throw new Error('C3 supports exactly one defined Wasm memory');
60
+ const flags = u32(end);
61
+ if (flags !== 0 && flags !== 1)
62
+ throw new Error('Shared and memory64 C3 memories are unsupported');
63
+ const minimum = u32(end);
64
+ const originalMaximum = flags === 1 ? u32(end) : null;
65
+ if (
66
+ offset !== end ||
67
+ minimum > 65536 ||
68
+ (originalMaximum !== null && (originalMaximum < minimum || originalMaximum > 65536))
69
+ ) {
70
+ throw new Error('Invalid Wasm memory section');
71
+ }
72
+ const maximum = Math.min(
73
+ Math.floor(maximumBytes / C3_WASM_PAGE_BYTES),
74
+ originalMaximum ?? 65536
75
+ );
76
+ if (minimum > maximum) {
77
+ const error = new Error(
78
+ 'C3 Wasm initial memory exceeds the configured memory limit'
79
+ );
80
+ Object.assign(error, {
81
+ code: 'resource-limit',
82
+ resource: 'wasm-memory',
83
+ actual: minimum * C3_WASM_PAGE_BYTES,
84
+ limit: maximumBytes
85
+ });
86
+ throw error;
87
+ }
88
+ memory = { start, end, minimum, maximum, originalMaximum };
89
+ }
90
+ offset = end;
91
+ }
92
+ if (!memory) throw new Error('C3 requires one defined Wasm memory');
93
+ if (verifyModules) {
94
+ const module = await WebAssembly.compile(bytes);
95
+ if (WebAssembly.Module.imports(module).some((item) => item.kind === 'memory')) {
96
+ throw new Error('Imported C3 Wasm memories are unsupported');
97
+ }
98
+ }
99
+ const payload = [1, 1, ...encode(memory.minimum), ...encode(memory.maximum)];
100
+ const section = Uint8Array.from([5, ...encode(payload.length), ...payload]);
101
+ const limited = new Uint8Array(bytes.length - (memory.end - memory.start) + section.length);
102
+ limited.set(bytes.subarray(0, memory.start));
103
+ limited.set(section, memory.start);
104
+ limited.set(bytes.subarray(memory.end), memory.start + section.length);
105
+ if (verifyModules) await WebAssembly.compile(limited);
106
+ return {
107
+ bytes: limited,
108
+ evidence: {
109
+ originalSha256: await c3Digest(bytes),
110
+ limitedSha256: await c3Digest(limited),
111
+ initialBytes: memory.minimum * C3_WASM_PAGE_BYTES,
112
+ maximumBytes: memory.maximum * C3_WASM_PAGE_BYTES,
113
+ originalMaximumBytes:
114
+ memory.originalMaximum === null ? null : memory.originalMaximum * C3_WASM_PAGE_BYTES
115
+ }
116
+ };
117
+ }