wasm-idle 0.4.1 → 0.5.0

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 (39) hide show
  1. package/README.md +94 -3
  2. package/dist/clang/app.d.ts +4 -0
  3. package/dist/clang/app.js +315 -12
  4. package/dist/clang/index.d.ts +4 -0
  5. package/dist/clang/index.js +151 -8
  6. package/dist/debug/controller.svelte.d.ts +43 -0
  7. package/dist/debug/controller.svelte.js +268 -0
  8. package/dist/debug/editor/index.d.ts +2 -1
  9. package/dist/debug/editor/index.js +1 -1
  10. package/dist/debug/editor/monaco.d.ts +8 -0
  11. package/dist/debug/editor/monaco.js +23 -0
  12. package/dist/debug/expression.d.ts +24 -0
  13. package/dist/debug/expression.js +307 -138
  14. package/dist/debug/index.d.ts +4 -1
  15. package/dist/debug/index.js +2 -1
  16. package/dist/index.d.ts +5 -3
  17. package/dist/index.js +3 -3
  18. package/dist/playground/assets.d.ts +5 -0
  19. package/dist/playground/assets.js +8 -0
  20. package/dist/playground/clang.d.ts +5 -0
  21. package/dist/playground/clang.js +45 -2
  22. package/dist/playground/index.d.ts +5 -5
  23. package/dist/playground/index.js +45 -5
  24. package/dist/playground/options.d.ts +9 -0
  25. package/dist/playground/python.d.ts +1 -0
  26. package/dist/playground/python.js +12 -0
  27. package/dist/playground/rust.d.ts +32 -0
  28. package/dist/playground/rust.js +175 -0
  29. package/dist/playground/sandbox.d.ts +18 -4
  30. package/dist/playground/wasmRustVersion.d.ts +1 -0
  31. package/dist/playground/wasmRustVersion.js +1 -0
  32. package/dist/playground/worker/clang.js +12 -4
  33. package/dist/playground/worker/rust.d.ts +1 -0
  34. package/dist/playground/worker/rust.js +159 -0
  35. package/dist/terminal/Terminal.svelte +78 -25
  36. package/dist/terminal/Terminal.svelte.d.ts +2 -0
  37. package/dist/terminal/theme.js +2 -2
  38. package/dist/terminal/types.d.ts +3 -0
  39. package/package.json +15 -9
package/README.md CHANGED
@@ -2,12 +2,80 @@
2
2
 
3
3
  ![wasm-idle](static/image.jpeg)
4
4
 
5
- Executes C++, Python, and Java code with working stdio.
5
+ Executes C++, Python, Java, and Rust code with working stdio.
6
6
 
7
7
  Refer to src/lib/clang.
8
8
 
9
9
  Java uses TeaVM's browser compiler/runtime. TeaVM compiler/runtime/classlib assets are bundled under `static/teavm/` by default, and the asset base URL can be overridden with `PUBLIC_TEAVM_BASE_URL`.
10
10
 
