wasm-idle 0.4.0 → 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 +111 -2
- package/dist/clang/app.d.ts +4 -0
- package/dist/clang/app.js +330 -15
- package/dist/clang/index.d.ts +10 -0
- package/dist/clang/index.js +284 -25
- 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 +6 -3
- package/dist/index.js +3 -3
- package/dist/playground/assetBridge.d.ts +20 -0
- package/dist/playground/assetBridge.js +192 -0
- package/dist/playground/assets.d.ts +37 -0
- package/dist/playground/assets.js +105 -0
- package/dist/playground/clang.d.ts +11 -1
- package/dist/playground/clang.js +58 -4
- package/dist/playground/index.d.ts +5 -5
- package/dist/playground/index.js +48 -6
- package/dist/playground/java.d.ts +7 -2
- package/dist/playground/java.js +31 -5
- package/dist/playground/javaStdin.js +235 -2
- package/dist/playground/options.d.ts +18 -0
- package/dist/playground/options.js +15 -1
- package/dist/playground/python.d.ts +7 -1
- package/dist/playground/python.js +50 -5
- package/dist/playground/rust.d.ts +32 -0
- package/dist/playground/rust.js +175 -0
- package/dist/playground/sandbox.d.ts +19 -4
- package/dist/playground/wasmRustVersion.d.ts +1 -0
- package/dist/playground/wasmRustVersion.js +1 -0
- package/dist/playground/worker/assets.d.ts +12 -0
- package/dist/playground/worker/assets.js +257 -0
- package/dist/playground/worker/clang.js +22 -6
- package/dist/playground/worker/java.js +15 -35
- package/dist/playground/worker/python.js +13 -33
- package/dist/playground/worker/rust.d.ts +1 -0
- package/dist/playground/worker/rust.js +159 -0
- package/dist/terminal/Terminal.svelte +121 -18
- package/dist/terminal/Terminal.svelte.d.ts +4 -0
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +4 -0
- package/package.json +19 -13
package/README.md
CHANGED
|
@@ -2,10 +2,119 @@
|
|
|
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
|
-
|
|
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
|
+
|
|
79
|
+
`Terminal` and `playground(...).load(...)` support either the legacy shared `path`/`rootUrl` or per-runtime asset config:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import type { PlaygroundRuntimeAssets } from 'wasm-idle';
|
|
83
|
+
|
|
84
|
+
const runtimeAssets: PlaygroundRuntimeAssets = {
|
|
85
|
+
rootUrl: 'https://cdn.example.com/repl',
|
|
86
|
+
python: {
|
|
87
|
+
loader: async ({ asset }) => ({ url: `https://cdn.example.com/repl/pyodide/${asset}` })
|
|
88
|
+
},
|
|
89
|
+
java: {
|
|
90
|
+
baseUrl: 'https://cdn.example.com/repl/teavm/'
|
|
91
|
+
},
|
|
92
|
+
rust: {
|
|
93
|
+
compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
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
|
+
```
|
|
119
|
+
|
|
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,15 +74,125 @@ 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)
|
|
74
190
|
return ESUCCESS;
|
|
75
191
|
session.currentFunctionId = functionId;
|
|
76
192
|
session.currentLine = line;
|
|
77
|
-
const frame = [...session.frames]
|
|
193
|
+
const frame = [...session.frames]
|
|
194
|
+
.reverse()
|
|
195
|
+
.find((candidate) => candidate.functionId === functionId);
|
|
78
196
|
if (frame)
|
|
79
197
|
frame.line = line;
|
|
80
198
|
session.pauseOnEntry = false;
|
|
@@ -93,9 +211,49 @@ export default class App {
|
|
|
93
211
|
: variable.length
|
|
94
212
|
? [variable.length]
|
|
95
213
|
: [];
|
|
96
|
-
if (!Number.isFinite(address) ||
|
|
214
|
+
if (!Number.isFinite(address) ||
|
|
215
|
+
address <= 0 ||
|
|
216
|
+
!dimensions.length ||
|
|
217
|
+
(!variable.elementKind && !variable.structFields?.length)) {
|
|
97
218
|
return [{ name: variable.name, value: '?' }];
|
|
98
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: '?' }];
|
|
99
257
|
const elementStride = variable.elementKind === 'double'
|
|
100
258
|
? 8
|
|
101
259
|
: variable.elementKind === 'bool' || variable.elementKind === 'char'
|
|
@@ -115,7 +273,9 @@ export default class App {
|
|
|
115
273
|
}
|
|
116
274
|
if (variable.elementKind === 'char') {
|
|
117
275
|
const charCode = this.mem.read8(offset);
|
|
118
|
-
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
276
|
+
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
277
|
+
? `'${String.fromCharCode(charCode)}'`
|
|
278
|
+
: `${charCode}`);
|
|
119
279
|
continue;
|
|
120
280
|
}
|
|
121
281
|
if (variable.elementKind === 'float') {
|
|
@@ -147,7 +307,9 @@ export default class App {
|
|
|
147
307
|
}
|
|
148
308
|
if (variable.elementKind === 'char') {
|
|
149
309
|
const charCode = this.mem.read8(offset);
|
|
150
|
-
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
310
|
+
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
311
|
+
? `'${String.fromCharCode(charCode)}'`
|
|
312
|
+
: `${charCode}`);
|
|
151
313
|
continue;
|
|
152
314
|
}
|
|
153
315
|
if (variable.elementKind === 'float') {
|
|
@@ -176,6 +338,132 @@ export default class App {
|
|
|
176
338
|
return [];
|
|
177
339
|
if (line < variable.fromLine || line > variable.toLine)
|
|
178
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
|
+
}
|
|
179
467
|
const value = session.globalValues?.get(variable.slot) ?? '?';
|
|
180
468
|
return [{ name: variable.name, value }];
|
|
181
469
|
}) || [];
|
|
@@ -184,9 +472,10 @@ export default class App {
|
|
|
184
472
|
line,
|
|
185
473
|
reason,
|
|
186
474
|
locals: [...locals, ...globals],
|
|
187
|
-
callStack: [...session.frames]
|
|
188
|
-
.
|
|
189
|
-
|
|
475
|
+
callStack: [...session.frames].reverse().map((stackFrame) => ({
|
|
476
|
+
functionName: stackFrame.functionName,
|
|
477
|
+
line: stackFrame.line
|
|
478
|
+
}))
|
|
190
479
|
});
|
|
191
480
|
const sequence = Atomics.load(buffer, 0);
|
|
192
481
|
while (true) {
|
|
@@ -226,6 +515,18 @@ export default class App {
|
|
|
226
515
|
session.resumeSkipLine = session.currentLine;
|
|
227
516
|
return ESUCCESS;
|
|
228
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
|
+
}
|
|
229
530
|
}
|
|
230
531
|
}
|
|
231
532
|
__wasm_idle_debug_enter(functionId, line) {
|
|
@@ -344,6 +645,18 @@ export default class App {
|
|
|
344
645
|
const session = this.debugSession;
|
|
345
646
|
if (!session?.buffer)
|
|
346
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
|
+
}
|
|
347
660
|
if (session.resumeSkipActive) {
|
|
348
661
|
if (functionId === session.resumeSkipFunctionId && line === session.resumeSkipLine) {
|
|
349
662
|
return ESUCCESS;
|
|
@@ -380,19 +693,20 @@ export default class App {
|
|
|
380
693
|
// +2 to account for = and \0 in "name=value\0".
|
|
381
694
|
size += name.length + value.length + 2;
|
|
382
695
|
}
|
|
383
|
-
this.mem.
|
|
384
|
-
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})`);
|
|
385
699
|
return ESUCCESS;
|
|
386
700
|
}
|
|
387
701
|
environ_get(environ_ptrs, environ_buf) {
|
|
388
702
|
this.mem.check();
|
|
389
703
|
const names = Object.getOwnPropertyNames(this.environ);
|
|
704
|
+
this.trace(`environ_get(entries=${JSON.stringify(names)})`);
|
|
390
705
|
for (const name of names) {
|
|
391
706
|
this.mem.write32(environ_ptrs, environ_buf);
|
|
392
707
|
environ_ptrs += 4;
|
|
393
708
|
environ_buf += this.mem.writeStr(environ_buf, `${name}=${this.environ[name]}`);
|
|
394
709
|
}
|
|
395
|
-
this.mem.write32(environ_ptrs, 0);
|
|
396
710
|
return ESUCCESS;
|
|
397
711
|
}
|
|
398
712
|
args_sizes_get(argc_out, argv_buf_size_out) {
|
|
@@ -401,18 +715,19 @@ export default class App {
|
|
|
401
715
|
for (let arg of this.argv) {
|
|
402
716
|
size += arg.length + 1; // "arg\0".
|
|
403
717
|
}
|
|
404
|
-
this.mem.
|
|
405
|
-
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})`);
|
|
406
721
|
return ESUCCESS;
|
|
407
722
|
}
|
|
408
723
|
args_get(argv_ptrs, argv_buf) {
|
|
409
724
|
this.mem.check();
|
|
725
|
+
this.trace(`args_get(argv=${JSON.stringify(this.argv)})`);
|
|
410
726
|
for (let arg of this.argv) {
|
|
411
727
|
this.mem.write32(argv_ptrs, argv_buf);
|
|
412
728
|
argv_ptrs += 4;
|
|
413
729
|
argv_buf += this.mem.writeStr(argv_buf, arg);
|
|
414
730
|
}
|
|
415
|
-
this.mem.write32(argv_ptrs, 0);
|
|
416
731
|
return ESUCCESS;
|
|
417
732
|
}
|
|
418
733
|
random_get(buf, buf_len) {
|
package/dist/clang/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DebugVariable, DebugVariableMetadata } from '../playground/options
|
|
|
2
2
|
import App from './app';
|
|
3
3
|
import { MemFS } from './memory';
|
|
4
4
|
import { type Writable } from 'svelte/store';
|
|
5
|
+
type ClangSourceLanguage = 'C' | 'CPP';
|
|
5
6
|
interface APIOption {
|
|
6
7
|
stdin: () => string;
|
|
7
8
|
stdout: (str: string) => void;
|
|
@@ -17,12 +18,19 @@ interface APIOption {
|
|
|
17
18
|
path: string;
|
|
18
19
|
}
|
|
19
20
|
interface DebugRunOptions {
|
|
21
|
+
language?: ClangSourceLanguage;
|
|
20
22
|
args?: string[];
|
|
23
|
+
compileArgs?: string[];
|
|
24
|
+
programArgs?: string[];
|
|
25
|
+
cppVersion?: string;
|
|
26
|
+
cVersion?: string;
|
|
21
27
|
debug?: boolean;
|
|
22
28
|
breakpoints?: number[];
|
|
23
29
|
pauseOnEntry?: boolean;
|
|
24
30
|
debugBuffer?: Int32Array;
|
|
25
31
|
interruptBuffer?: Uint8Array;
|
|
32
|
+
watchBuffer?: Int32Array;
|
|
33
|
+
watchResultBuffer?: Int32Array;
|
|
26
34
|
}
|
|
27
35
|
export default class Clang {
|
|
28
36
|
ready: Promise<void>;
|
|
@@ -38,6 +46,8 @@ export default class Clang {
|
|
|
38
46
|
debugPauseOnEntry: boolean;
|
|
39
47
|
debugBuffer?: Int32Array;
|
|
40
48
|
debugInterruptBuffer?: Uint8Array;
|
|
49
|
+
debugWatchBuffer?: Int32Array;
|
|
50
|
+
debugWatchResultBuffer?: Int32Array;
|
|
41
51
|
onDebugEvent?: (event: {
|
|
42
52
|
type: 'pause';
|
|
43
53
|
line: number;
|