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.
- package/README.md +94 -3
- package/dist/clang/app.d.ts +4 -0
- package/dist/clang/app.js +315 -12
- package/dist/clang/index.d.ts +4 -0
- package/dist/clang/index.js +151 -8
- package/dist/debug/controller.svelte.d.ts +43 -0
- package/dist/debug/controller.svelte.js +268 -0
- package/dist/debug/editor/index.d.ts +2 -1
- package/dist/debug/editor/index.js +1 -1
- package/dist/debug/editor/monaco.d.ts +8 -0
- package/dist/debug/editor/monaco.js +23 -0
- package/dist/debug/expression.d.ts +24 -0
- package/dist/debug/expression.js +307 -138
- package/dist/debug/index.d.ts +4 -1
- package/dist/debug/index.js +2 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -3
- package/dist/playground/assets.d.ts +5 -0
- package/dist/playground/assets.js +8 -0
- package/dist/playground/clang.d.ts +5 -0
- package/dist/playground/clang.js +45 -2
- package/dist/playground/index.d.ts +5 -5
- package/dist/playground/index.js +45 -5
- package/dist/playground/options.d.ts +9 -0
- package/dist/playground/python.d.ts +1 -0
- package/dist/playground/python.js +12 -0
- package/dist/playground/rust.d.ts +32 -0
- package/dist/playground/rust.js +175 -0
- package/dist/playground/sandbox.d.ts +18 -4
- package/dist/playground/wasmRustVersion.d.ts +1 -0
- package/dist/playground/wasmRustVersion.js +1 -0
- package/dist/playground/worker/clang.js +12 -4
- package/dist/playground/worker/rust.d.ts +1 -0
- package/dist/playground/worker/rust.js +159 -0
- package/dist/terminal/Terminal.svelte +78 -25
- package/dist/terminal/Terminal.svelte.d.ts +2 -0
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +3 -0
- package/package.json +15 -9
package/README.md
CHANGED
|
@@ -2,12 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
Executes C++, Python, and
|
|
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
|
|
120
|
+
Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM, and `wasm-rust`.
|
package/dist/clang/app.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
396
|
-
this.mem.
|
|
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.
|
|
417
|
-
this.mem.
|
|
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) {
|
package/dist/clang/index.d.ts
CHANGED
|
@@ -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;
|