11
+ ## Rust browser integration
12
+
13
+ The demo app now bundles a local `wasm-rust` browser compiler under `static/wasm-rust/` and points the example `Terminal` at `/wasm-rust/index.js` by default. Refresh that bundle after rebuilding the sibling `wasm-rust` project with:
14
+
15
+ ```bash
16
+ cd wasm-idle
17
+ pnpm run sync:wasm-rust
18
+ ```
19
+
20
+ The built-in Rust route now supports both `wasm32-wasip1` and `wasm32-wasip2`. The page exposes a
21
+ target selector when Rust is active, defaults to `wasm32-wasip1`, and persists that choice in local
22
+ storage.
23
+
24
+ ## Browser regression commands
25
+
26
+ Browser-level Rust checks are reproducible from this repo:
27
+
28
+ ```bash
29
+ cd wasm-idle
30
+ pnpm run probe:rust-browser
31
+ pnpm run test:browser:playwright
32
+
33
+ WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
34
+ WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
35
+ pnpm run probe:rust-browser
36
+
37
+ WASM_IDLE_RUN_REAL_BROWSER_RUST=1 \
38
+ WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
39
+ WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
40
+ pnpm exec vitest run src/lib/playground/rust.playwright.test.ts
41
+ ```
42
+
43
+ Both commands exercise the real Chromium page path. The default Rust probe now feeds stdin with a
44
+ single line (`5\n`) and expects the page to finish without sending EOF, which keeps the regression
45
+ aligned with the default Rust sample and proves that pressing Enter is enough for line-based stdin.
46
+ Programs that intentionally read stdin until EOF can still be finished with `Ctrl+D` or the toolbar
47
+ `Send EOF` button while the process is running.
48
+ The browser helper writes stdin through the page-owned `window.__wasmIdleDebug.writeTerminalInput(...)`
49
+ hook instead of trying to click xterm's hidden helper textarea, which proved too flaky for repeatable
50
+ Playwright runs.
51
+ If Rust ever reports `invalid metadata files for crate core` or `Unsupported archive identifier`,
52
+ the browser almost always fetched a stale or wrong `wasm-rust` sysroot asset. Hard refresh the page
53
+ and resync `static/wasm-rust/` from the sibling `wasm-rust/dist/`.
54
+ When browser-rustc does retry, it now emits a visible warning instead of only a debug-level
55
+ transition into attempt `2/5`, `3/5`, and so on.
56
+ When the Rust `log` option is enabled, those compile-time `wasm-rust` progress and retry lines are
57
+ also forwarded into the terminal transcript before the final runtime output, so the browser console
58
+ is no longer required to inspect build progress.
59
+ They also resync the vendored `wasm-rust` bundle and rebuild `wasm-idle` first, so the browser run is
60
+ checked against the current assets rather than a stale preview output.
61
+ The probe/test helper also claims a dedicated local preview port by default instead of reusing
62
+ whatever already answers on `localhost`, which keeps the regression target tied to the current build.
63
+ If you point the probe at `dev.seorii.io`, remember that route currently requires an authenticated
64
+ session; the repo-owned regression target is the local preview path above.
65
+
66
+ ## Runtime expectations
67
+
68
+ Rust still supports an external browser compiler module for library consumers. Point `PUBLIC_WASM_RUST_COMPILER_URL` at a built `wasm-rust` ESM entry such as `.../wasm-rust/dist/index.js`, or pass `runtimeAssets.rust.compilerUrl` at runtime.
69
+
70
+ The Rust browser path now executes returned artifacts through the target-appropriate runtime inside
71
+ the Rust worker:
72
+
73
+ - `wasm32-wasip1` runs as preview1 core wasm through `@bjorn3/browser_wasi_shim`
74
+ - `wasm32-wasip2` runs as a preview2 component through `preview2-shim` plus transpiled `jco` output
75
+
76
+ The generic `src/lib/clang/app.ts` host remains in place for other runtimes, but Rust now delegates
77
+ execution to `wasm-rust` so the selected target and returned artifact format stay aligned.
78
+
11
79
  `Terminal` and `playground(...).load(...)` support either the legacy shared `path`/`rootUrl` or per-runtime asset config:
12
80
 
13
81
  ```ts
@@ -20,10 +88,33 @@ const runtimeAssets: PlaygroundRuntimeAssets = {
20
88
  },
21
89
  java: {
22
90
  baseUrl: 'https://cdn.example.com/repl/teavm/'
91
+ },
92
+ rust: {
93
+ compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
23
94
  }
24
95
  };
25
96
  ```
26
97
 
