wasm-idle 1.1.0 → 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 (145) hide show
  1. package/README.md +140 -37
  2. package/dist/playground/assetBridge.js +106 -31
  3. package/dist/playground/assets.d.ts +2 -2
  4. package/dist/playground/c3.js +22 -0
  5. package/dist/playground/c3Memory.d.ts +19 -0
  6. package/dist/playground/c3Memory.js +117 -0
  7. package/dist/playground/cachedClang.js +143 -34
  8. package/dist/playground/clangAssetIntegrity.js +6 -6
  9. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  10. package/dist/playground/cobol.d.ts +3 -0
  11. package/dist/playground/cobol.js +10 -1
  12. package/dist/playground/commonlispAssets.d.ts +2 -2
  13. package/dist/playground/compiledArtifactCache.d.ts +3 -1
  14. package/dist/playground/compiledArtifactCache.js +31 -5
  15. package/dist/playground/container/ContainerExplorer.svelte +307 -0
  16. package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
  17. package/dist/playground/container/binding.d.ts +26 -0
  18. package/dist/playground/container/binding.js +247 -0
  19. package/dist/playground/container/clang-runtime.d.ts +38 -0
  20. package/dist/playground/container/clang-runtime.js +343 -0
  21. package/dist/playground/container/client.d.ts +15 -0
  22. package/dist/playground/container/client.js +189 -0
  23. package/dist/playground/container/explorer.d.ts +31 -0
  24. package/dist/playground/container/explorer.js +112 -0
  25. package/dist/playground/container/owner.worker.js +390 -0
  26. package/dist/playground/container/protocol.d.ts +73 -0
  27. package/dist/playground/container/protocol.js +28 -0
  28. package/dist/playground/container/release.d.ts +31 -0
  29. package/dist/playground/container/release.js +612 -0
  30. package/dist/playground/container/runtime-adapter.d.ts +73 -0
  31. package/dist/playground/container/runtime-adapter.js +474 -0
  32. package/dist/playground/container/shell-control.d.ts +49 -0
  33. package/dist/playground/container/shell-control.js +303 -0
  34. package/dist/playground/container/workspace.d.ts +8 -0
  35. package/dist/playground/container/workspace.js +29 -0
  36. package/dist/playground/d.d.ts +6 -1
  37. package/dist/playground/d.js +83 -5
  38. package/dist/playground/dotnet.js +6 -8
  39. package/dist/playground/fortran.d.ts +3 -0
  40. package/dist/playground/fortran.js +10 -1
  41. package/dist/playground/go.js +14 -3
  42. package/dist/playground/goDebugInstrumentation.js +184 -54
  43. package/dist/playground/java.js +3 -2
  44. package/dist/playground/javaRuntimeErrors.d.ts +9 -0
  45. package/dist/playground/javaRuntimeErrors.js +60 -0
  46. package/dist/playground/lean4Assets.d.ts +4 -4
  47. package/dist/playground/lfortran.js +10 -0
  48. package/dist/playground/lfortranAssets.d.ts +2 -2
  49. package/dist/playground/nim.js +22 -0
  50. package/dist/playground/objectivec.d.ts +3 -0
  51. package/dist/playground/objectivec.js +11 -2
  52. package/dist/playground/options.d.ts +2 -0
  53. package/dist/playground/python.js +8 -0
  54. package/dist/playground/runtimeAssetCache.d.ts +1 -32
  55. package/dist/playground/runtimeAssetCache.js +1 -193
  56. package/dist/playground/runtimeSession.d.ts +2 -2
  57. package/dist/playground/runtimeSession.js +11 -11
  58. package/dist/playground/rust.d.ts +6 -0
  59. package/dist/playground/rust.js +103 -6
  60. package/dist/playground/staticStdinRing.d.ts +2 -1
  61. package/dist/playground/staticStdinRing.js +21 -10
  62. package/dist/playground/staticWorkerRuntime.d.ts +15 -1
  63. package/dist/playground/staticWorkerRuntime.js +119 -17
  64. package/dist/playground/tinygo.d.ts +3 -0
  65. package/dist/playground/tinygo.js +81 -6
  66. package/dist/playground/v.d.ts +3 -0
  67. package/dist/playground/v.js +10 -1
  68. package/dist/playground/wasmAwkVersion.d.ts +13 -13
  69. package/dist/playground/wasmAwkVersion.js +4 -4
  70. package/dist/playground/wasmBqnVersion.d.ts +2 -2
  71. package/dist/playground/wasmBqnVersion.js +2 -2
  72. package/dist/playground/wasmC3Version.d.ts +2 -2
  73. package/dist/playground/wasmC3Version.js +2 -2
  74. package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
  75. package/dist/playground/wasmClojureScriptVersion.js +2 -2
  76. package/dist/playground/wasmCommonLispVersion.d.ts +2 -2
  77. package/dist/playground/wasmCommonLispVersion.js +2 -2
  78. package/dist/playground/wasmDIntegrity.d.ts +5 -5
  79. package/dist/playground/wasmDIntegrity.js +5 -5
  80. package/dist/playground/wasmDVersion.d.ts +16 -1
  81. package/dist/playground/wasmDVersion.js +16 -1
  82. package/dist/playground/wasmDebugVersion.d.ts +1 -1
  83. package/dist/playground/wasmDebugVersion.js +1 -1
  84. package/dist/playground/wasmForthVersion.d.ts +2 -2
  85. package/dist/playground/wasmForthVersion.js +2 -2
  86. package/dist/playground/wasmGleamVersion.d.ts +2 -2
  87. package/dist/playground/wasmGleamVersion.js +2 -2
  88. package/dist/playground/wasmGrainVersion.d.ts +2 -2
  89. package/dist/playground/wasmGrainVersion.js +2 -2
  90. package/dist/playground/wasmJVersion.d.ts +2 -2
  91. package/dist/playground/wasmJVersion.js +2 -2
  92. package/dist/playground/wasmJanetVersion.js +2 -2
  93. package/dist/playground/wasmJuliaVersion.js +2 -2
  94. package/dist/playground/wasmLean4Version.d.ts +5 -5
  95. package/dist/playground/wasmLean4Version.js +5 -5
  96. package/dist/playground/wasmLfortranVersion.d.ts +2 -2
  97. package/dist/playground/wasmLfortranVersion.js +2 -2
  98. package/dist/playground/wasmNimVersion.d.ts +9 -9
  99. package/dist/playground/wasmNimVersion.js +6 -6
  100. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  101. package/dist/playground/wasmOcamlVersion.js +1 -1
  102. package/dist/playground/wasmPascalVersion.js +2 -2
  103. package/dist/playground/wasmPerlVersion.js +2 -2
  104. package/dist/playground/wasmPrologVersion.d.ts +2 -2
  105. package/dist/playground/wasmPrologVersion.js +2 -2
  106. package/dist/playground/wasmReScriptVersion.d.ts +2 -2
  107. package/dist/playground/wasmReScriptVersion.js +2 -2
  108. package/dist/playground/wasmRustVersion.d.ts +8 -8
  109. package/dist/playground/wasmRustVersion.js +7 -7
  110. package/dist/playground/wasmTclVersion.d.ts +4 -4
  111. package/dist/playground/wasmTclVersion.js +2 -2
  112. package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
  113. package/dist/playground/wasmTinyGoVersion.js +14 -14
  114. package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
  115. package/dist/playground/wasmTypeScriptVersion.js +5 -5
  116. package/dist/playground/worker/cachedClangCompile.js +52 -4
  117. package/dist/playground/worker/cachedClangExecute.js +6 -5
  118. package/dist/playground/worker/clang.js +32 -10
  119. package/dist/playground/worker/cobol.js +2 -2
  120. package/dist/playground/worker/d.js +12 -5
  121. package/dist/playground/worker/fortran.js +2 -2
  122. package/dist/playground/worker/go.js +16 -5
  123. package/dist/playground/worker/haskell.js +10 -7
  124. package/dist/playground/worker/java.js +75 -10
  125. package/dist/playground/worker/lua.js +2 -3
  126. package/dist/playground/worker/php.js +15 -9
  127. package/dist/playground/worker/python.js +46 -9
  128. package/dist/playground/worker/pythonExecution.js +17 -1
  129. package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
  130. package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
  131. package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
  132. package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
  133. package/dist/playground/worker/r.js +13 -6
  134. package/dist/playground/worker/rust.js +34 -2
  135. package/dist/playground/worker/sqlite.js +4 -3
  136. package/dist/playground/worker/tinygo.js +11 -3
  137. package/dist/playground/worker/typescript.js +2 -3
  138. package/dist/playground/worker/v.js +2 -2
  139. package/dist/playground/worker/wat.js +2 -3
  140. package/dist/playground/worker/zig.js +20 -6
  141. package/dist/playground/workerSession.d.ts +1 -28
  142. package/dist/playground/workerSession.js +1 -164
  143. package/package.json +20 -13
  144. package/dist/playground/worker/python.runtime.cases.js +0 -374
  145. /package/dist/playground/{worker/python.runtime.cases.d.ts → container/owner.worker.d.ts} +0 -0
