wasm-idle 1.0.5 → 1.0.7

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 (112) hide show
  1. package/README.md +52 -5
  2. package/dist/index.d.ts +4 -1
  3. package/dist/index.js +2 -0
  4. package/dist/playground/assetBridge.d.ts +11 -2
  5. package/dist/playground/assetBridge.js +190 -46
  6. package/dist/playground/assets.d.ts +9 -1
  7. package/dist/playground/assets.js +29 -5
  8. package/dist/playground/awk.js +1 -0
  9. package/dist/playground/bash.d.ts +1 -0
  10. package/dist/playground/bash.js +7 -1
  11. package/dist/playground/bqn.js +1 -0
  12. package/dist/playground/bqnPreflight.d.ts +2 -1
  13. package/dist/playground/bqnPreflight.js +1 -0
  14. package/dist/playground/c3.js +1 -0
  15. package/dist/playground/cachedClang.d.ts +10 -0
  16. package/dist/playground/cachedClang.js +330 -0
  17. package/dist/playground/clang.d.ts +8 -0
  18. package/dist/playground/clang.js +47 -8
  19. package/dist/playground/clangWorkerProtocol.d.ts +57 -0
  20. package/dist/playground/clangWorkerProtocol.js +11 -0
  21. package/dist/playground/clojurescript.js +1 -0
  22. package/dist/playground/clojurescriptPreflight.d.ts +2 -1
  23. package/dist/playground/clojurescriptPreflight.js +1 -0
  24. package/dist/playground/compiledArtifactCache.d.ts +21 -0
  25. package/dist/playground/compiledArtifactCache.js +67 -0
  26. package/dist/playground/elixir.js +4 -1
  27. package/dist/playground/forth.js +1 -0
  28. package/dist/playground/forthPreflight.d.ts +2 -1
  29. package/dist/playground/forthPreflight.js +1 -0
  30. package/dist/playground/fortran.js +7 -1
  31. package/dist/playground/haskell.js +4 -1
  32. package/dist/playground/haskellAssets.d.ts +2 -1
  33. package/dist/playground/haskellAssets.js +8 -8
  34. package/dist/playground/index.d.ts +1 -1
  35. package/dist/playground/index.js +2 -2
  36. package/dist/playground/j.js +1 -0
  37. package/dist/playground/jPreflight.d.ts +2 -1
  38. package/dist/playground/jPreflight.js +1 -0
  39. package/dist/playground/janet.js +1 -0
  40. package/dist/playground/java.js +20 -8
  41. package/dist/playground/julia.js +1 -0
  42. package/dist/playground/lfortranAssets.d.ts +2 -1
  43. package/dist/playground/lfortranAssets.js +1 -0
  44. package/dist/playground/lisp.js +4 -1
  45. package/dist/playground/lispAssets.d.ts +2 -1
  46. package/dist/playground/lispAssets.js +10 -0
  47. package/dist/playground/lldbManifest.d.ts +2 -2
  48. package/dist/playground/lldbManifest.js +17 -3
  49. package/dist/playground/lldbSession.d.ts +1 -0
  50. package/dist/playground/lldbSession.js +2 -1
  51. package/dist/playground/nim.js +1 -0
  52. package/dist/playground/ocaml.d.ts +1 -0
  53. package/dist/playground/ocaml.js +12 -1
  54. package/dist/playground/options.d.ts +2 -0
  55. package/dist/playground/pascal.js +1 -0
  56. package/dist/playground/perl.js +1 -0
  57. package/dist/playground/prolog.js +1 -0
  58. package/dist/playground/python.d.ts +7 -0
  59. package/dist/playground/python.js +41 -8
  60. package/dist/playground/runtimeAssetCache.d.ts +26 -0
  61. package/dist/playground/runtimeAssetCache.js +84 -0
  62. package/dist/playground/runtimeSession.d.ts +21 -0
  63. package/dist/playground/runtimeSession.js +119 -0
  64. package/dist/playground/rust.d.ts +1 -0
  65. package/dist/playground/rust.js +11 -1
  66. package/dist/playground/rustExecutableGraph.d.ts +1 -0
  67. package/dist/playground/rustExecutableGraph.js +1 -0
  68. package/dist/playground/staticRuntimePreflight.d.ts +2 -1
  69. package/dist/playground/staticRuntimePreflight.js +4 -2
  70. package/dist/playground/staticRuntimePreflightExecute.js +1 -0
  71. package/dist/playground/staticRuntimePreflightProtocol.d.ts +2 -1
  72. package/dist/playground/staticWorkerRuntime.d.ts +3 -1
  73. package/dist/playground/staticWorkerRuntime.js +44 -3
  74. package/dist/playground/tcl.js +1 -0
  75. package/dist/playground/tinygo.d.ts +2 -0
  76. package/dist/playground/tinygo.js +57 -3
  77. package/dist/playground/tinygoExecutableGraph.d.ts +4 -1
  78. package/dist/playground/tinygoExecutableGraph.js +59 -7
  79. package/dist/playground/typescript.js +4 -1
  80. package/dist/playground/wasmGoVersion.d.ts +1 -1
  81. package/dist/playground/wasmGoVersion.js +1 -1
  82. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  83. package/dist/playground/wasmOcamlVersion.js +1 -1
  84. package/dist/playground/wasmRustVersion.d.ts +90 -24
  85. package/dist/playground/wasmRustVersion.js +98 -23
  86. package/dist/playground/wasmTinyGoVersion.d.ts +40 -29
  87. package/dist/playground/wasmTinyGoVersion.js +43 -31
  88. package/dist/playground/worker/assets.d.ts +3 -0
  89. package/dist/playground/worker/assets.js +41 -0
  90. package/dist/playground/worker/cachedClangCompile.d.ts +1 -0
  91. package/dist/playground/worker/cachedClangCompile.js +85 -0
  92. package/dist/playground/worker/cachedClangExecute.d.ts +1 -0
  93. package/dist/playground/worker/cachedClangExecute.js +67 -0
  94. package/dist/playground/worker/clang.js +2 -8
  95. package/dist/playground/worker/clangStreaming.d.ts +1 -1
  96. package/dist/playground/worker/clangStreaming.js +20 -2
  97. package/dist/playground/worker/elixir.js +10 -8
  98. package/dist/playground/worker/fortran.js +4 -7
  99. package/dist/playground/worker/haskell.js +2 -0
  100. package/dist/playground/worker/lisp.js +2 -0
  101. package/dist/playground/worker/ocaml.js +13 -1
  102. package/dist/playground/worker/python.js +3 -2
  103. package/dist/playground/worker/runtimeAssetFetch.d.ts +11 -1
  104. package/dist/playground/worker/runtimeAssetFetch.js +64 -1
  105. package/dist/playground/worker/runtimeModule.d.ts +8 -0
  106. package/dist/playground/worker/runtimeModule.js +49 -0
  107. package/dist/playground/worker/rust.js +9 -2
  108. package/dist/playground/worker/staticRuntimePreflight.js +2 -0
  109. package/dist/playground/worker/typescript.js +4 -1
  110. package/dist/playground/worker/zig.js +7 -4
  111. package/dist/playground/zig.js +4 -1
  112. package/package.json +10 -7