27
- Python custom loaders receive file names under the Pyodide asset root and can serve both core assets and package files. TeaVM custom loaders receive file names under the TeaVM asset root. Compressed TeaVM runtime assets are no longer unpacked inside the library; provide the final file URL or handle decompression in your own loader.
98
+ Python custom loaders receive file names under the Pyodide asset root and can serve both core assets and package files. TeaVM custom loaders receive file names under the TeaVM asset root. Rust expects a browser-loadable compiler module URL; that module is responsible for serving its own nested runtime assets. Compressed TeaVM runtime assets are no longer unpacked inside the library; provide the final file URL or handle decompression in your own loader.
99
+
100
+ If you want a host app to reuse the same runtime asset configuration for both `<Terminal>` and direct `playground(...)` access, bind it once:
101
+
102
+ ```ts
103
+ import Terminal, { createPlaygroundBinding } from 'wasm-idle';
104
+
105
+ const wasmIdle = createPlaygroundBinding({
106
+ rootUrl: 'https://cdn.example.com/repl',
107
+ rust: {
108
+ compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
109
+ }
110
+ });
111
+
112
+ const sandbox = await wasmIdle.load('PYTHON');
113
+ await sandbox.load('print("hi")', false);
114
+ ```
115
+
116
+ ```svelte
117
+ <Terminal {...wasmIdle.terminalProps} bind:terminal />
118
+ ```
28
119
 
