wasm-idle 0.2.4 → 0.3.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/dist/clang/app.d.ts +44 -0
- package/dist/clang/app.js +229 -2
- package/dist/clang/index.d.ts +40 -9
- package/dist/clang/index.js +591 -15
- package/dist/clang/memory/memFs.d.ts +3 -1
- package/dist/clang/memory/memFs.js +12 -1
- package/dist/clang/memory/memory.d.ts +8 -4
- package/dist/clang/memory/memory.js +14 -0
- package/dist/clang/memory/tar.d.ts +1 -1
- package/dist/clang/memory/tar.js +1 -1
- package/dist/clang/wasm.d.ts +1 -1
- package/dist/clang/wasm.js +13 -4
- package/dist/clangd/config.d.ts +18 -0
- package/dist/clangd/config.js +14 -0
- package/dist/clangd/jsonStream.d.ts +9 -0
- package/dist/clangd/jsonStream.js +48 -0
- package/dist/clangd/session.d.ts +14 -0
- package/dist/clangd/session.js +94 -0
- package/dist/clangd/worker.d.ts +1 -0
- package/dist/clangd/worker.js +130 -0
- package/dist/debug/editor/index.d.ts +1 -0
- package/dist/debug/editor/index.js +1 -0
- package/dist/debug/editor/monaco.d.ts +18 -0
- package/dist/debug/editor/monaco.js +105 -0
- package/dist/debug/expression.d.ts +2 -0
- package/dist/debug/expression.js +182 -0
- package/dist/debug/index.d.ts +5 -0
- package/dist/debug/index.js +4 -0
- package/dist/debug/language/cpp.d.ts +2 -0
- package/dist/debug/language/cpp.js +7 -0
- package/dist/debug/language/index.d.ts +2 -0
- package/dist/debug/language/index.js +1 -0
- package/dist/debug/language/types.d.ts +6 -0
- package/dist/debug/language/types.js +1 -0
- package/dist/debug/locals.d.ts +2 -0
- package/dist/debug/locals.js +8 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/playground/clang.d.ts +8 -2
- package/dist/playground/clang.js +25 -4
- package/dist/playground/options.d.ts +38 -0
- package/dist/playground/options.js +1 -0
- package/dist/playground/python.d.ts +10 -2
- package/dist/playground/python.js +6 -3
- package/dist/playground/sandbox.d.ts +15 -2
- package/dist/playground/worker/clang.js +21 -4
- package/dist/playground/worker/python.js +196 -3
- package/dist/terminal/Terminal.svelte +35 -11
- package/dist/terminal/Terminal.svelte.d.ts +12 -9
- package/dist/terminal/index.d.ts +2 -0
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +13 -0
- package/dist/terminal/types.js +1 -0
- package/package.json +42 -33
package/dist/clang/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const clangCommonArgs = [
|
|
|
20
20
|
'80',
|
|
21
21
|
'-fcolor-diagnostics'
|
|
22
22
|
];
|
|
23
|
+
const clangLanguageStandardArg = '-std=gnu++2a';
|
|
23
24
|
const toUtf8 = (text) => {
|
|
24
25
|
const surrogate = encodeURIComponent(text);
|
|
25
26
|
let result = '';
|
|
@@ -44,9 +45,18 @@ export default class Clang {
|
|
|
44
45
|
moduleCache;
|
|
45
46
|
showTiming;
|
|
46
47
|
log;
|
|
47
|
-
|
|
48
|
+
debug = false;
|
|
49
|
+
debugBreakpoints = new Set();
|
|
50
|
+
debugPauseOnEntry = false;
|
|
51
|
+
debugBuffer;
|
|
52
|
+
debugInterruptBuffer;
|
|
53
|
+
onDebugEvent;
|
|
54
|
+
debugVariableMetadata = {};
|
|
55
|
+
debugFunctionMetadata = {};
|
|
56
|
+
lastBuildKey = '';
|
|
48
57
|
path;
|
|
49
58
|
wasm;
|
|
59
|
+
traceStartedAt = 0;
|
|
50
60
|
progress = {
|
|
51
61
|
clang: writable(0),
|
|
52
62
|
lld: writable(0),
|
|
@@ -58,11 +68,13 @@ export default class Clang {
|
|
|
58
68
|
this.showTiming = options.showTiming || false;
|
|
59
69
|
this.log = options.log || false;
|
|
60
70
|
this.path = options.path;
|
|
71
|
+
this.onDebugEvent = options.onDebugEvent;
|
|
61
72
|
this.memfs = new MemFS({
|
|
62
73
|
stdout: this.stdout,
|
|
63
74
|
stdin: options.stdin,
|
|
64
75
|
path: this.path,
|
|
65
|
-
progress: this.progress.memfs
|
|
76
|
+
progress: this.progress.memfs,
|
|
77
|
+
trace: (message) => this.trace(message)
|
|
66
78
|
});
|
|
67
79
|
const progress = derived([this.progress.clang, this.progress.lld, this.progress.memfs], ([clang, lld, memfs]) => {
|
|
68
80
|
return (clang + lld + memfs) / 3;
|
|
@@ -80,6 +92,16 @@ export default class Clang {
|
|
|
80
92
|
const yellowArrow = `${yellow}>${normal} `;
|
|
81
93
|
this.stdout(`${yellowArrow}${message}`);
|
|
82
94
|
}
|
|
95
|
+
beginTrace(debug) {
|
|
96
|
+
this.debug = debug;
|
|
97
|
+
this.traceStartedAt = Date.now();
|
|
98
|
+
}
|
|
99
|
+
trace(message) {
|
|
100
|
+
if (!this.debug)
|
|
101
|
+
return;
|
|
102
|
+
const elapsed = Date.now() - this.traceStartedAt;
|
|
103
|
+
this.stdout(`\x1b[2m[debug +${elapsed}ms] ${message}\x1b[0m\n`);
|
|
104
|
+
}
|
|
83
105
|
async hostLogAsync(message, promise) {
|
|
84
106
|
const start = +new Date();
|
|
85
107
|
this.hostLog(`${message}...`);
|
|
@@ -102,28 +124,563 @@ export default class Clang {
|
|
|
102
124
|
}
|
|
103
125
|
async compile(options) {
|
|
104
126
|
const input = options.input;
|
|
105
|
-
|
|
127
|
+
let source = options.code;
|
|
106
128
|
const obj = options.obj;
|
|
107
|
-
const
|
|
129
|
+
const debug = !!options.debug;
|
|
130
|
+
const opt = debug ? '0' : (options.opt || '2');
|
|
131
|
+
if (debug) {
|
|
132
|
+
const lines = source.split('\n');
|
|
133
|
+
let braceDepth = 0;
|
|
134
|
+
let functionDepth = 0;
|
|
135
|
+
let currentFunctionId = 0;
|
|
136
|
+
let nextFunctionId = 1;
|
|
137
|
+
let nextVariableSlot = 1;
|
|
138
|
+
let currentFunctionVariables = new Map();
|
|
139
|
+
let currentFunctionContainers = new Map();
|
|
140
|
+
let inBlockComment = false;
|
|
141
|
+
let pendingFunctionHeader;
|
|
142
|
+
this.debugVariableMetadata = {};
|
|
143
|
+
this.debugFunctionMetadata = {};
|
|
144
|
+
const instrumented = [
|
|
145
|
+
'#include <map>',
|
|
146
|
+
'#include <set>',
|
|
147
|
+
'#include <string>',
|
|
148
|
+
'#include <type_traits>',
|
|
149
|
+
'#include <vector>',
|
|
150
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_enter"))) void __wasm_idle_debug_enter(int functionId);',
|
|
151
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_leave"))) void __wasm_idle_debug_leave(int functionId);',
|
|
152
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_num"))) void __wasm_idle_debug_value_num(int functionId, int slot, double value);',
|
|
153
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_bool"))) void __wasm_idle_debug_value_bool(int functionId, int slot, int value);',
|
|
154
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_addr"))) void __wasm_idle_debug_value_addr(int functionId, int slot, int value);',
|
|
155
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_text"))) void __wasm_idle_debug_value_text(int functionId, int slot, const char* ptr, int len);',
|
|
156
|
+
'template <typename T>',
|
|
157
|
+
'static inline std::string __wasm_idle_debug_format_value(const T& value) {',
|
|
158
|
+
' if constexpr (std::is_same_v<T, bool>) return value ? "true" : "false";',
|
|
159
|
+
` else if constexpr (std::is_same_v<T, char>) return std::string("'") + value + "'";`,
|
|
160
|
+
' else if constexpr (std::is_same_v<T, signed char> || std::is_same_v<T, unsigned char>) return std::to_string((int)value);',
|
|
161
|
+
' else if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) return std::to_string(value);',
|
|
162
|
+
' else return "?";',
|
|
163
|
+
'}',
|
|
164
|
+
'template <typename T>',
|
|
165
|
+
'static inline void __wasm_idle_debug_emit_vector(int functionId, int slot, const std::vector<T>& values) {',
|
|
166
|
+
' std::string text = "[";',
|
|
167
|
+
' int count = 0;',
|
|
168
|
+
' for (const auto& value : values) {',
|
|
169
|
+
' if (count > 0) text += ", ";',
|
|
170
|
+
' if (count >= 8) { text += "..."; break; }',
|
|
171
|
+
' text += __wasm_idle_debug_format_value(value);',
|
|
172
|
+
' count += 1;',
|
|
173
|
+
' }',
|
|
174
|
+
' text += "]";',
|
|
175
|
+
' __wasm_idle_debug_value_text(functionId, slot, text.c_str(), (int)text.size());',
|
|
176
|
+
'}',
|
|
177
|
+
'template <typename T>',
|
|
178
|
+
'static inline void __wasm_idle_debug_emit_set(int functionId, int slot, const std::set<T>& values) {',
|
|
179
|
+
' std::string text = "{";',
|
|
180
|
+
' int count = 0;',
|
|
181
|
+
' for (const auto& value : values) {',
|
|
182
|
+
' if (count > 0) text += ", ";',
|
|
183
|
+
' if (count >= 8) { text += "..."; break; }',
|
|
184
|
+
' text += __wasm_idle_debug_format_value(value);',
|
|
185
|
+
' count += 1;',
|
|
186
|
+
' }',
|
|
187
|
+
' text += "}";',
|
|
188
|
+
' __wasm_idle_debug_value_text(functionId, slot, text.c_str(), (int)text.size());',
|
|
189
|
+
'}',
|
|
190
|
+
'template <typename K, typename V>',
|
|
191
|
+
'static inline void __wasm_idle_debug_emit_map(int functionId, int slot, const std::map<K, V>& values) {',
|
|
192
|
+
' std::string text = "{";',
|
|
193
|
+
' int count = 0;',
|
|
194
|
+
' for (const auto& entry : values) {',
|
|
195
|
+
' if (count > 0) text += ", ";',
|
|
196
|
+
' if (count >= 8) { text += "..."; break; }',
|
|
197
|
+
' text += __wasm_idle_debug_format_value(entry.first);',
|
|
198
|
+
' text += ": ";',
|
|
199
|
+
' text += __wasm_idle_debug_format_value(entry.second);',
|
|
200
|
+
' count += 1;',
|
|
201
|
+
' }',
|
|
202
|
+
' text += "}";',
|
|
203
|
+
' __wasm_idle_debug_value_text(functionId, slot, text.c_str(), (int)text.size());',
|
|
204
|
+
'}',
|
|
205
|
+
'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_line"))) void __wasm_idle_debug_line(int functionId, int line);'
|
|
206
|
+
];
|
|
207
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
208
|
+
const line = lines[index];
|
|
209
|
+
const indent = line.match(/^\s*/)?.[0] || '';
|
|
210
|
+
let rewrittenLine = line;
|
|
211
|
+
let analysisLine = line;
|
|
212
|
+
if (inBlockComment) {
|
|
213
|
+
const commentEnd = analysisLine.indexOf('*/');
|
|
214
|
+
if (commentEnd === -1) {
|
|
215
|
+
instrumented.push(line);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
analysisLine = analysisLine.slice(commentEnd + 2);
|
|
219
|
+
inBlockComment = false;
|
|
220
|
+
}
|
|
221
|
+
const commentStart = analysisLine.indexOf('/*');
|
|
222
|
+
if (commentStart !== -1) {
|
|
223
|
+
const commentEnd = analysisLine.indexOf('*/', commentStart + 2);
|
|
224
|
+
if (commentEnd === -1) {
|
|
225
|
+
inBlockComment = true;
|
|
226
|
+
analysisLine = analysisLine.slice(0, commentStart);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
analysisLine =
|
|
230
|
+
analysisLine.slice(0, commentStart) + analysisLine.slice(commentEnd + 2);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const lineComment = analysisLine.indexOf('//');
|
|
234
|
+
if (lineComment !== -1)
|
|
235
|
+
analysisLine = analysisLine.slice(0, lineComment);
|
|
236
|
+
const normalized = analysisLine.trim();
|
|
237
|
+
const inFunctionBody = functionDepth > 0 && braceDepth >= functionDepth;
|
|
238
|
+
const controlHeaderWithoutInlineBlock = /^(while|if|for)\s*\(/.test(normalized) && !normalized.includes('{');
|
|
239
|
+
const leadingInstrumentation = [];
|
|
240
|
+
const trailingInstrumentation = [];
|
|
241
|
+
const declaredContainerNames = new Set();
|
|
242
|
+
if (inFunctionBody &&
|
|
243
|
+
normalized &&
|
|
244
|
+
!normalized.startsWith('#') &&
|
|
245
|
+
normalized !== '{' &&
|
|
246
|
+
normalized !== '}' &&
|
|
247
|
+
!normalized.startsWith('else') &&
|
|
248
|
+
!normalized.startsWith('case ') &&
|
|
249
|
+
normalized !== 'case' &&
|
|
250
|
+
!normalized.startsWith('default') &&
|
|
251
|
+
!normalized.startsWith('catch') &&
|
|
252
|
+
!/^(public|private|protected)\s*:/.test(normalized) &&
|
|
253
|
+
!normalized.endsWith(':') &&
|
|
254
|
+
!normalized.includes(' else ')) {
|
|
255
|
+
leadingInstrumentation.push(`${indent}__wasm_idle_debug_line(${currentFunctionId}, ${index + 1});`);
|
|
256
|
+
const declarationMatch = normalized.match(/^(?:const\s+)?(?:(?:unsigned|signed)\s+)?(?:(?:short|long long|long)\s+)?(int|float|double|bool|char)\s+(.+);$/);
|
|
257
|
+
const containerDeclarationMatch = normalized.match(/^(?:const\s+)?(?:(?:std::)?(vector|set|map))\s*<(.+)>\s+([A-Za-z_]\w*)\s*(?:=.*)?;$/);
|
|
258
|
+
if (containerDeclarationMatch && currentFunctionId) {
|
|
259
|
+
const slot = nextVariableSlot++;
|
|
260
|
+
const container = containerDeclarationMatch[1];
|
|
261
|
+
const name = containerDeclarationMatch[3];
|
|
262
|
+
declaredContainerNames.add(name);
|
|
263
|
+
currentFunctionContainers.set(name, {
|
|
264
|
+
slot,
|
|
265
|
+
container,
|
|
266
|
+
fromLine: index + 1,
|
|
267
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
268
|
+
});
|
|
269
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
270
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
271
|
+
{
|
|
272
|
+
slot,
|
|
273
|
+
name,
|
|
274
|
+
kind: 'text',
|
|
275
|
+
fromLine: index + 1,
|
|
276
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
277
|
+
}
|
|
278
|
+
];
|
|
279
|
+
trailingInstrumentation.push(`${indent}__wasm_idle_debug_emit_${container}(${currentFunctionId}, ${slot}, ${name});`);
|
|
280
|
+
}
|
|
281
|
+
if (declarationMatch && currentFunctionId) {
|
|
282
|
+
const kind = declarationMatch[1] === 'bool' ? 'bool' : 'number';
|
|
283
|
+
const declarations = [];
|
|
284
|
+
let declarationBuffer = '';
|
|
285
|
+
let declarationParenDepth = 0;
|
|
286
|
+
let declarationBraceDepth = 0;
|
|
287
|
+
for (const character of declarationMatch[2]) {
|
|
288
|
+
if (character === ',' && declarationParenDepth === 0 && declarationBraceDepth === 0) {
|
|
289
|
+
if (declarationBuffer.trim())
|
|
290
|
+
declarations.push(declarationBuffer.trim());
|
|
291
|
+
declarationBuffer = '';
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (character === '(')
|
|
295
|
+
declarationParenDepth += 1;
|
|
296
|
+
if (character === ')')
|
|
297
|
+
declarationParenDepth = Math.max(0, declarationParenDepth - 1);
|
|
298
|
+
if (character === '{')
|
|
299
|
+
declarationBraceDepth += 1;
|
|
300
|
+
if (character === '}')
|
|
301
|
+
declarationBraceDepth = Math.max(0, declarationBraceDepth - 1);
|
|
302
|
+
declarationBuffer += character;
|
|
303
|
+
}
|
|
304
|
+
if (declarationBuffer.trim())
|
|
305
|
+
declarations.push(declarationBuffer.trim());
|
|
306
|
+
for (const declaration of declarations) {
|
|
307
|
+
const [left] = declaration.split('=');
|
|
308
|
+
const declarator = left?.trim() || '';
|
|
309
|
+
const arrayMatch = declarator.match(/([A-Za-z_]\w*)\s*\[(\d+)\]\s*$/);
|
|
310
|
+
if (arrayMatch) {
|
|
311
|
+
const slot = nextVariableSlot++;
|
|
312
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
313
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
314
|
+
{
|
|
315
|
+
slot,
|
|
316
|
+
name: arrayMatch[1],
|
|
317
|
+
kind: 'array',
|
|
318
|
+
elementKind: declarationMatch[1],
|
|
319
|
+
length: Number(arrayMatch[2]),
|
|
320
|
+
fromLine: index + 1,
|
|
321
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
322
|
+
}
|
|
323
|
+
];
|
|
324
|
+
trailingInstrumentation.push(`${indent}__wasm_idle_debug_value_addr(${currentFunctionId}, ${slot}, (int)((unsigned long long)(${arrayMatch[1]})));`);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (/[*&]/.test(declarator))
|
|
328
|
+
continue;
|
|
329
|
+
const name = declarator.match(/([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?$/)?.[1];
|
|
330
|
+
if (!name)
|
|
331
|
+
continue;
|
|
332
|
+
if (!currentFunctionVariables.has(name)) {
|
|
333
|
+
const slot = nextVariableSlot++;
|
|
334
|
+
currentFunctionVariables.set(name, {
|
|
335
|
+
slot,
|
|
336
|
+
kind,
|
|
337
|
+
fromLine: index + 1,
|
|
338
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
339
|
+
});
|
|
340
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
341
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
342
|
+
{
|
|
343
|
+
slot,
|
|
344
|
+
name,
|
|
345
|
+
kind,
|
|
346
|
+
fromLine: index + 1,
|
|
347
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
if (declaration.includes('=')) {
|
|
352
|
+
const variable = currentFunctionVariables.get(name);
|
|
353
|
+
if (variable) {
|
|
354
|
+
trailingInstrumentation.push(`${indent}${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name});`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const forDeclarationMatch = normalized.match(/^for\s*\(\s*(?:const\s+)?(?:(?:unsigned|signed)\s+)?(?:(?:short|long long|long)\s+)?(int|float|double|bool|char)\s+([A-Za-z_]\w*)\s*=/);
|
|
360
|
+
if (forDeclarationMatch && currentFunctionId) {
|
|
361
|
+
const kind = forDeclarationMatch[1] === 'bool' ? 'bool' : 'number';
|
|
362
|
+
const name = forDeclarationMatch[2];
|
|
363
|
+
if (!currentFunctionVariables.has(name)) {
|
|
364
|
+
const slot = nextVariableSlot++;
|
|
365
|
+
currentFunctionVariables.set(name, {
|
|
366
|
+
slot,
|
|
367
|
+
kind,
|
|
368
|
+
fromLine: index + 1,
|
|
369
|
+
toLine: index + 1
|
|
370
|
+
});
|
|
371
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
372
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
373
|
+
{
|
|
374
|
+
slot,
|
|
375
|
+
name,
|
|
376
|
+
kind,
|
|
377
|
+
fromLine: index + 1,
|
|
378
|
+
toLine: index + 1
|
|
379
|
+
}
|
|
380
|
+
];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!controlHeaderWithoutInlineBlock) {
|
|
384
|
+
for (const [name, container] of currentFunctionContainers) {
|
|
385
|
+
if (declaredContainerNames.has(name))
|
|
386
|
+
continue;
|
|
387
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
388
|
+
if (!new RegExp(`\\b${escapedName}\\b`).test(normalized))
|
|
389
|
+
continue;
|
|
390
|
+
trailingInstrumentation.push(`${indent}__wasm_idle_debug_emit_${container.container}(${currentFunctionId}, ${container.slot}, ${name});`);
|
|
391
|
+
}
|
|
392
|
+
for (const [name, variable] of currentFunctionVariables) {
|
|
393
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
394
|
+
if (normalized.startsWith('for') && variable.toLine === index + 1)
|
|
395
|
+
continue;
|
|
396
|
+
if (new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b`).test(normalized) ||
|
|
397
|
+
new RegExp(`\\b${escapedName}\\s*(?:[+\\-*/%]?=|\\+\\+|--)`).test(normalized) ||
|
|
398
|
+
new RegExp(`&\\s*${escapedName}\\b`).test(normalized)) {
|
|
399
|
+
trailingInstrumentation.push(`${indent}${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name});`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (/^return\b/.test(normalized))
|
|
404
|
+
leadingInstrumentation.push(`${indent}__wasm_idle_debug_leave(${currentFunctionId});`);
|
|
405
|
+
}
|
|
406
|
+
if (functionDepth > 0 && braceDepth === functionDepth && normalized === '}') {
|
|
407
|
+
leadingInstrumentation.push(`${indent}__wasm_idle_debug_leave(${currentFunctionId});`);
|
|
408
|
+
}
|
|
409
|
+
if (inFunctionBody &&
|
|
410
|
+
currentFunctionId &&
|
|
411
|
+
(/^(while|if)\s*\(/.test(normalized) || /^for\s*\(/.test(normalized))) {
|
|
412
|
+
const keywordMatch = normalized.match(/^(while|if|for)\b/);
|
|
413
|
+
const keyword = keywordMatch?.[1];
|
|
414
|
+
const keywordIndex = line.indexOf(keyword || '');
|
|
415
|
+
const openIndex = keywordIndex >= 0 ? line.indexOf('(', keywordIndex) : -1;
|
|
416
|
+
if (openIndex >= 0) {
|
|
417
|
+
let closeIndex = -1;
|
|
418
|
+
let parenDepth = 0;
|
|
419
|
+
for (let cursor = openIndex; cursor < line.length; cursor += 1) {
|
|
420
|
+
const character = line[cursor];
|
|
421
|
+
if (character === '(')
|
|
422
|
+
parenDepth += 1;
|
|
423
|
+
if (character === ')') {
|
|
424
|
+
parenDepth -= 1;
|
|
425
|
+
if (parenDepth === 0) {
|
|
426
|
+
closeIndex = cursor;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (closeIndex > openIndex) {
|
|
432
|
+
const conditionSource = line.slice(openIndex + 1, closeIndex);
|
|
433
|
+
if (keyword === 'for') {
|
|
434
|
+
const segments = [];
|
|
435
|
+
let segmentBuffer = '';
|
|
436
|
+
let segmentDepth = 0;
|
|
437
|
+
for (const character of conditionSource) {
|
|
438
|
+
if (character === ';' && segmentDepth === 0) {
|
|
439
|
+
segments.push(segmentBuffer);
|
|
440
|
+
segmentBuffer = '';
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (character === '(')
|
|
444
|
+
segmentDepth += 1;
|
|
445
|
+
if (character === ')')
|
|
446
|
+
segmentDepth = Math.max(0, segmentDepth - 1);
|
|
447
|
+
segmentBuffer += character;
|
|
448
|
+
}
|
|
449
|
+
segments.push(segmentBuffer);
|
|
450
|
+
if (segments.length === 3 && segments[1]?.trim()) {
|
|
451
|
+
const initSource = segments[0].trim();
|
|
452
|
+
const updateSource = segments[2].trim();
|
|
453
|
+
const initHooks = [];
|
|
454
|
+
const updateHooks = [];
|
|
455
|
+
const initLooksLikeDeclaration = /^(?:const\s+)?(?:(?:unsigned|signed)\s+)?(?:(?:short|long long|long)\s+)?(?:int|float|double|bool|char)\b/.test(initSource);
|
|
456
|
+
for (const [name, variable] of currentFunctionVariables) {
|
|
457
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
458
|
+
const mutationPattern = new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b|\\b${escapedName}\\s*(?:[+\\-*/%]?=|\\+\\+|--)`);
|
|
459
|
+
if (!initLooksLikeDeclaration && mutationPattern.test(initSource)) {
|
|
460
|
+
initHooks.push(`${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name})`);
|
|
461
|
+
}
|
|
462
|
+
if (mutationPattern.test(updateSource)) {
|
|
463
|
+
updateHooks.push(`${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name})`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const instrumentedInit = initHooks.length && initSource
|
|
467
|
+
? `(${initSource}, ${initHooks.join(', ')})`
|
|
468
|
+
: segments[0];
|
|
469
|
+
const instrumentedUpdate = updateHooks.length && updateSource
|
|
470
|
+
? `(${updateSource}, ${updateHooks.join(', ')})`
|
|
471
|
+
: segments[2];
|
|
472
|
+
rewrittenLine =
|
|
473
|
+
line.slice(0, openIndex + 1) +
|
|
474
|
+
`${instrumentedInit}; (__wasm_idle_debug_line(${currentFunctionId}, ${index + 1}), (${segments[1].trim()})); ${instrumentedUpdate}` +
|
|
475
|
+
line.slice(closeIndex);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
rewrittenLine =
|
|
480
|
+
line.slice(0, openIndex + 1) +
|
|
481
|
+
`(__wasm_idle_debug_line(${currentFunctionId}, ${index + 1}), (${conditionSource.trim()}))` +
|
|
482
|
+
line.slice(closeIndex);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
instrumented.push(...leadingInstrumentation);
|
|
488
|
+
instrumented.push(rewrittenLine);
|
|
489
|
+
instrumented.push(...trailingInstrumentation);
|
|
490
|
+
const startsInlineFunctionBody = functionDepth === 0 &&
|
|
491
|
+
normalized.includes('(') &&
|
|
492
|
+
normalized.includes(')') &&
|
|
493
|
+
normalized.includes('{') &&
|
|
494
|
+
!/^(if|for|while|switch|catch)\b/.test(normalized) &&
|
|
495
|
+
!/^(class|struct|namespace|enum|union)\b/.test(normalized);
|
|
496
|
+
const startsPendingFunctionBody = functionDepth === 0 && !!pendingFunctionHeader && normalized === '{';
|
|
497
|
+
braceDepth += (analysisLine.match(/{/g) || []).length;
|
|
498
|
+
braceDepth -= (analysisLine.match(/}/g) || []).length;
|
|
499
|
+
if (startsInlineFunctionBody || startsPendingFunctionBody) {
|
|
500
|
+
functionDepth = braceDepth;
|
|
501
|
+
currentFunctionId = nextFunctionId++;
|
|
502
|
+
let functionName = 'anonymous';
|
|
503
|
+
if (startsInlineFunctionBody) {
|
|
504
|
+
const beforeParen = normalized.slice(0, normalized.indexOf('(')).trim();
|
|
505
|
+
functionName = beforeParen.split(/\s+/).pop() || functionName;
|
|
506
|
+
}
|
|
507
|
+
else if (pendingFunctionHeader) {
|
|
508
|
+
functionName = pendingFunctionHeader.functionName || functionName;
|
|
509
|
+
}
|
|
510
|
+
this.debugFunctionMetadata[currentFunctionId] = functionName;
|
|
511
|
+
nextVariableSlot = 1;
|
|
512
|
+
currentFunctionVariables = new Map();
|
|
513
|
+
currentFunctionContainers = new Map();
|
|
514
|
+
instrumented.push(`${indent} __wasm_idle_debug_enter(${currentFunctionId});`);
|
|
515
|
+
const parameterSource = startsInlineFunctionBody
|
|
516
|
+
? normalized.slice(normalized.indexOf('(') + 1, normalized.lastIndexOf(')'))
|
|
517
|
+
: pendingFunctionHeader?.parameters || '';
|
|
518
|
+
for (const parameter of parameterSource
|
|
519
|
+
.split(',')
|
|
520
|
+
.map((value) => value.trim())
|
|
521
|
+
.filter(Boolean)) {
|
|
522
|
+
const cleaned = parameter.split('=')[0]?.trim() || '';
|
|
523
|
+
const containerParameterMatch = cleaned.match(/^(?:const\s+)?(?:(?:std::)?(vector|set|map)\s*<.+>)\s*&?\s*([A-Za-z_]\w*)\s*$/);
|
|
524
|
+
if (containerParameterMatch) {
|
|
525
|
+
const slot = nextVariableSlot++;
|
|
526
|
+
const container = containerParameterMatch[1];
|
|
527
|
+
const name = containerParameterMatch[2];
|
|
528
|
+
currentFunctionContainers.set(name, {
|
|
529
|
+
slot,
|
|
530
|
+
container,
|
|
531
|
+
fromLine: index + 1,
|
|
532
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
533
|
+
});
|
|
534
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
535
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
536
|
+
{
|
|
537
|
+
slot,
|
|
538
|
+
name,
|
|
539
|
+
kind: 'text',
|
|
540
|
+
fromLine: index + 1,
|
|
541
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
542
|
+
}
|
|
543
|
+
];
|
|
544
|
+
instrumented.push(`${indent} __wasm_idle_debug_emit_${container}(${currentFunctionId}, ${slot}, ${name});`);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const parameterArrayMatch = cleaned.match(/([A-Za-z_]\w*)\s*\[(\d+)\]\s*$/);
|
|
548
|
+
if (parameterArrayMatch && /\b(int|float|double|bool|char)\b/.test(cleaned)) {
|
|
549
|
+
const slot = nextVariableSlot++;
|
|
550
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
551
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
552
|
+
{
|
|
553
|
+
slot,
|
|
554
|
+
name: parameterArrayMatch[1],
|
|
555
|
+
kind: 'array',
|
|
556
|
+
elementKind: (cleaned.match(/\b(int|float|double|bool|char)\b/)?.[1] || 'int'),
|
|
557
|
+
length: Number(parameterArrayMatch[2]),
|
|
558
|
+
fromLine: index + 1,
|
|
559
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
560
|
+
}
|
|
561
|
+
];
|
|
562
|
+
instrumented.push(`${indent} __wasm_idle_debug_value_addr(${currentFunctionId}, ${slot}, (int)((unsigned long long)(${parameterArrayMatch[1]})));`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (/[*&\[]/.test(cleaned))
|
|
566
|
+
continue;
|
|
567
|
+
const nameMatch = cleaned.match(/([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*$/);
|
|
568
|
+
if (!nameMatch)
|
|
569
|
+
continue;
|
|
570
|
+
const name = nameMatch[1];
|
|
571
|
+
const kind = /\bbool\b/.test(cleaned) ? 'bool' : /\b(?:int|float|double|char|short|long)\b/.test(cleaned)
|
|
572
|
+
? 'number'
|
|
573
|
+
: '';
|
|
574
|
+
if (!kind)
|
|
575
|
+
continue;
|
|
576
|
+
const slot = nextVariableSlot++;
|
|
577
|
+
currentFunctionVariables.set(name, {
|
|
578
|
+
slot,
|
|
579
|
+
kind,
|
|
580
|
+
fromLine: index + 1,
|
|
581
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
582
|
+
});
|
|
583
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
584
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
585
|
+
{
|
|
586
|
+
slot,
|
|
587
|
+
name,
|
|
588
|
+
kind,
|
|
589
|
+
fromLine: index + 1,
|
|
590
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
591
|
+
}
|
|
592
|
+
];
|
|
593
|
+
instrumented.push(`${indent} ${kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${slot}, ${name});`);
|
|
594
|
+
}
|
|
595
|
+
pendingFunctionHeader = undefined;
|
|
596
|
+
}
|
|
597
|
+
else if (functionDepth === 0 &&
|
|
598
|
+
normalized.includes('(') &&
|
|
599
|
+
normalized.includes(')') &&
|
|
600
|
+
!normalized.includes('{') &&
|
|
601
|
+
!normalized.endsWith(';') &&
|
|
602
|
+
!/^(if|for|while|switch|catch)\b/.test(normalized) &&
|
|
603
|
+
!/^(class|struct|namespace|enum|union)\b/.test(normalized)) {
|
|
604
|
+
const beforeParen = normalized.slice(0, normalized.indexOf('(')).trim();
|
|
605
|
+
pendingFunctionHeader = {
|
|
606
|
+
functionName: beforeParen.split(/\s+/).pop() || 'anonymous',
|
|
607
|
+
parameters: normalized.slice(normalized.indexOf('(') + 1, normalized.lastIndexOf(')'))
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
else if (normalized && normalized !== '{') {
|
|
611
|
+
pendingFunctionHeader = undefined;
|
|
612
|
+
}
|
|
613
|
+
if (functionDepth > 0 && braceDepth < functionDepth) {
|
|
614
|
+
functionDepth = 0;
|
|
615
|
+
currentFunctionId = 0;
|
|
616
|
+
currentFunctionVariables = new Map();
|
|
617
|
+
currentFunctionContainers = new Map();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
source = instrumented.join('\n');
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
this.debugVariableMetadata = {};
|
|
624
|
+
}
|
|
625
|
+
const code = toUtf8(source);
|
|
108
626
|
await this.ready;
|
|
109
627
|
this.memfs.addFile(input, code);
|
|
110
628
|
const clang = await this.getModule(clangUrl(this.path));
|
|
111
|
-
|
|
629
|
+
const compilerArgs = [
|
|
630
|
+
'-cc1',
|
|
631
|
+
'-emit-obj',
|
|
632
|
+
...clangCommonArgs,
|
|
633
|
+
'-O' + opt,
|
|
634
|
+
'-o',
|
|
635
|
+
obj,
|
|
636
|
+
clangLanguageStandardArg,
|
|
637
|
+
'-x',
|
|
638
|
+
'c++',
|
|
639
|
+
input,
|
|
640
|
+
...(options.args || [])
|
|
641
|
+
];
|
|
642
|
+
this.trace(`compile ${input} -> ${obj}`);
|
|
643
|
+
return await this.run(clang, true, 'clang', ...compilerArgs);
|
|
112
644
|
}
|
|
113
|
-
async link(obj, wasm) {
|
|
645
|
+
async link(obj, wasm, debug = false) {
|
|
114
646
|
const stackSize = 1024 * 1024;
|
|
115
647
|
const libdir = 'lib/wasm32-wasi';
|
|
116
648
|
const crt1 = `${libdir}/crt1.o`;
|
|
117
649
|
await this.ready;
|
|
118
650
|
const lld = await this.getModule(lldUrl(this.path));
|
|
651
|
+
this.trace(`link ${obj} -> ${wasm}`);
|
|
119
652
|
return await this.run(lld, this.log, 'wasm-ld', '--no-threads', '--export-dynamic', // TODO required?
|
|
120
|
-
'-z', `stack-size=${stackSize}`, `-L${libdir}`, crt1, obj, '-lc', '-lc++', '-lc++abi', '-lm', `-Llib/clang/8.0.1/lib/wasi`, '-lclang_rt.builtins-wasm32', '-o', wasm);
|
|
653
|
+
...(debug ? ['--allow-undefined'] : []), '-z', `stack-size=${stackSize}`, `-L${libdir}`, crt1, obj, '-lc', '-lc++', '-lc++abi', '-lm', `-Llib/clang/8.0.1/lib/wasi`, '-lclang_rt.builtins-wasm32', '-o', wasm);
|
|
121
654
|
}
|
|
122
655
|
async run(module, out, ...args) {
|
|
123
656
|
this.memfs.out = out;
|
|
124
657
|
this.hostLog(`${args.join(' ')}\n`);
|
|
658
|
+
this.trace(`run ${args.join(' ')}`);
|
|
125
659
|
const start = +new Date();
|
|
126
660
|
const app = new App(module, this.memfs, args[0], ...args.slice(1));
|
|
661
|
+
app.trace = (message) => this.trace(message);
|
|
662
|
+
app.debugSession = {
|
|
663
|
+
buffer: this.debugBuffer,
|
|
664
|
+
interruptBuffer: this.debugInterruptBuffer,
|
|
665
|
+
breakpoints: new Set(this.debugBreakpoints),
|
|
666
|
+
pauseOnEntry: this.debugPauseOnEntry,
|
|
667
|
+
stepArmed: this.debugPauseOnEntry,
|
|
668
|
+
nextLineArmed: false,
|
|
669
|
+
stepOutArmed: false,
|
|
670
|
+
callDepth: 0,
|
|
671
|
+
stepOutDepth: 0,
|
|
672
|
+
currentFunctionId: 0,
|
|
673
|
+
currentLine: 0,
|
|
674
|
+
resumeSkipActive: false,
|
|
675
|
+
resumeSkipFunctionId: 0,
|
|
676
|
+
resumeSkipLine: 0,
|
|
677
|
+
nextLineFunctionId: 0,
|
|
678
|
+
nextLineLine: 0,
|
|
679
|
+
variableMetadata: this.debugVariableMetadata,
|
|
680
|
+
functionMetadata: this.debugFunctionMetadata,
|
|
681
|
+
frames: [],
|
|
682
|
+
onPause: (event) => this.onDebugEvent?.(event)
|
|
683
|
+
};
|
|
127
684
|
const instantiate = +new Date();
|
|
128
685
|
const stillRunning = await app.run();
|
|
129
686
|
const end = +new Date();
|
|
@@ -133,16 +690,35 @@ export default class Clang {
|
|
|
133
690
|
this.stdout(`${green}(${start - instantiate}ms/${end - instantiate}ms)${normal}\n`);
|
|
134
691
|
return stillRunning ? app : null;
|
|
135
692
|
}
|
|
136
|
-
async compileLink(code,
|
|
693
|
+
async compileLink(code, options = {}) {
|
|
694
|
+
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
137
695
|
const input = `test.cc`, obj = `test.o`, wasm = `test.wasm`;
|
|
138
|
-
|
|
696
|
+
this.beginTrace(debug);
|
|
697
|
+
this.debugBreakpoints = new Set(debug ? breakpoints : []);
|
|
698
|
+
this.debugPauseOnEntry = debug && pauseOnEntry;
|
|
699
|
+
this.debugBuffer = debugBuffer;
|
|
700
|
+
this.debugInterruptBuffer = interruptBuffer;
|
|
701
|
+
const buildKey = JSON.stringify({ code, args, debug });
|
|
702
|
+
if (this.lastBuildKey === buildKey) {
|
|
703
|
+
this.trace(`reuse ${wasm}`);
|
|
139
704
|
return this.wasm;
|
|
140
|
-
|
|
141
|
-
await this.
|
|
142
|
-
this.
|
|
143
|
-
|
|
705
|
+
}
|
|
706
|
+
await this.compile({ input, code, obj, args, debug });
|
|
707
|
+
await this.link(obj, wasm, debug);
|
|
708
|
+
this.lastBuildKey = buildKey;
|
|
709
|
+
const wasmBytes = Uint8Array.from(this.memfs.getFileContents(wasm));
|
|
710
|
+
return (this.wasm = await this.hostLogAsync(`Compiling ${wasm}`, WebAssembly.compile(wasmBytes)));
|
|
144
711
|
}
|
|
145
|
-
async compileLinkRun(code,
|
|
146
|
-
|
|
712
|
+
async compileLinkRun(code, options = {}) {
|
|
713
|
+
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
714
|
+
this.debug = debug;
|
|
715
|
+
return await this.run(await this.compileLink(code, {
|
|
716
|
+
args,
|
|
717
|
+
debug,
|
|
718
|
+
breakpoints,
|
|
719
|
+
pauseOnEntry,
|
|
720
|
+
debugBuffer,
|
|
721
|
+
interruptBuffer
|
|
722
|
+
}), true, `test.wasm`);
|
|
147
723
|
}
|
|
148
724
|
}
|