@@ -1,7 +1,7 @@
1
1
  import { resolveBashRuntimeAssetConfig } from './assets';
2
2
  import { BASH_WORKER_PROTOCOL_VERSION, isBashWorkerToHostMessage } from './bashWorkerProtocol';
3
3
  import { reportWorkerProgress } from './workerProgress';
4
- import { AssetTooLargeError, AssetIntegrityError, AssetNotFoundError, BusyError, CancelledError, CompileError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, ProtocolError, ResourceLimitError, RuntimeConfigurationError, RuntimeExecutionError, TimeoutError, UnsupportedBrowserFeatureError, UnsupportedLanguageError, WasmIdleError, WorkerStartupError, preflightBashRuntimeAssets, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
4
+ import { AssetTooLargeError, AssetIntegrityError, AssetNotFoundError, BusyError, CancelledError, CompileError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, ProtocolError, ResourceLimitError, RuntimeConfigurationError, RuntimeExecutionError, TimeoutError, UnsupportedBrowserFeatureError, UnsupportedLanguageError, WasmIdleError, WorkerStartupError, preflightBashRuntimeAssets, resolveExecutionLimits, resolveRuntimeAssetCacheOptions, validateExecutionWorkspace } from '@wasm-idle/core';
5
5
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
6
6
  const textEncoder = new TextEncoder();
7
7
  const abortReason = (signal, phase) => signal.reason !== undefined
@@ -29,6 +29,7 @@ class Bash {
29
29
  activeOperation = null;
30
30
  readyWorker = null;
31
31
  loadedConfig = null;
32
+ persistentCache;
32
33
  requestUid = 0;
33
34
  sessionUid = 0;
34
35
  disposed = false;
@@ -85,6 +86,9 @@ class Bash {
85
86
  const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
86
87
  const resolved = resolveBashRuntimeAssetConfig(runtimeAssets, currentUrl);
87
88
  this.requireActive(operation);
89
+ this.persistentCache =
90
+ typeof runtimeAssets === 'string' ? undefined : runtimeAssets.persistentCache;
91
+ operation.persistentCache = resolveRuntimeAssetCacheOptions(this.persistentCache, options.persistentCache);
88
92
  const config = {
89
93
  ...resolved,
90
94
  identity: createBashConfigIdentity(resolved.preflightKey, limits),
@@ -165,6 +169,7 @@ class Bash {
165
169
  this.requireActive(operation);
166
170
  if (signal?.aborted)
167
171
  throw abortReason(signal, operation.stage);
172
+ operation.persistentCache = resolveRuntimeAssetCacheOptions(this.persistentCache, options.persistentCache);
168
173
  const limits = resolveExecutionLimits(options.limits);
169
174
  this.requireActive(operation);
170
175
  if (!this.readyWorker) {
@@ -481,6 +486,7 @@ class Bash {
481
486
  webcUrl: config.webcUrl,
482
487
  profile: config.preflightProfile,
483
488
  limits: config.limits,
489
+ persistentCache: operation.persistentCache,
484
490
  signal: preflightController.signal,
485
491
  reportProgress: ({ assetKey, loadedBytes }) => {
486
492
  loadedByAsset.set(assetKey, loadedBytes);
@@ -35,6 +35,7 @@ class Bqn extends StaticWorkerRuntimeSandbox {
35
35
  manifestUrl: urls.manifestUrl || '',
36
36
  profile,
37
37
  limits: context.limits,
38
+ persistentCache: context.persistentCache,
38
39
  signal: context.signal,
39
40
  reportProgress(progress) {
40
41
  if (progress.kind === 'asset') {
@@ -1,5 +1,5 @@
1
1
  import type { BqnRuntimePreflightProfile } from './assets';
2
- import { type ExecutionLimits, type RuntimeAssetPreflightProgress } from '@wasm-idle/core';
2
+ import { type ExecutionLimits, type RuntimeAssetCacheOptions, type RuntimeAssetPreflightProgress } from '@wasm-idle/core';
3
3
  export declare const BQN_PREFLIGHT_PROTOCOL: "wasm-idle-bqn-preflight";
4
4
  export declare const BQN_PREFLIGHT_PROTOCOL_VERSION: 1;
5
5
  export declare const BQN_PREFLIGHT_RUNTIME_ID: "BQN";
@@ -8,6 +8,7 @@ export interface BqnRuntimePreflightRequest {
8
8
  readonly manifestUrl: string;
9
9
  readonly profile: BqnRuntimePreflightProfile;
10
10
  readonly limits?: Partial<ExecutionLimits>;
11
+ readonly persistentCache?: RuntimeAssetCacheOptions;
11
12
  readonly signal?: AbortSignal;
12
13
  readonly fetch?: typeof globalThis.fetch;
13
14
  readonly reportProgress?: (progress: RuntimeAssetPreflightProgress) => void;
@@ -237,6 +237,7 @@ export async function preflightBqnRuntimeAssets(request) {
237
237
  wasm: wasmRequestUrl
238
238
  },
239
239
  fetch: request.fetch,
240
+ persistentCache: request.persistentCache,
240
241
  signal: controller.signal,
241
242
  limits,
242
243
  maxConcurrentDownloads: 3,
@@ -121,6 +121,7 @@ class C3 extends StaticWorkerRuntimeSandbox {
121
121
  runtimeId: 'C3',
122
122
  rootUrl: urls.baseUrl,
123
123
  limits: context.limits,
124
+ persistentCache: context.persistentCache,
124
125
  signal: context.signal,
125
126
  redirect: 'error',
126
127
  requireExactResponseUrl: true,
@@ -0,0 +1,10 @@
1
+ import { type Sandbox } from '@wasm-idle/core';
2
+ import type { RuntimeAssetCache } from './runtimeAssetCache';
3
+ import { CompiledArtifactCache } from './compiledArtifactCache';
4
+ type WorkerFactories = {
5
+ compile(): Worker | Promise<Worker>;
6
+ execute(): Worker | Promise<Worker>;
7
+ };
8
+ /** Wrap the public sandbox contract; all other languages and debugger operations stay unchanged. */
9
+ export declare function createCachedClangSandbox(legacy: Sandbox, language: 'C' | 'CPP', runtimeCache: RuntimeAssetCache, cache?: CompiledArtifactCache, factories?: WorkerFactories): Sandbox;
10
+ export {};
@@ -0,0 +1,330 @@
1
+ import { resolveExecutionLimits, resolveRuntimeAssetCacheOptions } from '@wasm-idle/core';
2
+ import { resolveRuntimeAssetConfig } from './assets';
3
+ import { WorkerAssetBridge } from './assetBridge';
4
+ import { shouldStreamBundledClang } from './clangStreamingPolicy';
5
+ import { CompiledArtifactCache, clangCompileKey } from './compiledArtifactCache';
6
+ const workers = {
7
+ async compile() {
8
+ const { default: CompileWorker } = await import('./worker/cachedClangCompile?worker');
9
+ return new CompileWorker();
10
+ },
11
+ async execute() {
12
+ const { default: ExecuteWorker } = await import('./worker/cachedClangExecute?worker');
13
+ return new ExecuteWorker();
14
+ }
15
+ };
16
+ /** Wrap the public sandbox contract; all other languages and debugger operations stay unchanged. */
17
+ export function createCachedClangSandbox(legacy, language, runtimeCache, cache = new CompiledArtifactCache(), factories = workers) {
18
+ let cached = false;
19
+ let loadedAssets = '';
20
+ let runtimeBaseUrl = '';
21
+ let assetConfig;
22
+ let maxAssetBytes = 0;
23
+ let languageSysroots = false;
24
+ let activeWorker;
25
+ let cancelWorker;
26
+ let generation = 0;
27
+ let running = false;
28
+ let disposed = false;
29
+ let disposal;
30
+ let inputBuffer;
31
+ let inputRequested = false;
32
+ let pendingEof = false;
33
+ const pendingInput = [];
34
+ const encoder = new TextEncoder();
35
+ function resetInput() {
36
+ pendingInput.length = 0;
37
+ pendingEof = false;
38
+ inputRequested = false;
39
+ inputBuffer = undefined;
40
+ }
41
+ function flushInput() {
42
+ if (!inputRequested || !inputBuffer)
43
+ return;
44
+ const control = new Int32Array(inputBuffer, 0, 2);
45
+ const payload = new Uint8Array(inputBuffer, 8);
46
+ const next = pendingInput[0];
47
+ if (next) {
48
+ const length = Math.min(next.byteLength, payload.byteLength);
49
+ payload.set(next.subarray(0, length));
50
+ if (length === next.byteLength)
51
+ pendingInput.shift();
52
+ else
53
+ pendingInput[0] = next.subarray(length);
54
+ Atomics.store(control, 1, length);
55
+ Atomics.store(control, 0, 1);
56
+ }
57
+ else if (pendingEof) {
58
+ Atomics.store(control, 1, 0);
59
+ Atomics.store(control, 0, 2);
60
+ }
61
+ else
62
+ return;
63
+ inputRequested = false;
64
+ Atomics.notify(control, 0);
65
+ }
66
+ function stop() {
67
+ generation++;
68
+ running = false;
69
+ cancelWorker?.(new Error('Process terminated'));
70
+ resetInput();
71
+ }
72
+ async function workerOperation(factory, message, token, progress, compileLimit = 0, signal, executionAssetConfig = assetConfig) {
73
+ const worker = await factory();
74
+ if (token !== generation || signal?.aborted) {
75
+ worker.terminate();
76
+ throw signal?.reason ?? new Error('Process terminated');
77
+ }
78
+ return new Promise((resolve, reject) => {
79
+ let bridge;
80
+ try {
81
+ if (compileLimit)
82
+ bridge = new WorkerAssetBridge(worker, 'clang', executionAssetConfig, progress, compileLimit, languageSysroots, runtimeCache);
83
+ }
84
+ catch (error) {
85
+ worker.terminate();
86
+ reject(error);
87
+ return;
88
+ }
89
+ activeWorker = worker;
90
+ const abort = () => finish(signal?.reason instanceof Error
91
+ ? signal.reason
92
+ : new Error('Process terminated'));
93
+ const finish = (error, value) => {
94
+ if (activeWorker !== worker)
95
+ return;
96
+ activeWorker = undefined;
97
+ cancelWorker = undefined;
98
+ signal?.removeEventListener('abort', abort);
99
+ bridge?.dispose();
100
+ worker.onmessage = null;
101
+ worker.onerror = null;
102
+ worker.onmessageerror = null;
103
+ worker.terminate();
104
+ if (error)
105
+ reject(error);
106
+ else
107
+ resolve(value);
108
+ };
109
+ cancelWorker = finish;
110
+ signal?.addEventListener('abort', abort, { once: true });
111
+ worker.onerror = (event) => finish(new Error(event.message || 'C/C++ worker failed'));
112
+ worker.onmessageerror = () => finish(new Error('C/C++ worker message could not be decoded'));
113
+ worker.onmessage = (event) => {
114
+ if (activeWorker !== worker)
115
+ return;
116
+ try {
117
+ if (bridge?.handleMessage(event))
118
+ return;
119
+ const { data } = event;
120
+ if (data.type === 'output')
121
+ legacy.output?.(data.output);
122
+ else if (data.type === 'progress') {
123
+ progress?.set?.(data.progress.percent / 100);
124
+ progress?.report?.({
125
+ kind: 'activity',
126
+ phase: 'compiling',
127
+ label: data.progress.message
128
+ });
129
+ }
130
+ else if (data.type === 'stdin') {
131
+ inputRequested = true;
132
+ progress?.report?.({
133
+ kind: 'ready',
134
+ state: 'waiting-input',
135
+ reason: 'stdin-request',
136
+ label: 'Runtime ready for input'
137
+ });
138
+ flushInput();
139
+ }
140
+ else if (data.type === 'compiled' || data.type === 'error') {
141
+ if (data.stdout)
142
+ legacy.output?.(data.stdout);
143
+ if (data.stderr)
144
+ legacy.output?.(data.stderr);
145
+ if (data.type === 'error')
146
+ finish(new Error(data.error));
147
+ else
148
+ finish(undefined, data.artifact);
149
+ }
150
+ else if (data.type === 'done') {
151
+ if (data.exitCode !== 0 && data.exitCode !== null)
152
+ finish(new Error(`Program exited with code ${data.exitCode}`));
153
+ else
154
+ finish(undefined, true);
155
+ }
156
+ }
157
+ catch (error) {
158
+ finish(error instanceof Error ? error : new Error(String(error)));
159
+ }
160
+ };
161
+ try {
162
+ worker.postMessage(message);
163
+ }
164
+ catch (error) {
165
+ finish(error instanceof Error ? error : new Error(String(error)));
166
+ }
167
+ });
168
+ }
169
+ const overrides = {
170
+ async load(assets = '', code = '', log = true, args = [], options = {}, progress) {
171
+ if (disposed)
172
+ throw new Error('C/C++ sandbox was disposed');
173
+ loadedAssets = assets;
174
+ const config = typeof assets === 'object' ? assets : undefined;
175
+ assetConfig = resolveRuntimeAssetConfig('clang', assets, globalThis.location?.href || 'http://localhost/', options.persistentCache);
176
+ const debugMode = options.debugMode || (options.debug ? 'trace' : 'none');
177
+ const cacheEligible = debugMode === 'none' &&
178
+ shouldStreamBundledClang(assetConfig) &&
179
+ !options.workspaceFiles?.some((file) => typeof file.content !== 'string') &&
180
+ (options.stdin === undefined || typeof options.stdin === 'string') &&
181
+ typeof SharedArrayBuffer !== 'undefined';
182
+ if (!cached)
183
+ await legacy.clear();
184
+ if (disposed)
185
+ throw new Error('C/C++ sandbox was disposed');
186
+ stop();
187
+ cached = cacheEligible;
188
+ if (!cached)
189
+ return legacy.load(assets, code, log, args, options, progress);
190
+ runtimeBaseUrl = assetConfig.baseUrl;
191
+ maxAssetBytes = resolveExecutionLimits(options.limits).maxAssetBytes;
192
+ languageSysroots = config?.clang?.bundledLanguageSysroots === true;
193
+ },
194
+ async run(code, prepare, log = true, progress, args = [], options = {}) {
195
+ if (disposed)
196
+ throw new Error('C/C++ sandbox was disposed');
197
+ if (running)
198
+ throw new Error('C/C++ sandbox is already running');
199
+ if (cached &&
200
+ ((options.debugMode || (options.debug ? 'trace' : 'none')) !== 'none' ||
201
+ (options.stdin !== undefined && typeof options.stdin !== 'string') ||
202
+ options.workspaceFiles?.some((file) => typeof file.content !== 'string'))) {
203
+ await overrides.load(loadedAssets, code, log, args, {
204
+ ...options,
205
+ persistentCache: assetConfig.persistentCache
206
+ }, progress);
207
+ }
208
+ if (!cached)
209
+ return legacy.run(code, prepare, log, progress, args, options);
210
+ const executionAssetConfig = {
211
+ ...assetConfig,
212
+ persistentCache: resolveRuntimeAssetCacheOptions(assetConfig.persistentCache, options.persistentCache)
213
+ };
214
+ options.signal?.throwIfAborted();
215
+ const token = ++generation;
216
+ // Reactive UI arrays/records are Proxies and cannot cross postMessage.
217
+ // Snapshot known fields before awaiting compilation, so the cache key,
218
+ // compiler and executor also observe the same input values.
219
+ const request = {
220
+ code,
221
+ language,
222
+ activePath: options.activePath,
223
+ workspaceFiles: options.workspaceFiles === undefined
224
+ ? undefined
225
+ : Array.from(options.workspaceFiles, (file) => ({
226
+ path: file.path,
227
+ content: file.content
228
+ })),
229
+ compileArgs: Array.from(options.compileArgs ?? args),
230
+ cVersion: options.cVersion,
231
+ cppVersion: options.cppVersion
232
+ };
233
+ const programArgs = Array.from(options.programArgs ?? []);
234
+ const env = options.env === undefined
235
+ ? undefined
236
+ : Object.fromEntries(Object.entries(options.env));
237
+ const stdin = options.stdin;
238
+ const limit = Math.min(maxAssetBytes, resolveExecutionLimits(options.limits).maxAssetBytes);
239
+ const key = clangCompileKey(runtimeBaseUrl, limit, request, languageSysroots);
240
+ let artifact = cache.get(key);
241
+ running = true;
242
+ try {
243
+ if (!artifact) {
244
+ artifact = await workerOperation(() => factories.compile(), {
245
+ request,
246
+ runtimeBaseUrl,
247
+ maxAssetBytes: limit,
248
+ languageSysroots,
249
+ log,
250
+ assets: {
251
+ baseUrl: runtimeBaseUrl,
252
+ maxAssetBytes: limit,
253
+ useAssetBridge: assetConfig.useAssetBridge,
254
+ useModuleBridge: true
255
+ }
256
+ }, token, progress, limit, options.signal, executionAssetConfig);
257
+ if (token !== generation)
258
+ throw new Error('Process terminated');
259
+ cache.set(key, artifact);
260
+ }
261
+ progress?.set?.(1);
262
+ if (prepare)
263
+ return true;
264
+ inputBuffer = new SharedArrayBuffer(65536 + 8);
265
+ return await workerOperation(() => factories.execute(), {
266
+ artifact,
267
+ code,
268
+ activePath: request.activePath,
269
+ workspaceFiles: request.workspaceFiles,
270
+ programArgs,
271
+ env,
272
+ stdin,
273
+ inputBuffer
274
+ }, token, progress, 0, options.signal);
275
+ }
276
+ finally {
277
+ if (token === generation) {
278
+ running = false;
279
+ if (!prepare)
280
+ resetInput();
281
+ }
282
+ }
283
+ },
284
+ write(input) {
285
+ if (!cached)
286
+ return legacy.write?.(input);
287
+ if (input)
288
+ pendingInput.push(encoder.encode(input));
289
+ flushInput();
290
+ },
291
+ eof() {
292
+ if (!cached)
293
+ return legacy.eof();
294
+ pendingEof = true;
295
+ flushInput();
296
+ },
297
+ async clear() {
298
+ stop();
299
+ if (!cached)
300
+ await legacy.clear();
301
+ },
302
+ async terminate() {
303
+ stop();
304
+ if (!cached)
305
+ await legacy.terminate();
306
+ },
307
+ async kill() {
308
+ await overrides.terminate();
309
+ },
310
+ async cancel() {
311
+ await overrides.terminate();
312
+ },
313
+ dispose() {
314
+ if (disposal)
315
+ return disposal;
316
+ disposed = true;
317
+ stop();
318
+ disposal = Promise.resolve(legacy.dispose ? legacy.dispose() : legacy.terminate());
319
+ return disposal;
320
+ }
321
+ };
322
+ return new Proxy(legacy, {
323
+ get(target, property) {
324
+ if (Object.hasOwn(overrides, property))
325
+ return overrides[property];
326
+ const value = Reflect.get(target, property);
327
+ return typeof value === 'function' ? value.bind(target) : value;
328
+ }
329
+ });
330
+ }
@@ -1,5 +1,6 @@
1
1
  import type { DebugCommand, DebugDataBreakpoint, DebugDataBreakpointInfoArguments, DebugSessionEvent, SandboxExecutionOptions } from './options';
2
2
  import { WorkerAssetBridge } from './assetBridge';
3
+ import { RuntimeAssetCache } from './runtimeAssetCache';
3
4
  import { type PlaygroundRuntimeAssets, type RuntimeAssetIntegrityEntry } from './assets';
4
5
  import type { Sandbox } from './sandbox';
5
6
  import { type ProgressSink } from './workerProgress';
@@ -26,7 +27,12 @@ declare class Clang implements Sandbox {
26
27
  debugRuntimeBaseUrl: string;
27
28
  debugManifestUrl: string;
28
29
  debugManifestReceipt?: Readonly<RuntimeAssetIntegrityEntry>;
30
+ private persistentCache?;
29
31
  private lldbSession?;
32
+ private runtimeAssetCache;
33
+ private ownsRuntimeAssetCache;
34
+ private disposed;
35
+ private disposal?;
30
36
  private debugMode;
31
37
  private readonly lldbBreakpoints;
32
38
  private debugEvaluationQueue;
@@ -56,5 +62,7 @@ declare class Clang implements Sandbox {
56
62
  kill(): Promise<void>;
57
63
  terminate(): Promise<void>;
58
64
  clear(): Promise<void>;
65
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
66
+ dispose(): Promise<void>;
59
67
  }
60
68
  export default Clang;
@@ -1,5 +1,6 @@
1
1
  import { shouldStreamBundledClang } from './clangStreamingPolicy';
2
2
  import { WorkerAssetBridge } from './assetBridge';
3
+ import { RuntimeAssetCache } from './runtimeAssetCache';
3
4
  import { resolveDebugRuntimeUrls, resolveRuntimeAssetConfig } from './assets';
4
5
  import { LldbSandboxSession } from './lldbSession';
5
6
  import { normalizeDwarfWorkspacePath } from '@wasm-idle/llvm-core/clang';
@@ -38,7 +39,12 @@ class Clang {
38
39
  debugRuntimeBaseUrl = '';
39
40
  debugManifestUrl = '';
40
41
  debugManifestReceipt;
42
+ persistentCache;
41
43
  lldbSession;
44
+ runtimeAssetCache = new RuntimeAssetCache();
45
+ ownsRuntimeAssetCache = true;
46
+ disposed = false;
47
+ disposal;
42
48
  debugMode = 'none';
43
49
  lldbBreakpoints = new Map();
44
50
  debugEvaluationQueue = Promise.resolve();
@@ -74,6 +80,8 @@ class Clang {
74
80
  load(runtimeAssets = '', code = '', log = true, args = [], options = {}, progress) {
75
81
  let limits;
76
82
  try {
83
+ if (this.disposed)
84
+ throw new Error('Clang runtime has been disposed');
77
85
  limits = resolveExecutionLimits(options.limits);
78
86
  }
79
87
  catch (error) {
@@ -84,9 +92,10 @@ class Clang {
84
92
  this.pendingInput = [];
85
93
  this.waitingForInput = false;
86
94
  this.pendingEof = false;
87
- const assetConfig = resolveRuntimeAssetConfig('clang', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
95
+ const assetConfig = resolveRuntimeAssetConfig('clang', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '', options.persistentCache);
88
96
  const debugRuntime = resolveDebugRuntimeUrls(runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
89
97
  this.debugRuntimeBaseUrl = debugRuntime.baseUrl;
98
+ this.persistentCache = assetConfig.persistentCache;
90
99
  this.debugManifestUrl = debugRuntime.manifestUrl;
91
100
  this.debugManifestReceipt = debugRuntime.manifestReceipt;
92
101
  const bundledAssets = shouldStreamBundledClang(assetConfig);
@@ -107,7 +116,7 @@ class Clang {
107
116
  if (!this.workerSession.attach(worker, operation))
108
117
  return;
109
118
  this.worker = worker;
110
- this.assetBridge = new WorkerAssetBridge(this.worker, 'clang', assetConfig, progress, limits.maxAssetBytes, languageSysrootProfiles);
119
+ this.assetBridge = new WorkerAssetBridge(this.worker, 'clang', assetConfig, progress, limits.maxAssetBytes, languageSysrootProfiles, this.runtimeAssetCache);
111
120
  this.worker.onmessage = (event) => {
112
121
  if (!this.workerSession.isActive(operation))
113
122
  return;
@@ -129,7 +138,8 @@ class Clang {
129
138
  assets: {
130
139
  baseUrl: assetConfig.baseUrl,
131
140
  maxAssetBytes: limits.maxAssetBytes,
132
- useAssetBridge: assetConfig.useAssetBridge
141
+ useAssetBridge: assetConfig.useAssetBridge,
142
+ useModuleBridge: true
133
143
  },
134
144
  maxAssetBytes: limits.maxAssetBytes
135
145
  });
@@ -172,6 +182,8 @@ class Clang {
172
182
  }
173
183
  }
174
184
  run(code, prepare, log = this.log, prog, args = [], options = {}) {
185
+ if (this.disposed)
186
+ return Promise.reject(new Error('Clang runtime has been disposed'));
175
187
  const debugMode = options.debugMode || (options.debug ? 'trace' : 'none');
176
188
  this.debugMode = debugMode;
177
189
  if (debugMode !== 'none')
@@ -181,6 +193,10 @@ class Clang {
181
193
  if (!this.worker)
182
194
  return reject('Worker not loaded');
183
195
  const operation = this.workerSession.beginRun(this.worker, reject);
196
+ const persistentCacheOverride = options.persistentCache;
197
+ if (!this.workerSession.isActive(operation))
198
+ return;
199
+ const persistentCache = this.assetBridge?.setExecutionPersistentCache(persistentCacheOverride);
184
200
  const { compileArgs, programArgs } = resolveSandboxExecutionArgs(this.language, args, options);
185
201
  const sourcePath = normalizeLldbSourcePath(options.debugPath || options.activePath, this.language === 'C' ? 'main.c' : 'main.cc');
186
202
  this.lldbBreakpoints.clear();
@@ -218,6 +234,7 @@ class Clang {
218
234
  if (lldbArtifact) {
219
235
  const compilerWorker = this.worker;
220
236
  const lldbSession = new LldbSandboxSession({
237
+ persistentCache: persistentCache ?? this.persistentCache,
221
238
  manifestUrl: this.debugManifestUrl,
222
239
  manifestReceipt: this.debugManifestReceipt,
223
240
  runtimeBaseUrl: this.debugRuntimeBaseUrl,
@@ -422,11 +439,33 @@ class Clang {
422
439
  resetBufferedStdin(this.watchResultBuffer);
423
440
  const debugBuffer = new Int32Array(this.debugBuffer);
424
441
  debugBuffer.fill(0);
425
- await new Promise((resolve) => setTimeout(resolve, 200));
426
- if (!this.exit) {
427
- this.workerSession.terminate();
428
- this.exit = true;
429
- }
442
+ new Uint8Array(this.interruptBuffer).fill(0);
443
+ }
444
+ setRuntimeAssetCache(cache) {
445
+ if (this.disposed)
446
+ throw new Error('Clang runtime has been disposed');
447
+ if (this.worker)
448
+ throw new Error('Cannot replace an active runtime cache');
449
+ if (cache === this.runtimeAssetCache)
450
+ return;
451
+ if (this.ownsRuntimeAssetCache)
452
+ this.runtimeAssetCache.dispose();
453
+ this.runtimeAssetCache = cache;
454
+ this.ownsRuntimeAssetCache = false;
455
+ }
456
+ dispose() {
457
+ if (this.disposal)
458
+ return this.disposal;
459
+ this.disposed = true;
460
+ this.disposal = Promise.resolve()
461
+ .then(() => this.clear())
462
+ .finally(() => {
463
+ if (this.ownsRuntimeAssetCache)
464
+ this.runtimeAssetCache.dispose();
465
+ this.output = null;
466
+ this.ondebug = undefined;
467
+ });
468
+ return this.disposal;
430
469
  }
431
470
  }
432
471
  export default Clang;
@@ -0,0 +1,57 @@
1
+ import type { BrowserClangArtifact, BrowserClangCompileProgress, BrowserClangCompileRequest, BrowserClangWorkspaceFile } from '@wasm-idle/llvm-core/clang';
2
+ import type { WorkerRuntimeAssetConfig } from './worker/assets';
3
+ export type ClangCompileWorkerRequest = {
4
+ request: Omit<BrowserClangCompileRequest, 'onProgress'>;
5
+ runtimeBaseUrl: string;
6
+ maxAssetBytes?: number;
7
+ languageSysroots?: boolean;
8
+ log?: boolean;
9
+ assets: WorkerRuntimeAssetConfig;
10
+ };
11
+ export type ClangCompileWorkerResponse = {
12
+ type: 'progress';
13
+ progress: BrowserClangCompileProgress;
14
+ } | {
15
+ type: 'compiled';
16
+ artifact: BrowserClangArtifact;
17
+ stdout: string;
18
+ stderr: string;
19
+ } | {
20
+ type: 'error';
21
+ error: string;
22
+ stdout: string;
23
+ stderr: string;
24
+ };
25
+ export type ClangExecuteWorkerRequest = {
26
+ artifact: BrowserClangArtifact;
27
+ programArgs?: string[];
28
+ env?: Record<string, string>;
29
+ workspaceFiles?: BrowserClangWorkspaceFile[];
30
+ activePath?: string;
31
+ code?: string;
32
+ inputBuffer: SharedArrayBuffer;
33
+ stdin?: string;
34
+ };
35
+ export type ClangExecuteWorkerResponse = {
36
+ type: 'stdin';
37
+ } | {
38
+ type: 'output';
39
+ output: string;
40
+ } | {
41
+ type: 'done';
42
+ exitCode: number | null;
43
+ } | {
44
+ type: 'error';
45
+ error: string;
46
+ };
47
+ /**
48
+ * One stdin response occupies two Int32 control words followed by UTF-8 bytes.
49
+ * Before requesting input the worker sets state to WAITING, posts `stdin`, and
50
+ * waits on word 0. The parent writes bytes and length, then atomically publishes
51
+ * DATA or EOF and notifies word 0. It writes only in response to a request.
52
+ * The worker copies DATA before making another request; EOF remains latched.
53
+ */
54
+ export declare const CLANG_STDIN_HEADER_BYTES: number;
55
+ export declare const CLANG_STDIN_WAITING = 0;
56
+ export declare const CLANG_STDIN_DATA = 1;
57
+ export declare const CLANG_STDIN_EOF = 2;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * One stdin response occupies two Int32 control words followed by UTF-8 bytes.
3
+ * Before requesting input the worker sets state to WAITING, posts `stdin`, and
4
+ * waits on word 0. The parent writes bytes and length, then atomically publishes
5
+ * DATA or EOF and notifies word 0. It writes only in response to a request.
6
+ * The worker copies DATA before making another request; EOF remains latched.
7
+ */
8
+ export const CLANG_STDIN_HEADER_BYTES = Int32Array.BYTES_PER_ELEMENT * 2;
9
+ export const CLANG_STDIN_WAITING = 0;
10
+ export const CLANG_STDIN_DATA = 1;
11
+ export const CLANG_STDIN_EOF = 2;
@@ -35,6 +35,7 @@ class ClojureScript extends StaticWorkerRuntimeSandbox {
35
35
  manifestUrl: urls.manifestUrl || '',
36
36
  profile,
37
37
  limits: context.limits,
38
+ persistentCache: context.persistentCache,
38
39
  signal: context.signal,
39
40
  reportProgress(progress) {
40
41
  if (progress.kind === 'asset') {
@@ -1,5 +1,5 @@
1
1
  import type { ClojureScriptRuntimePreflightProfile } from './assets';
2
- import { type ExecutionLimits, type RuntimeAssetPreflightProgress } from '@wasm-idle/core';
2
+ import { type ExecutionLimits, type RuntimeAssetCacheOptions, type RuntimeAssetPreflightProgress } from '@wasm-idle/core';
3
3
  export declare const CLOJURESCRIPT_PREFLIGHT_PROTOCOL: "wasm-idle-clojurescript-preflight";
4
4
  export declare const CLOJURESCRIPT_PREFLIGHT_PROTOCOL_VERSION: 1;
5
5
  export declare const CLOJURESCRIPT_PREFLIGHT_RUNTIME_ID: "CLOJURESCRIPT";
@@ -8,6 +8,7 @@ export interface ClojureScriptRuntimePreflightRequest {
8
8
  readonly manifestUrl: string;
9
9
  readonly profile: ClojureScriptRuntimePreflightProfile;
10
10
  readonly limits?: Partial<ExecutionLimits>;
11
+ readonly persistentCache?: RuntimeAssetCacheOptions;
11
12
  readonly signal?: AbortSignal;
12
13
  readonly fetch?: typeof globalThis.fetch;
13
14
  readonly reportProgress?: (progress: RuntimeAssetPreflightProgress) => void;