29
- Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, and TeaVM.
120
+ Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM, and `wasm-rust`.
@@ -3,7 +3,10 @@ import { Memory, type MemFS } from './memory';
3
3
  interface DebugSession {
4
4
  buffer?: Int32Array;
5
5
  interruptBuffer?: Uint8Array;
6
+ watchBuffer?: Int32Array;
7
+ watchResultBuffer?: Int32Array;
6
8
  breakpoints: Set<number>;
9
+ breakpointVersion: number;
7
10
  pauseOnEntry: boolean;
8
11
  stepArmed: boolean;
9
12
  nextLineArmed: boolean;
@@ -52,6 +55,7 @@ export default class App {
52
55
  constructor(module: WebAssembly.Module, memfs: MemFS, name: string, ...args: string[]);
53
56
  run(): Promise<boolean | undefined>;
54
57
  proc_exit(code: number): void;
58
+ debugEvaluate(expression: string): string;
55
59
  private pauseDebugSession;
56
60
  __wasm_idle_debug_enter(functionId: number, line: number): number;
57
61
  __wasm_idle_debug_leave(functionId: number): number;
package/dist/clang/app.js CHANGED
@@ -2,6 +2,8 @@ import { bindNew } from './apply';
2
2
  import { Memory } from './memory';
3
3
  import { getInstance } from './wasm';
4
4
  import { AbortError, NotImplemented, ProcExit } from './error';
5
+ import { evaluateDebugExpressionWithResolver, parseStoredDebugValue } from '../debug/expression';
6
+ import { flushQueuedStdin, readBufferedStdin } from '../playground/stdinBuffer';
5
7
  const ESUCCESS = 0;
6
8
  const RAF_PROC_EXIT_CODE = 0xc0c0a;
7
9
  export default class App {
@@ -21,11 +23,17 @@ export default class App {
21
23
  this.environ = { USER: 'jungol' };
22
24
  this.memfs = memfs;
23
25
  const env = bindNew(this, '__wasm_idle_debug_enter', '__wasm_idle_debug_leave', '__wasm_idle_debug_line', '__wasm_idle_debug_value_num', '__wasm_idle_debug_value_bool', '__wasm_idle_debug_value_addr', '__wasm_idle_debug_value_text');
24
- const wasi_unstable = {
26
+ const wasi = {
25
27
  ...bindNew(this, 'proc_exit', 'environ_sizes_get', 'environ_get', 'args_sizes_get', 'args_get', 'random_get', 'clock_time_get', 'poll_oneoff'),
26
28
  ...this.memfs.exports
27
29
  };
28
- this.ready = getInstance(module, { wasi_unstable, env }).then((instance) => {
30
+ // Rust/WASI modules import `wasi_snapshot_preview1`, while older toolchains here still use
31
+ // `wasi_unstable`. Expose the same host under both names so either artifact shape runs.
32
+ this.ready = getInstance(module, {
33
+ wasi_unstable: wasi,
34
+ wasi_snapshot_preview1: wasi,
35
+ env
36
+ }).then((instance) => {
29
37
  this.instance = instance;
30
38
  this.exports = this.instance.exports;
31
39
  this.mem = new Memory(this.exports.memory);
@@ -66,8 +74,116 @@ export default class App {
66
74
  this.trace('start() returned without proc_exit');
67
75
  }
68
76
  proc_exit(code) {
77
+ this.trace(`proc_exit_throw(code=${code})`);
69
78
  throw new ProcExit(code);
70
79
  }
80
+ debugEvaluate(expression) {
81
+ const session = this.debugSession;
82
+ if (!session)
83
+ throw new Error('unavailable');
84
+ const frame = [...session.frames]
85
+ .reverse()
86
+ .find((candidate) => candidate.functionId === session.currentFunctionId);
87
+ const activeLine = session.currentLine;
88
+ const activeLocals = [...(session.variableMetadata[session.currentFunctionId] || [])]
89
+ .reverse()
90
+ .filter((variable) => activeLine >= variable.fromLine && activeLine <= variable.toLine);
91
+ const activeGlobals = [...(session.globalVariableMetadata || [])]
92
+ .reverse()
93
+ .filter((variable) => activeLine >= variable.fromLine && activeLine <= variable.toLine);
94
+ return evaluateDebugExpressionWithResolver(expression, (name) => {
95
+ const resolveArrayValue = (variable, addressValue) => {
96
+ const dimensions = variable.dimensions?.length
97
+ ? variable.dimensions
98
+ : variable.length
99
+ ? [variable.length]
100
+ : [];
101
+ const address = Number(addressValue);
102
+ if (!Number.isFinite(address) ||
103
+ address <= 0 ||
104
+ !dimensions.length ||
105
+ (!variable.elementKind && !variable.structFields?.length)) {
106
+ throw new Error('unavailable');
107
+ }
108
+ this.mem?.check?.();
109
+ const scalarSize = variable.structFields?.length && variable.structSize
110
+ ? variable.structSize
111
+ : variable.elementKind === 'double'
112
+ ? 8
113
+ : variable.elementKind === 'bool' || variable.elementKind === 'char'
114
+ ? 1
115
+ : 4;
116
+ const readScalar = (kind, offset) => {
117
+ if (kind === 'bool')
118
+ return !!this.mem.read8(offset);
119
+ if (kind === 'char') {
120
+ const charCode = this.mem.read8(offset);
121
+ return charCode >= 0x20 && charCode <= 0x7e
122
+ ? String.fromCharCode(charCode)
123
+ : charCode;
124
+ }
125
+ if (kind === 'float')
126
+ return this.mem.readFloat32(offset);
127
+ if (kind === 'double')
128
+ return this.mem.readFloat64(offset);
129
+ return this.mem.readInt32(offset);
130
+ };
131
+ const buildStructValue = (baseAddress) => ({
132
+ __debugExpressionKind: 'object',
133
+ has: (fieldName) => !!variable.structFields?.some((field) => field.name === fieldName),
134
+ get: (fieldName) => {
135
+ const field = variable.structFields?.find((candidate) => candidate.name === fieldName);
136
+ if (!field)
137
+ throw new Error('unavailable');
138
+ return readScalar(field.kind, baseAddress + field.offset);
139
+ },
140
+ keys: () => variable.structFields?.map((field) => field.name) || []
141
+ });
142
+ const buildArrayValue = (baseAddress, remainingDimensions) => ({
143
+ __debugExpressionKind: 'array',
144
+ length: remainingDimensions[0],
145
+ truncated: remainingDimensions[0] > 8,
146
+ get: (index) => {
147
+ if (!Number.isInteger(index) ||
148
+ index < 0 ||
149
+ index >= remainingDimensions[0]) {
150
+ throw new Error('unavailable');
151
+ }
152
+ if (remainingDimensions.length > 1) {
153
+ const nestedStride = remainingDimensions
154
+ .slice(1)
155
+ .reduce((total, size) => total * size, 1) * scalarSize;
156
+ return buildArrayValue(baseAddress + index * nestedStride, remainingDimensions.slice(1));
157
+ }
158
+ if (variable.structFields?.length && variable.structSize) {
159
+ return buildStructValue(baseAddress + index * variable.structSize);
160
+ }
161
+ if (!variable.elementKind)
162
+ throw new Error('unavailable');
163
+ return readScalar(variable.elementKind, baseAddress + index * scalarSize);
164
+ },
165
+ keys: () => Array.from({ length: Math.min(remainingDimensions[0], 8) }, (_, index) => index)
166
+ });
167
+ return buildArrayValue(address, dimensions);
168
+ };
169
+ const resolveVariableValue = (variable, storedValue) => {
170
+ if (storedValue == null || storedValue === '?')
171
+ throw new Error('unavailable');
172
+ if (variable.kind === 'array')
173
+ return resolveArrayValue(variable, storedValue);
174
+ return parseStoredDebugValue(storedValue);
175
+ };
176
+ const localVariable = activeLocals.find((variable) => variable.name === name);
177
+ if (localVariable) {
178
+ return resolveVariableValue(localVariable, frame?.values.get(localVariable.slot));
179
+ }
180
+ const globalVariable = activeGlobals.find((variable) => variable.name === name);
181
+ if (globalVariable) {
182
+ return resolveVariableValue(globalVariable, session.globalValues.get(globalVariable.slot));
183
+ }
184
+ throw new Error('unavailable');
185
+ });
186
+ }
71
187
  pauseDebugSession(session, functionId, line, reason) {
72
188
  const buffer = session.buffer;
73
189
  if (!buffer)
@@ -98,9 +214,46 @@ export default class App {
98
214
  if (!Number.isFinite(address) ||
99
215
  address <= 0 ||
100
216
  !dimensions.length ||
101
- !variable.elementKind) {
217
+ (!variable.elementKind && !variable.structFields?.length)) {
102
218
  return [{ name: variable.name, value: '?' }];
103
219
  }
220
+ if (variable.structFields?.length && variable.structSize) {
221
+ const previewLength = Math.min(dimensions[0], 8);
222
+ const values = [];
223
+ for (let index = 0; index < previewLength; index += 1) {
224
+ const fieldValues = [];
225
+ for (const field of variable.structFields) {
226
+ const offset = address + index * variable.structSize + field.offset;
227
+ if (field.kind === 'bool') {
228
+ fieldValues.push(`${field.name}: ${this.mem.read8(offset) ? 'true' : 'false'}`);
229
+ continue;
230
+ }
231
+ if (field.kind === 'char') {
232
+ const charCode = this.mem.read8(offset);
233
+ fieldValues.push(`${field.name}: ${charCode >= 0x20 && charCode <= 0x7e ? `'${String.fromCharCode(charCode)}'` : `${charCode}`}`);
234
+ continue;
235
+ }
236
+ if (field.kind === 'float') {
237
+ fieldValues.push(`${field.name}: ${this.mem.readFloat32(offset)}`);
238
+ continue;
239
+ }
240
+ if (field.kind === 'double') {
241
+ fieldValues.push(`${field.name}: ${this.mem.readFloat64(offset)}`);
242
+ continue;
243
+ }
244
+ fieldValues.push(`${field.name}: ${this.mem.readInt32(offset)}`);
245
+ }
246
+ values.push(`{${fieldValues.join(', ')}}`);
247
+ }
248
+ return [
249
+ {
250
+ name: variable.name,
251
+ value: `[${values.join(', ')}${dimensions[0] > previewLength ? ', ...' : ''}]`
252
+ }
253
+ ];
254
+ }
255
+ if (!variable.elementKind)
256
+ return [{ name: variable.name, value: '?' }];
104
257
  const elementStride = variable.elementKind === 'double'
105
258
  ? 8
106
259
  : variable.elementKind === 'bool' || variable.elementKind === 'char'
@@ -185,6 +338,132 @@ export default class App {
185
338
  return [];
186
339
  if (line < variable.fromLine || line > variable.toLine)
187
340
  return [];
341
+ if (variable.kind === 'array') {
342
+ this.mem?.check?.();
343
+ const address = Number(session.globalValues?.get(variable.slot) ?? Number.NaN);
344
+ const dimensions = variable.dimensions?.length
345
+ ? variable.dimensions
346
+ : variable.length
347
+ ? [variable.length]
348
+ : [];
349
+ if (!Number.isFinite(address) ||
350
+ address <= 0 ||
351
+ !dimensions.length ||
352
+ (!variable.elementKind && !variable.structFields?.length)) {
353
+ return [{ name: variable.name, value: '?' }];
354
+ }
355
+ if (variable.structFields?.length && variable.structSize) {
356
+ const previewLength = Math.min(dimensions[0], 8);
357
+ const values = [];
358
+ for (let index = 0; index < previewLength; index += 1) {
359
+ const fieldValues = [];
360
+ for (const field of variable.structFields) {
361
+ const offset = address + index * variable.structSize + field.offset;
362
+ if (field.kind === 'bool') {
363
+ fieldValues.push(`${field.name}: ${this.mem.read8(offset) ? 'true' : 'false'}`);
364
+ continue;
365
+ }
366
+ if (field.kind === 'char') {
367
+ const charCode = this.mem.read8(offset);
368
+ fieldValues.push(`${field.name}: ${charCode >= 0x20 && charCode <= 0x7e ? `'${String.fromCharCode(charCode)}'` : `${charCode}`}`);
369
+ continue;
370
+ }
371
+ if (field.kind === 'float') {
372
+ fieldValues.push(`${field.name}: ${this.mem.readFloat32(offset)}`);
373
+ continue;
374
+ }
375
+ if (field.kind === 'double') {
376
+ fieldValues.push(`${field.name}: ${this.mem.readFloat64(offset)}`);
377
+ continue;
378
+ }
379
+ fieldValues.push(`${field.name}: ${this.mem.readInt32(offset)}`);
380
+ }
381
+ values.push(`{${fieldValues.join(', ')}}`);
382
+ }
383
+ return [
384
+ {
385
+ name: variable.name,
386
+ value: `[${values.join(', ')}${dimensions[0] > previewLength ? ', ...' : ''}]`
387
+ }
388
+ ];
389
+ }
390
+ if (!variable.elementKind)
391
+ return [{ name: variable.name, value: '?' }];
392
+ const elementStride = variable.elementKind === 'double'
393
+ ? 8
394
+ : variable.elementKind === 'bool' || variable.elementKind === 'char'
395
+ ? 1
396
+ : 4;
397
+ if (dimensions.length === 2) {
398
+ const previewRows = Math.min(dimensions[0], 4);
399
+ const previewCols = Math.min(dimensions[1], 8);
400
+ const rows = [];
401
+ for (let row = 0; row < previewRows; row += 1) {
402
+ const values = [];
403
+ for (let col = 0; col < previewCols; col += 1) {
404
+ const offset = address + (row * dimensions[1] + col) * elementStride;
405
+ if (variable.elementKind === 'bool') {
406
+ values.push(this.mem.read8(offset) ? 'true' : 'false');
407
+ continue;
408
+ }
409
+ if (variable.elementKind === 'char') {
410
+ const charCode = this.mem.read8(offset);
411
+ values.push(charCode >= 0x20 && charCode <= 0x7e
412
+ ? `'${String.fromCharCode(charCode)}'`
413
+ : `${charCode}`);
414
+ continue;
415
+ }
416
+ if (variable.elementKind === 'float') {
417
+ values.push(`${this.mem.readFloat32(offset)}`);
418
+ continue;
419
+ }
420
+ if (variable.elementKind === 'double') {
421
+ values.push(`${this.mem.readFloat64(offset)}`);
422
+ continue;
423
+ }
424
+ values.push(`${this.mem.readInt32(offset)}`);
425
+ }
426
+ rows.push(`[${values.join(', ')}${dimensions[1] > previewCols ? ', ...' : ''}]`);
427
+ }
428
+ return [
429
+ {
430
+ name: variable.name,
431
+ value: `[${rows.join(', ')}${dimensions[0] > previewRows ? ', ...' : ''}]`
432
+ }
433
+ ];
434
+ }
435
+ const previewLength = Math.min(dimensions[0], 8);
436
+ const values = [];
437
+ for (let index = 0; index < previewLength; index += 1) {
438
+ const offset = address + index * elementStride;
439
+ if (variable.elementKind === 'bool') {
440
+ values.push(this.mem.read8(offset) ? 'true' : 'false');
441
+ continue;
442
+ }
443
+ if (variable.elementKind === 'char') {
444
+ const charCode = this.mem.read8(offset);
445
+ values.push(charCode >= 0x20 && charCode <= 0x7e
446
+ ? `'${String.fromCharCode(charCode)}'`
447
+ : `${charCode}`);
448
+ continue;
449
+ }
450
+ if (variable.elementKind === 'float') {
451
+ values.push(`${this.mem.readFloat32(offset)}`);
452
+ continue;
453
+ }
454
+ if (variable.elementKind === 'double') {
455
+ values.push(`${this.mem.readFloat64(offset)}`);
456
+ continue;
457
+ }
458
+ values.push(`${this.mem.readInt32(offset)}`);
459
+ }
460
+ return [
461
+ {
462
+ name: variable.name,
463
+ value: `[${values.join(', ')}${dimensions[0] > previewLength ? ', ...' : ''}]`
464
+ }
465
+ ];
466
+ }
188
467
  const value = session.globalValues?.get(variable.slot) ?? '?';
189
468
  return [{ name: variable.name, value }];
190
469
  }) || [];
@@ -193,9 +472,7 @@ export default class App {
193
472
  line,
194
473
  reason,
195
474
  locals: [...locals, ...globals],
196
- callStack: [...session.frames]
197
- .reverse()
198
- .map((stackFrame) => ({
475
+ callStack: [...session.frames].reverse().map((stackFrame) => ({
199
476
  functionName: stackFrame.functionName,
200
477
  line: stackFrame.line
201
478
  }))
@@ -238,6 +515,18 @@ export default class App {
238
515
  session.resumeSkipLine = session.currentLine;
239
516
  return ESUCCESS;
240
517
  }
518
+ if (command === 5) {
519
+ const expression = session.watchBuffer ? readBufferedStdin(session.watchBuffer) : '';
520
+ let result = '?';
521
+ try {
522
+ result = expression ? this.debugEvaluate(expression) : '?';
523
+ }
524
+ catch (error) {
525
+ result = error instanceof Error && error.message === 'unavailable' ? '?' : 'error';
526
+ }
527
+ if (session.watchResultBuffer)
528
+ flushQueuedStdin([result], session.watchResultBuffer);
529
+ }
241
530
  }
242
531
  }
243
532
  __wasm_idle_debug_enter(functionId, line) {
@@ -356,6 +645,18 @@ export default class App {
356
645
  const session = this.debugSession;
357
646
  if (!session?.buffer)
358
647
  return ESUCCESS;
648
+ const sharedBreakpointVersion = Atomics.load(session.buffer, 2);
649
+ if (sharedBreakpointVersion !== session.breakpointVersion) {
650
+ const count = Math.max(0, Atomics.load(session.buffer, 3));
651
+ const nextBreakpoints = new Set();
652
+ for (let index = 0; index < count && index + 4 < session.buffer.length; index += 1) {
653
+ const breakpoint = Atomics.load(session.buffer, index + 4);
654
+ if (breakpoint > 0)
655
+ nextBreakpoints.add(breakpoint);
656
+ }
657
+ session.breakpoints = nextBreakpoints;
658
+ session.breakpointVersion = sharedBreakpointVersion;
659
+ }
359
660
  if (session.resumeSkipActive) {
360
661
  if (functionId === session.resumeSkipFunctionId && line === session.resumeSkipLine) {
361
662
  return ESUCCESS;
@@ -392,19 +693,20 @@ export default class App {
392
693
  // +2 to account for = and \0 in "name=value\0".
393
694
  size += name.length + value.length + 2;
394
695
  }
395
- this.mem.write64(environ_count_out, names.length);
396
- this.mem.write64(environ_buf_size_out, size);
696
+ this.mem.write32(environ_count_out, names.length);
697
+ this.mem.write32(environ_buf_size_out, size);
698
+ this.trace(`environ_sizes_get(count=${names.length}, bytes=${size})`);
397
699
  return ESUCCESS;
398
700
  }
399
701
  environ_get(environ_ptrs, environ_buf) {
400
702
  this.mem.check();
401
703
  const names = Object.getOwnPropertyNames(this.environ);
704
+ this.trace(`environ_get(entries=${JSON.stringify(names)})`);
402
705
  for (const name of names) {
403
706
  this.mem.write32(environ_ptrs, environ_buf);
404
707
  environ_ptrs += 4;
405
708
  environ_buf += this.mem.writeStr(environ_buf, `${name}=${this.environ[name]}`);
406
709
  }
407
- this.mem.write32(environ_ptrs, 0);
408
710
  return ESUCCESS;
409
711
  }
410
712
  args_sizes_get(argc_out, argv_buf_size_out) {
@@ -413,18 +715,19 @@ export default class App {
413
715
  for (let arg of this.argv) {
414
716
  size += arg.length + 1; // "arg\0".
415
717
  }
416
- this.mem.write64(argc_out, this.argv.length);
417
- this.mem.write64(argv_buf_size_out, size);
718
+ this.mem.write32(argc_out, this.argv.length);
719
+ this.mem.write32(argv_buf_size_out, size);
720
+ this.trace(`args_sizes_get(count=${this.argv.length}, bytes=${size})`);
418
721
  return ESUCCESS;
419
722
  }
420
723
  args_get(argv_ptrs, argv_buf) {
421
724
  this.mem.check();
725
+ this.trace(`args_get(argv=${JSON.stringify(this.argv)})`);
422
726
  for (let arg of this.argv) {
423
727
  this.mem.write32(argv_ptrs, argv_buf);
424
728
  argv_ptrs += 4;
425
729
  argv_buf += this.mem.writeStr(argv_buf, arg);
426
730
  }
427
- this.mem.write32(argv_ptrs, 0);
428
731
  return ESUCCESS;
429
732
  }
430
733
  random_get(buf, buf_len) {
@@ -29,6 +29,8 @@ interface DebugRunOptions {
29
29
  pauseOnEntry?: boolean;
30
30
  debugBuffer?: Int32Array;
31
31
  interruptBuffer?: Uint8Array;
32
+ watchBuffer?: Int32Array;
33
+ watchResultBuffer?: Int32Array;
32
34
  }
33
35
  export default class Clang {
34
36
  ready: Promise<void>;
@@ -44,6 +46,8 @@ export default class Clang {
44
46
  debugPauseOnEntry: boolean;
45
47
  debugBuffer?: Int32Array;
46
48
  debugInterruptBuffer?: Uint8Array;
49
+ debugWatchBuffer?: Int32Array;
50
+ debugWatchResultBuffer?: Int32Array;
47
51
  onDebugEvent?: (event: {
48
52
  type: 'pause';
49
53
  line: number;