@@ -1,193 +1 @@
1
- const abortReason = (signal) => signal.reason ?? new DOMException('Runtime asset load aborted', 'AbortError');
2
- /** Immutable bootstrap assets only: never store a worker, Wasm instance, or user filesystem. */
3
- export class RuntimeAssetCache {
4
- maxBytes;
5
- maxEntries;
6
- entries = new Map();
7
- inFlight = new Map();
8
- inFlightKeyBytes = 0;
9
- identities = new WeakMap();
10
- nextIdentity = 0;
11
- bytes = 0;
12
- hits = 0;
13
- misses = 0;
14
- disposed = false;
15
- constructor(maxBytes = 256 * 1024 * 1024, maxEntries = 64) {
16
- this.maxBytes = maxBytes;
17
- this.maxEntries = maxEntries;
18
- if (!Number.isSafeInteger(maxBytes) ||
19
- maxBytes < 0 ||
20
- !Number.isSafeInteger(maxEntries) ||
21
- maxEntries < 0) {
22
- throw new TypeError('Runtime cache limits must be non-negative safe integers');
23
- }
24
- }
25
- /** Identity is owner-local; a custom loader must never alias another loader. */
26
- identity(value) {
27
- if (!value)
28
- return 0;
29
- let identity = this.identities.get(value);
30
- if (identity === undefined) {
31
- identity = ++this.nextIdentity;
32
- this.identities.set(value, identity);
33
- }
34
- return identity;
35
- }
36
- get(key) {
37
- if (this.disposed)
38
- return undefined;
39
- const entry = this.entries.get(key);
40
- if (!entry) {
41
- this.misses++;
42
- return undefined;
43
- }
44
- this.hits++;
45
- this.entries.delete(key);
46
- this.entries.set(key, entry);
47
- return entry.value;
48
- }
49
- /** Only the trusted host publishes fully verified results; failures are never retained. */
50
- set(key, value, bytes) {
51
- if (this.disposed)
52
- return;
53
- if (!Number.isSafeInteger(bytes) || bytes < 0)
54
- throw new TypeError('Invalid cache byte count');
55
- const charge = bytes + key.length * 2;
56
- if (this.maxEntries === 0 || charge > this.maxBytes)
57
- return;
58
- const previous = this.entries.get(key);
59
- if (previous)
60
- this.bytes -= previous.bytes;
61
- this.entries.delete(key);
62
- this.entries.set(key, { value, bytes: charge });
63
- this.bytes += charge;
64
- while (this.entries.size > this.maxEntries || this.bytes > this.maxBytes) {
65
- const oldest = this.entries.keys().next().value;
66
- this.bytes -= this.entries.get(oldest).bytes;
67
- this.entries.delete(oldest);
68
- }
69
- }
70
- /** Share work, but give every consumer its own cancellation and progress subscription. */
71
- share(key, signal, start, onProgress) {
72
- if (signal.aborted)
73
- return Promise.reject(abortReason(signal));
74
- let operation = this.inFlight.get(key);
75
- if (!operation) {
76
- // A disabled or saturated owner cache still loads assets without retaining operations.
77
- if (this.disposed ||
78
- this.inFlight.size >= this.maxEntries ||
79
- this.inFlightKeyBytes + key.length * 2 > this.maxBytes) {
80
- return Promise.resolve().then(() => {
81
- if (signal.aborted)
82
- throw abortReason(signal);
83
- return start(signal, (progress) => onProgress?.(progress));
84
- });
85
- }
86
- const pending = {
87
- controller: new AbortController(),
88
- promise: undefined,
89
- consumers: new Set(),
90
- progress: new Map(),
91
- settled: false,
92
- keyBytes: key.length * 2
93
- };
94
- this.inFlight.set(key, pending);
95
- this.inFlightKeyBytes += pending.keyBytes;
96
- pending.promise = Promise.resolve().then(() => {
97
- if (pending.controller.signal.aborted)
98
- throw abortReason(pending.controller.signal);
99
- return start(pending.controller.signal, (progress, progressKey = '') => {
100
- pending.progress.set(progressKey, progress);
101
- for (const consumer of pending.consumers) {
102
- try {
103
- consumer(progress);
104
- }
105
- catch {
106
- // One progress listener cannot interrupt another consumer's load.
107
- }
108
- }
109
- });
110
- });
111
- const cleanup = () => {
112
- pending.settled = true;
113
- pending.progress.clear();
114
- this.removeOperation(key, pending);
115
- };
116
- void pending.promise.then(cleanup, cleanup);
117
- operation = pending;
118
- }
119
- const shared = operation;
120
- return new Promise((resolve, reject) => {
121
- let active = true;
122
- const receiveProgress = (progress) => {
123
- if (active)
124
- onProgress?.(progress);
125
- };
126
- const release = () => {
127
- active = false;
128
- signal.removeEventListener('abort', onAbort);
129
- shared.consumers.delete(receiveProgress);
130
- if (!shared.settled && shared.consumers.size === 0) {
131
- this.removeOperation(key, shared);
132
- shared.controller.abort(abortReason(signal));
133
- }
134
- };
135
- const onAbort = () => {
136
- if (!active)
137
- return;
138
- release();
139
- reject(abortReason(signal));
140
- };
141
- shared.consumers.add(receiveProgress);
142
- signal.addEventListener('abort', onAbort, { once: true });
143
- void shared.promise.then((value) => {
144
- if (!active)
145
- return;
146
- release();
147
- resolve(value);
148
- }, (error) => {
149
- if (!active)
150
- return;
151
- release();
152
- reject(error);
153
- });
154
- for (const progress of shared.progress.values()) {
155
- try {
156
- receiveProgress(progress);
157
- }
158
- catch {
159
- // Progress is advisory, including replay to a late subscriber.
160
- }
161
- }
162
- if (signal.aborted)
163
- onAbort();
164
- });
165
- }
166
- removeOperation(key, operation) {
167
- if (this.inFlight.get(key) !== operation)
168
- return;
169
- this.inFlight.delete(key);
170
- this.inFlightKeyBytes -= operation.keyBytes;
171
- }
172
- stats() {
173
- return {
174
- hits: this.hits,
175
- misses: this.misses,
176
- entries: this.entries.size,
177
- bytes: this.bytes,
178
- inFlight: this.inFlight.size,
179
- disposed: this.disposed
180
- };
181
- }
182
- dispose() {
183
- if (this.disposed)
184
- return;
185
- this.disposed = true;
186
- this.entries.clear();
187
- // Existing consumers may finish, but a disposed owner never retains their results.
188
- this.inFlight.clear();
189
- this.inFlightKeyBytes = 0;
190
- this.identities = new WeakMap();
191
- this.bytes = 0;
192
- }
193
- }
1
+ export { RuntimeAssetCache } from '@wasm-idle/core';
@@ -15,7 +15,7 @@ export interface PlaygroundRuntimeSession {
15
15
  };
16
16
  }
17
17
  /**
18
- * A browser-owned language session. Panels own disposable execution leases, not the
19
- * compiler cache. A problem/editor change may release all leases without cooling it.
18
+ * A browser-owned runtime session. Panels own disposable execution leases, while
19
+ * verified compiler assets survive problem, editor and language changes.
20
20
  */
21
21
  export declare function createRuntimeSession(runtimeAssets: SandboxRuntimeAssets, options?: PlaygroundBindingOptions): PlaygroundRuntimeSession;
@@ -4,16 +4,16 @@ import { RuntimeAssetCache } from './runtimeAssetCache';
4
4
  import { CompiledArtifactCache } from './compiledArtifactCache';
5
5
  import { createCachedClangSandbox } from './cachedClang';
6
6
  /**
7
- * A browser-owned language session. Panels own disposable execution leases, not the
8
- * compiler cache. A problem/editor change may release all leases without cooling it.
7
+ * A browser-owned runtime session. Panels own disposable execution leases, while
8
+ * verified compiler assets survive problem, editor and language changes.
9
9
  */
10
10
  export function createRuntimeSession(runtimeAssets, options = {}) {
11
11
  let language;
12
12
  let epoch = 0;
13
13
  let disposed = false;
14
14
  let disposal;
15
- let assetCache = new RuntimeAssetCache();
16
- let artifactCache = new CompiledArtifactCache();
15
+ const assetCache = new RuntimeAssetCache();
16
+ const artifactCache = new CompiledArtifactCache();
17
17
  const leases = new Set();
18
18
  let retiring = Promise.resolve();
19
19
  const stale = () => new RuntimeConfigurationError('Runtime session was disposed or its language changed', {
@@ -21,14 +21,17 @@ export function createRuntimeSession(runtimeAssets, options = {}) {
21
21
  });
22
22
  const release = (owned) => {
23
23
  const previous = retiring;
24
- retiring = Promise.allSettled([previous, ...owned.map((lease) => lease.dispose())]).then((results) => {
24
+ const released = Promise.allSettled([
25
+ previous,
26
+ ...owned.map((lease) => lease.dispose())
27
+ ]).then((results) => {
25
28
  const failure = results.find((result) => result.status === 'rejected');
26
29
  if (failure?.status === 'rejected')
27
30
  throw failure.reason;
28
31
  });
29
- // Errors remain visible to the caller without unhandled background rejections.
30
- void retiring.catch(() => undefined);
31
- return retiring;
32
+ // Keep ordering as a completion barrier; report this failure only to its owner.
33
+ retiring = released.catch(() => undefined);
34
+ return released;
32
35
  };
33
36
  const session = {
34
37
  async selectLanguage(value) {
@@ -41,10 +44,7 @@ export function createRuntimeSession(runtimeAssets, options = {}) {
41
44
  return await retiring;
42
45
  language = next;
43
46
  epoch++;
44
- assetCache.dispose();
45
47
  artifactCache.clear();
46
- assetCache = new RuntimeAssetCache();
47
- artifactCache = new CompiledArtifactCache();
48
48
  await release([...leases].filter((lease) => lease.epoch !== undefined));
49
49
  },
50
50
  createBinding() {
@@ -1,6 +1,7 @@
1
1
  import { type PlaygroundRuntimeAssets, type RuntimeAssetIntegrityEntry } from './assets';
2
2
  import { type DebugCommand, type DebugDataBreakpoint, type DebugDataBreakpointInfoArguments, type DebugSessionEvent, type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
3
3
  import type { Sandbox, SandboxProgress } from './sandbox';
4
+ import { RuntimeAssetCache } from '@wasm-idle/core';
4
5
  declare class Rust implements Sandbox {
5
6
  output: any;
6
7
  ondebug?: (event: DebugSessionEvent) => void;
@@ -19,6 +20,9 @@ declare class Rust implements Sandbox {
19
20
  debugManifestUrl: string;
20
21
  debugManifestReceipt?: Readonly<RuntimeAssetIntegrityEntry>;
21
22
  executableGraphFingerprint: string;
23
+ private runtimeAssetsKey;
24
+ private runtimeAssetCache;
25
+ private ownsRuntimeAssetCache;
22
26
  private persistentCacheBaseline?;
23
27
  oncompilerdiagnostic?: (diagnostic: CompilerDiagnostic) => void;
24
28
  waitingForInput: boolean;
@@ -58,6 +62,8 @@ declare class Rust implements Sandbox {
58
62
  debugDataBreakpointInfo(arguments_: DebugDataBreakpointInfoArguments): Promise<import("@wasm-idle/core").DebugDataBreakpointInfo | null>;
59
63
  debugSetDataBreakpoints(breakpoints: DebugDataBreakpoint[]): Promise<import("@wasm-idle/core").DebugResolvedDataBreakpoint[]>;
60
64
  kill(): Promise<void>;
65
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
66
+ dispose(): Promise<void>;
61
67
  terminate(): Promise<void>;
62
68
  clear(): Promise<void>;
63
69
  }
@@ -8,7 +8,7 @@ import { resolveRustNonDebugResourceLimits } from './rustWorkerLimits';
8
8
  import { WASM_RUST_EXECUTABLE_GRAPH_PROFILE, WASM_RUST_RUNTIME_PROFILE } from './wasmRustVersion';
9
9
  import { WorkerSession } from './workerSession';
10
10
  import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
11
- import { resolveExecutionLimits, resolveRuntimeAssetCacheOptions, TimeoutError } from '@wasm-idle/core';
11
+ import { createRuntimeAssetsKey, RuntimeAssetCache, resolveExecutionLimits, resolveRuntimeAssetCacheOptions, serveRuntimeWasmModule, TimeoutError } from '@wasm-idle/core';
12
12
  const debugBreakpointBufferInts = 1028;
13
13
  const rustLldbSourcePath = '/workspace/main.rs';
14
14
  const outputEncoder = new TextEncoder();
@@ -54,6 +54,9 @@ class Rust {
54
54
  debugManifestUrl = '';
55
55
  debugManifestReceipt;
56
56
  executableGraphFingerprint = '';
57
+ runtimeAssetsKey = '';
58
+ runtimeAssetCache = new RuntimeAssetCache();
59
+ ownsRuntimeAssetCache = true;
57
60
  persistentCacheBaseline;
58
61
  oncompilerdiagnostic;
59
62
  waitingForInput = false;
@@ -122,9 +125,10 @@ class Rust {
122
125
  let candidateLoadReject = null;
123
126
  let clearCandidateWait = () => { };
124
127
  try {
125
- const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
128
+ const currentUrl = globalThis.location?.href ?? '';
126
129
  const persistentCache = resolveRuntimeAssetCacheOptions(typeof runtimeAssets === 'object' ? runtimeAssets.persistentCache : undefined, _options.persistentCache);
127
130
  const nextCompilerUrl = resolveRustCompilerUrl(runtimeAssets, currentUrl);
131
+ const nextRuntimeAssetsKey = createRuntimeAssetsKey(runtimeAssets) ?? '';
128
132
  const nextDebugModuleUrl = resolveRustDebugModuleUrl(runtimeAssets, currentUrl);
129
133
  const debugRuntime = resolveDebugRuntimeUrls(runtimeAssets, currentUrl);
130
134
  const nextDebugRuntimeBaseUrl = debugRuntime.baseUrl;
@@ -133,9 +137,8 @@ class Rust {
133
137
  const nextAssetPath = typeof runtimeAssets === 'string'
134
138
  ? runtimeAssets
135
139
  : runtimeAssets?.rootUrl ||
136
- (typeof window !== 'undefined'
137
- ? window.location.pathname.replace(/\/$/, '')
138
- : '');
140
+ globalThis.location?.pathname.replace(/\/$/, '') ||
141
+ '';
139
142
  if (!nextCompilerUrl) {
140
143
  throw new Error('Rust runtime is not configured. Set runtimeAssets.rust.compilerUrl.');
141
144
  }
@@ -174,6 +177,7 @@ class Rust {
174
177
  this.compilerUrl !== nextCompilerUrl ||
175
178
  this.debugModuleUrl !== nextDebugModuleUrl ||
176
179
  this.assetPath !== nextAssetPath ||
180
+ this.runtimeAssetsKey !== nextRuntimeAssetsKey ||
177
181
  this.executableGraphFingerprint !==
178
182
  WASM_RUST_EXECUTABLE_GRAPH_PROFILE.fingerprint;
179
183
  if (!needsWorkerReset) {
@@ -279,6 +283,7 @@ class Rust {
279
283
  this.waitingForInput = false;
280
284
  this.pendingEof = false;
281
285
  this.compilerUrl = nextCompilerUrl;
286
+ this.runtimeAssetsKey = nextRuntimeAssetsKey;
282
287
  this.persistentCacheBaseline = persistentCache;
283
288
  this.debugModuleUrl = nextDebugModuleUrl;
284
289
  this.assetPath = nextAssetPath;
@@ -383,6 +388,20 @@ class Rust {
383
388
  let phaseTimeoutState;
384
389
  let outputBytes = 0;
385
390
  let diagnosticCount = 0;
391
+ let moduleBroker;
392
+ const moduleServices = new Set();
393
+ const closeCompilerModuleBroker = () => {
394
+ if (moduleBroker) {
395
+ moduleBroker.port1.onmessage = null;
396
+ moduleBroker.port1.onmessageerror = null;
397
+ moduleBroker.port1.close();
398
+ moduleBroker.port2.close();
399
+ moduleBroker = undefined;
400
+ }
401
+ for (const service of moduleServices)
402
+ service.close();
403
+ moduleServices.clear();
404
+ };
386
405
  const clearScheduledPhaseTimeout = () => {
387
406
  if (phaseTimeout !== undefined)
388
407
  clearTimeout(phaseTimeout);
@@ -459,6 +478,7 @@ class Rust {
459
478
  return;
460
479
  settled = true;
461
480
  this.runActive = false;
481
+ closeCompilerModuleBroker();
462
482
  clearPhaseTimeout();
463
483
  if (this.activeDebugPhaseTimeout === debugPhaseTimeout) {
464
484
  this.activeDebugPhaseTimeout = undefined;
@@ -471,6 +491,7 @@ class Rust {
471
491
  return;
472
492
  settled = true;
473
493
  this.runActive = false;
494
+ closeCompilerModuleBroker();
474
495
  clearPhaseTimeout();
475
496
  if (this.activeDebugPhaseTimeout === debugPhaseTimeout) {
476
497
  this.activeDebugPhaseTimeout = undefined;
@@ -572,6 +593,7 @@ class Rust {
572
593
  if (debugEvent)
573
594
  forwardDebugEvent(debugEvent);
574
595
  if (lldbArtifact) {
596
+ closeCompilerModuleBroker();
575
597
  startPhaseTimeout('run', limits.runTimeoutMs);
576
598
  const compilerWorker = this.worker;
577
599
  const lldbSession = new LldbSandboxSession({
@@ -679,8 +701,63 @@ class Rust {
679
701
  this.worker.onmessage = handler;
680
702
  this.begin = Date.now();
681
703
  try {
704
+ if (typeof MessageChannel === 'function' && typeof MessagePort === 'function') {
705
+ moduleBroker = new MessageChannel();
706
+ const isActiveModuleBroker = () => !settled && _uid === this.uid && this.workerSession.isActive(operation);
707
+ moduleBroker.port1.onmessageerror = () => {
708
+ if (isActiveModuleBroker()) {
709
+ this.workerSession.terminate(new Error('Rust module broker message failed'));
710
+ }
711
+ };
712
+ moduleBroker.port1.onmessage = (event) => {
713
+ const message = event.data;
714
+ if (!isActiveModuleBroker()) {
715
+ if (message?.port instanceof MessagePort) {
716
+ try {
717
+ message.port.close();
718
+ }
719
+ catch {
720
+ // A retired endpoint cannot affect the current worker operation.
721
+ }
722
+ }
723
+ return;
724
+ }
725
+ if (!(message?.port instanceof MessagePort)) {
726
+ this.workerSession.terminate(new Error('Invalid Rust module service request'));
727
+ return;
728
+ }
729
+ if (message.type !== 'rustc-module-service') {
730
+ message.port.close();
731
+ this.workerSession.terminate(new Error('Invalid Rust module service request'));
732
+ return;
733
+ }
734
+ try {
735
+ const receipt = WASM_RUST_RUNTIME_PROFILE.assetReceipts['wasm-rust/runtime/rustc/rustc.wasm.gz'];
736
+ moduleServices.add(serveRuntimeWasmModule(message.port, {
737
+ cache: this.runtimeAssetCache,
738
+ receipt: {
739
+ bytes: receipt.uncompressedBytes,
740
+ sha256: receipt.uncompressedSha256
741
+ },
742
+ identity: JSON.stringify([
743
+ this.runtimeAssetsKey,
744
+ this.compilerUrl,
745
+ WASM_RUST_RUNTIME_PROFILE.profileId,
746
+ this.executableGraphFingerprint
747
+ ]),
748
+ compileKey: 'rustc-native-default-v1',
749
+ maxAssetBytes: limits.maxAssetBytes
750
+ }));
751
+ }
752
+ catch (error) {
753
+ message.port.close();
754
+ this.workerSession.terminate(error);
755
+ }
756
+ };
757
+ }
682
758
  this.worker.postMessage({
683
759
  persistentCache,
760
+ ...(moduleBroker ? { rustcModuleBroker: moduleBroker.port2 } : {}),
684
761
  code,
685
762
  prepare,
686
763
  buffer: this.buffer,
@@ -704,7 +781,7 @@ class Rust {
704
781
  }
705
782
  : {})
706
783
  }
707
- });
784
+ }, moduleBroker ? [moduleBroker.port2] : []);
708
785
  }
709
786
  catch (error) {
710
787
  this.workerSession.terminate(error);
@@ -776,6 +853,26 @@ class Rust {
776
853
  kill() {
777
854
  return this.terminate();
778
855
  }
856
+ setRuntimeAssetCache(cache) {
857
+ if (cache === this.runtimeAssetCache)
858
+ return;
859
+ if (this.worker || this.loadController || this.runActive) {
860
+ throw new Error('Cannot replace the Rust runtime asset cache while its worker is active');
861
+ }
862
+ if (this.ownsRuntimeAssetCache)
863
+ this.runtimeAssetCache.dispose();
864
+ this.runtimeAssetCache = cache;
865
+ this.ownsRuntimeAssetCache = false;
866
+ }
867
+ async dispose() {
868
+ try {
869
+ await this.terminate();
870
+ }
871
+ finally {
872
+ if (this.ownsRuntimeAssetCache)
873
+ this.runtimeAssetCache.dispose();
874
+ }
875
+ }
779
876
  async terminate() {
780
877
  this.loadGeneration += 1;
781
878
  this.loadController?.abort(new Error('Rust runtime terminated'));
@@ -1,5 +1,5 @@
1
1
  export declare const STATIC_STDIN_RING_PROTOCOL: "wasm-idle-static-stdin-ring";
2
- export declare const STATIC_STDIN_RING_PROTOCOL_VERSION: 1;
2
+ export declare const STATIC_STDIN_RING_PROTOCOL_VERSION: 2;
3
3
  export declare const STATIC_STDIN_RING_CONTROL_SLOTS: 4;
4
4
  export declare const STATIC_STDIN_RING_CONTROL_BYTES: number;
5
5
  export declare const STATIC_STDIN_RING_WRITE_INDEX: 0;
@@ -27,6 +27,7 @@ export declare class StaticStdinRingHost {
27
27
  readonly maxBufferedBytes: number;
28
28
  private readonly control;
29
29
  private readonly data;
30
+ private readonly counterRange;
30
31
  private readonly pending;
31
32
  private pendingBytes;
32
33
  private pendingOffset;
@@ -1,5 +1,5 @@
1
1
  export const STATIC_STDIN_RING_PROTOCOL = 'wasm-idle-static-stdin-ring';
2
- export const STATIC_STDIN_RING_PROTOCOL_VERSION = 1;
2
+ export const STATIC_STDIN_RING_PROTOCOL_VERSION = 2;
3
3
  export const STATIC_STDIN_RING_CONTROL_SLOTS = 4;
4
4
  export const STATIC_STDIN_RING_CONTROL_BYTES = STATIC_STDIN_RING_CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT;
5
5
  export const STATIC_STDIN_RING_WRITE_INDEX = 0;
@@ -24,6 +24,7 @@ export class StaticStdinRingHost {
24
24
  maxBufferedBytes;
25
25
  control;
26
26
  data;
27
+ counterRange;
27
28
  pending = [];
28
29
  pendingBytes = 0;
29
30
  pendingOffset = 0;
@@ -43,12 +44,18 @@ export class StaticStdinRingHost {
43
44
  if (maxBufferedBytes < capacity) {
44
45
  throw new TypeError('Static stdin maximum buffered bytes must cover the ring capacity');
45
46
  }
47
+ if (capacity > 2 ** 31) {
48
+ throw new TypeError('Static stdin ring capacity must not exceed 2^31 bytes');
49
+ }
46
50
  if (typeof SharedArrayBuffer !== 'function') {
47
51
  throw new TypeError('SharedArrayBuffer is required for streaming static stdin');
48
52
  }
49
53
  const buffer = new SharedArrayBuffer(STATIC_STDIN_RING_CONTROL_BYTES + capacity);
50
54
  this.control = new Int32Array(buffer, 0, STATIC_STDIN_RING_CONTROL_SLOTS);
51
55
  this.data = new Uint8Array(buffer, STATIC_STDIN_RING_CONTROL_BYTES, capacity);
56
+ // A capacity-aligned modulus preserves physical offsets at rollover for
57
+ // non-power-of-two rings too. At least two full rings fit in this range.
58
+ this.counterRange = Math.floor(2 ** 32 / capacity) * capacity;
52
59
  this.maxBufferedBytes = maxBufferedBytes;
53
60
  this.descriptor = Object.freeze({
54
61
  protocol: STATIC_STDIN_RING_PROTOCOL,
@@ -107,10 +114,12 @@ export class StaticStdinRingHost {
107
114
  this.flush();
108
115
  }
109
116
  ringBytes() {
110
- const write = Atomics.load(this.control, STATIC_STDIN_RING_WRITE_INDEX);
111
- const read = Atomics.load(this.control, STATIC_STDIN_RING_READ_INDEX);
112
- const used = write - read;
113
- if (used < 0 || used > this.data.byteLength) {
117
+ const write = Atomics.load(this.control, STATIC_STDIN_RING_WRITE_INDEX) >>> 0;
118
+ const read = Atomics.load(this.control, STATIC_STDIN_RING_READ_INDEX) >>> 0;
119
+ const used = (write - read + this.counterRange) % this.counterRange;
120
+ if (write >= this.counterRange ||
121
+ read >= this.counterRange ||
122
+ used > this.data.byteLength) {
114
123
  throw new Error('Static stdin ring counters are invalid');
115
124
  }
116
125
  return used;
@@ -118,10 +127,12 @@ export class StaticStdinRingHost {
118
127
  flush() {
119
128
  if (this.cancelled)
120
129
  return;
121
- let write = Atomics.load(this.control, STATIC_STDIN_RING_WRITE_INDEX);
122
- const read = Atomics.load(this.control, STATIC_STDIN_RING_READ_INDEX);
123
- const used = write - read;
124
- if (used < 0 || used > this.data.byteLength) {
130
+ let write = Atomics.load(this.control, STATIC_STDIN_RING_WRITE_INDEX) >>> 0;
131
+ const read = Atomics.load(this.control, STATIC_STDIN_RING_READ_INDEX) >>> 0;
132
+ const used = (write - read + this.counterRange) % this.counterRange;
133
+ if (write >= this.counterRange ||
134
+ read >= this.counterRange ||
135
+ used > this.data.byteLength) {
125
136
  throw new Error('Static stdin ring counters are invalid');
126
137
  }
127
138
  let available = this.data.byteLength - used;
@@ -136,7 +147,7 @@ export class StaticStdinRingHost {
136
147
  if (first < count) {
137
148
  this.data.set(chunk.subarray(this.pendingOffset + first, this.pendingOffset + count), 0);
138
149
  }
139
- write += count;
150
+ write = (write + count) % this.counterRange;
140
151
  available -= count;
141
152
  this.pendingOffset += count;
142
153
  this.pendingBytes -= count;
@@ -1,5 +1,5 @@
1
1
  import type { PlaygroundRuntimeAssets } from './assets';
2
- import { type ExecutionLimits, type RuntimeAssetIntegrityEntry, type RuntimeAssetCacheOptions, type RuntimeStdinMode, type RuntimeWorkerLifetimePolicy } from '@wasm-idle/core';
2
+ import { type ExecutionLimits, type RuntimeAssetIntegrityEntry, type RuntimeAssetCacheOptions, type RuntimeAssetCache, type RuntimeWasmModuleReceipt, type RuntimeStdinMode, type RuntimeWorkerLifetimePolicy } from '@wasm-idle/core';
3
3
  import { type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
4
4
  import type { Sandbox, SandboxProgress } from './sandbox';
5
5
  type StaticWorkerReceipt = Readonly<Required<Pick<RuntimeAssetIntegrityEntry, 'bytes' | 'sha256'>>>;
@@ -55,8 +55,16 @@ export interface StaticWorkerRuntimeConfig {
55
55
  workerLifetime?: RuntimeWorkerLifetimePolicy;
56
56
  resolveRuntimeAssets: (runtimeAssets: string | PlaygroundRuntimeAssets, currentUrl: string) => StaticWorkerRuntimeUrls;
57
57
  preflightRuntimeAssets?: (urls: StaticWorkerRuntimeUrls, context: StaticWorkerRuntimePreflightContext) => unknown | Promise<unknown>;
58
+ /** Only the host selects verified logical receipts and deterministic compilation settings. */
59
+ compilerModules?: (urls: StaticWorkerRuntimeUrls, limits: ExecutionLimits) => Readonly<Record<string, StaticWorkerCompilerModule>>;
58
60
  runtimePreflightDelivery?: 'structured-clone' | 'transfer-owned' | 'transfer-owned-worker-cache';
59
61
  }
62
+ export interface StaticWorkerCompilerModule {
63
+ readonly receipt: RuntimeWasmModuleReceipt;
64
+ /** Include the transform implementation version, options and any enforced memory cap. */
65
+ readonly compileKey: string;
66
+ readonly compile?: (ownedBytes: Uint8Array<ArrayBuffer>) => Promise<WebAssembly.Module>;
67
+ }
60
68
  type StdinWaiter = {
61
69
  reject: (reason: unknown) => void;
62
70
  resolve: () => void;
@@ -101,7 +109,13 @@ export declare class StaticWorkerRuntimeSandbox implements Sandbox {
101
109
  private readonly workerRuntimePreflightMaxAssetBytes;
102
110
  private readonly workersWithCachedRuntimePreflight;
103
111
  private workerStartMaxAssetBytes;
112
+ private runtimeAssetCache?;
113
+ private readonly workerCompilerModuleServices;
104
114
  constructor(config: StaticWorkerRuntimeConfig);
115
+ /** The application owns this cache; worker or sandbox disposal never disposes it. */
116
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
117
+ private createCompilerModulePorts;
118
+ private closeCompilerModuleServices;
105
119
  private usesOwnedRuntimePreflight;
106
120
  private usesWorkerCachedRuntimePreflight;
107
121
  private resetWorkerForLowerAssetLimit;