wasm-idle 0.2.5 → 0.3.1
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 +270 -2
- package/dist/clang/index.d.ts +40 -9
- package/dist/clang/index.js +603 -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 +262 -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 +39 -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,575 @@ 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 arrayDimensions = [];
|
|
310
|
+
for (const match of declarator.matchAll(/\[(\d+)\]/g)) {
|
|
311
|
+
arrayDimensions.push(Number(match[1]));
|
|
312
|
+
}
|
|
313
|
+
const arrayName = declarator.match(/([A-Za-z_]\w*)\s*(?=\[\d+\])/);
|
|
314
|
+
if (arrayDimensions.length && arrayName) {
|
|
315
|
+
const slot = nextVariableSlot++;
|
|
316
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
317
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
318
|
+
{
|
|
319
|
+
slot,
|
|
320
|
+
name: arrayName[1],
|
|
321
|
+
kind: 'array',
|
|
322
|
+
elementKind: declarationMatch[1],
|
|
323
|
+
length: arrayDimensions[0],
|
|
324
|
+
dimensions: arrayDimensions,
|
|
325
|
+
fromLine: index + 1,
|
|
326
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
327
|
+
}
|
|
328
|
+
];
|
|
329
|
+
trailingInstrumentation.push(`${indent}__wasm_idle_debug_value_addr(${currentFunctionId}, ${slot}, (int)((unsigned long long)(${arrayName[1]})));`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (/[*&]/.test(declarator))
|
|
333
|
+
continue;
|
|
334
|
+
const name = declarator.match(/([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?$/)?.[1];
|
|
335
|
+
if (!name)
|
|
336
|
+
continue;
|
|
337
|
+
if (!currentFunctionVariables.has(name)) {
|
|
338
|
+
const slot = nextVariableSlot++;
|
|
339
|
+
currentFunctionVariables.set(name, {
|
|
340
|
+
slot,
|
|
341
|
+
kind,
|
|
342
|
+
fromLine: index + 1,
|
|
343
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
344
|
+
});
|
|
345
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
346
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
347
|
+
{
|
|
348
|
+
slot,
|
|
349
|
+
name,
|
|
350
|
+
kind,
|
|
351
|
+
fromLine: index + 1,
|
|
352
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
353
|
+
}
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
if (declaration.includes('=')) {
|
|
357
|
+
const variable = currentFunctionVariables.get(name);
|
|
358
|
+
if (variable) {
|
|
359
|
+
trailingInstrumentation.push(`${indent}${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name});`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
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*=/);
|
|
365
|
+
if (forDeclarationMatch && currentFunctionId) {
|
|
366
|
+
const kind = forDeclarationMatch[1] === 'bool' ? 'bool' : 'number';
|
|
367
|
+
const name = forDeclarationMatch[2];
|
|
368
|
+
if (!currentFunctionVariables.has(name)) {
|
|
369
|
+
const slot = nextVariableSlot++;
|
|
370
|
+
currentFunctionVariables.set(name, {
|
|
371
|
+
slot,
|
|
372
|
+
kind,
|
|
373
|
+
fromLine: index + 1,
|
|
374
|
+
toLine: index + 1
|
|
375
|
+
});
|
|
376
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
377
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
378
|
+
{
|
|
379
|
+
slot,
|
|
380
|
+
name,
|
|
381
|
+
kind,
|
|
382
|
+
fromLine: index + 1,
|
|
383
|
+
toLine: index + 1
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!controlHeaderWithoutInlineBlock) {
|
|
389
|
+
for (const [name, container] of currentFunctionContainers) {
|
|
390
|
+
if (declaredContainerNames.has(name))
|
|
391
|
+
continue;
|
|
392
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
393
|
+
if (!new RegExp(`\\b${escapedName}\\b`).test(normalized))
|
|
394
|
+
continue;
|
|
395
|
+
trailingInstrumentation.push(`${indent}__wasm_idle_debug_emit_${container.container}(${currentFunctionId}, ${container.slot}, ${name});`);
|
|
396
|
+
}
|
|
397
|
+
for (const [name, variable] of currentFunctionVariables) {
|
|
398
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
399
|
+
if (normalized.startsWith('for') && variable.toLine === index + 1)
|
|
400
|
+
continue;
|
|
401
|
+
if (new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b`).test(normalized) ||
|
|
402
|
+
new RegExp(`\\b${escapedName}\\s*(?:[+\\-*/%]?=|\\+\\+|--)`).test(normalized) ||
|
|
403
|
+
new RegExp(`&\\s*${escapedName}\\b`).test(normalized)) {
|
|
404
|
+
trailingInstrumentation.push(`${indent}${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name});`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (/^return\b/.test(normalized))
|
|
409
|
+
leadingInstrumentation.push(`${indent}__wasm_idle_debug_leave(${currentFunctionId});`);
|
|
410
|
+
}
|
|
411
|
+
if (functionDepth > 0 && braceDepth === functionDepth && normalized === '}') {
|
|
412
|
+
leadingInstrumentation.push(`${indent}__wasm_idle_debug_leave(${currentFunctionId});`);
|
|
413
|
+
}
|
|
414
|
+
if (inFunctionBody &&
|
|
415
|
+
currentFunctionId &&
|
|
416
|
+
(/^(while|if)\s*\(/.test(normalized) || /^for\s*\(/.test(normalized))) {
|
|
417
|
+
const keywordMatch = normalized.match(/^(while|if|for)\b/);
|
|
418
|
+
const keyword = keywordMatch?.[1];
|
|
419
|
+
const keywordIndex = line.indexOf(keyword || '');
|
|
420
|
+
const openIndex = keywordIndex >= 0 ? line.indexOf('(', keywordIndex) : -1;
|
|
421
|
+
if (openIndex >= 0) {
|
|
422
|
+
let closeIndex = -1;
|
|
423
|
+
let parenDepth = 0;
|
|
424
|
+
for (let cursor = openIndex; cursor < line.length; cursor += 1) {
|
|
425
|
+
const character = line[cursor];
|
|
426
|
+
if (character === '(')
|
|
427
|
+
parenDepth += 1;
|
|
428
|
+
if (character === ')') {
|
|
429
|
+
parenDepth -= 1;
|
|
430
|
+
if (parenDepth === 0) {
|
|
431
|
+
closeIndex = cursor;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (closeIndex > openIndex) {
|
|
437
|
+
const conditionSource = line.slice(openIndex + 1, closeIndex);
|
|
438
|
+
if (keyword === 'for') {
|
|
439
|
+
const segments = [];
|
|
440
|
+
let segmentBuffer = '';
|
|
441
|
+
let segmentDepth = 0;
|
|
442
|
+
for (const character of conditionSource) {
|
|
443
|
+
if (character === ';' && segmentDepth === 0) {
|
|
444
|
+
segments.push(segmentBuffer);
|
|
445
|
+
segmentBuffer = '';
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (character === '(')
|
|
449
|
+
segmentDepth += 1;
|
|
450
|
+
if (character === ')')
|
|
451
|
+
segmentDepth = Math.max(0, segmentDepth - 1);
|
|
452
|
+
segmentBuffer += character;
|
|
453
|
+
}
|
|
454
|
+
segments.push(segmentBuffer);
|
|
455
|
+
if (segments.length === 3 && segments[1]?.trim()) {
|
|
456
|
+
const initSource = segments[0].trim();
|
|
457
|
+
const updateSource = segments[2].trim();
|
|
458
|
+
const initHooks = [];
|
|
459
|
+
const updateHooks = [];
|
|
460
|
+
const initLooksLikeDeclaration = /^(?:const\s+)?(?:(?:unsigned|signed)\s+)?(?:(?:short|long long|long)\s+)?(?:int|float|double|bool|char)\b/.test(initSource);
|
|
461
|
+
for (const [name, variable] of currentFunctionVariables) {
|
|
462
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
463
|
+
const mutationPattern = new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b|\\b${escapedName}\\s*(?:[+\\-*/%]?=|\\+\\+|--)`);
|
|
464
|
+
if (!initLooksLikeDeclaration && mutationPattern.test(initSource)) {
|
|
465
|
+
initHooks.push(`${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name})`);
|
|
466
|
+
}
|
|
467
|
+
if (mutationPattern.test(updateSource)) {
|
|
468
|
+
updateHooks.push(`${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name})`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const instrumentedInit = initHooks.length && initSource
|
|
472
|
+
? `(${initSource}, ${initHooks.join(', ')})`
|
|
473
|
+
: segments[0];
|
|
474
|
+
const instrumentedUpdate = updateHooks.length && updateSource
|
|
475
|
+
? `(${updateSource}, ${updateHooks.join(', ')})`
|
|
476
|
+
: segments[2];
|
|
477
|
+
rewrittenLine =
|
|
478
|
+
line.slice(0, openIndex + 1) +
|
|
479
|
+
`${instrumentedInit}; (__wasm_idle_debug_line(${currentFunctionId}, ${index + 1}), (${segments[1].trim()})); ${instrumentedUpdate}` +
|
|
480
|
+
line.slice(closeIndex);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
rewrittenLine =
|
|
485
|
+
line.slice(0, openIndex + 1) +
|
|
486
|
+
`(__wasm_idle_debug_line(${currentFunctionId}, ${index + 1}), (${conditionSource.trim()}))` +
|
|
487
|
+
line.slice(closeIndex);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
instrumented.push(...leadingInstrumentation);
|
|
493
|
+
instrumented.push(rewrittenLine);
|
|
494
|
+
instrumented.push(...trailingInstrumentation);
|
|
495
|
+
const startsInlineFunctionBody = functionDepth === 0 &&
|
|
496
|
+
normalized.includes('(') &&
|
|
497
|
+
normalized.includes(')') &&
|
|
498
|
+
normalized.includes('{') &&
|
|
499
|
+
!/^(if|for|while|switch|catch)\b/.test(normalized) &&
|
|
500
|
+
!/^(class|struct|namespace|enum|union)\b/.test(normalized);
|
|
501
|
+
const startsPendingFunctionBody = functionDepth === 0 && !!pendingFunctionHeader && normalized === '{';
|
|
502
|
+
braceDepth += (analysisLine.match(/{/g) || []).length;
|
|
503
|
+
braceDepth -= (analysisLine.match(/}/g) || []).length;
|
|
504
|
+
if (startsInlineFunctionBody || startsPendingFunctionBody) {
|
|
505
|
+
functionDepth = braceDepth;
|
|
506
|
+
currentFunctionId = nextFunctionId++;
|
|
507
|
+
let functionName = 'anonymous';
|
|
508
|
+
if (startsInlineFunctionBody) {
|
|
509
|
+
const beforeParen = normalized.slice(0, normalized.indexOf('(')).trim();
|
|
510
|
+
functionName = beforeParen.split(/\s+/).pop() || functionName;
|
|
511
|
+
}
|
|
512
|
+
else if (pendingFunctionHeader) {
|
|
513
|
+
functionName = pendingFunctionHeader.functionName || functionName;
|
|
514
|
+
}
|
|
515
|
+
this.debugFunctionMetadata[currentFunctionId] = functionName;
|
|
516
|
+
nextVariableSlot = 1;
|
|
517
|
+
currentFunctionVariables = new Map();
|
|
518
|
+
currentFunctionContainers = new Map();
|
|
519
|
+
instrumented.push(`${indent} __wasm_idle_debug_enter(${currentFunctionId});`);
|
|
520
|
+
const parameterSource = startsInlineFunctionBody
|
|
521
|
+
? normalized.slice(normalized.indexOf('(') + 1, normalized.lastIndexOf(')'))
|
|
522
|
+
: pendingFunctionHeader?.parameters || '';
|
|
523
|
+
for (const parameter of parameterSource
|
|
524
|
+
.split(',')
|
|
525
|
+
.map((value) => value.trim())
|
|
526
|
+
.filter(Boolean)) {
|
|
527
|
+
const cleaned = parameter.split('=')[0]?.trim() || '';
|
|
528
|
+
const containerParameterMatch = cleaned.match(/^(?:const\s+)?(?:(?:std::)?(vector|set|map)\s*<.+>)\s*&?\s*([A-Za-z_]\w*)\s*$/);
|
|
529
|
+
if (containerParameterMatch) {
|
|
530
|
+
const slot = nextVariableSlot++;
|
|
531
|
+
const container = containerParameterMatch[1];
|
|
532
|
+
const name = containerParameterMatch[2];
|
|
533
|
+
currentFunctionContainers.set(name, {
|
|
534
|
+
slot,
|
|
535
|
+
container,
|
|
536
|
+
fromLine: index + 1,
|
|
537
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
538
|
+
});
|
|
539
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
540
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
541
|
+
{
|
|
542
|
+
slot,
|
|
543
|
+
name,
|
|
544
|
+
kind: 'text',
|
|
545
|
+
fromLine: index + 1,
|
|
546
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
547
|
+
}
|
|
548
|
+
];
|
|
549
|
+
instrumented.push(`${indent} __wasm_idle_debug_emit_${container}(${currentFunctionId}, ${slot}, ${name});`);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const parameterArrayDimensions = [];
|
|
553
|
+
for (const match of cleaned.matchAll(/\[(\d+)\]/g)) {
|
|
554
|
+
parameterArrayDimensions.push(Number(match[1]));
|
|
555
|
+
}
|
|
556
|
+
const parameterArrayName = cleaned.match(/([A-Za-z_]\w*)\s*(?=\[\d+\])/);
|
|
557
|
+
if (parameterArrayDimensions.length &&
|
|
558
|
+
parameterArrayName &&
|
|
559
|
+
/\b(int|float|double|bool|char)\b/.test(cleaned)) {
|
|
560
|
+
const slot = nextVariableSlot++;
|
|
561
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
562
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
563
|
+
{
|
|
564
|
+
slot,
|
|
565
|
+
name: parameterArrayName[1],
|
|
566
|
+
kind: 'array',
|
|
567
|
+
elementKind: (cleaned.match(/\b(int|float|double|bool|char)\b/)?.[1] || 'int'),
|
|
568
|
+
length: parameterArrayDimensions[0],
|
|
569
|
+
dimensions: parameterArrayDimensions,
|
|
570
|
+
fromLine: index + 1,
|
|
571
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
572
|
+
}
|
|
573
|
+
];
|
|
574
|
+
instrumented.push(`${indent} __wasm_idle_debug_value_addr(${currentFunctionId}, ${slot}, (int)((unsigned long long)(${parameterArrayName[1]})));`);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (/[*&\[]/.test(cleaned))
|
|
578
|
+
continue;
|
|
579
|
+
const nameMatch = cleaned.match(/([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*$/);
|
|
580
|
+
if (!nameMatch)
|
|
581
|
+
continue;
|
|
582
|
+
const name = nameMatch[1];
|
|
583
|
+
const kind = /\bbool\b/.test(cleaned) ? 'bool' : /\b(?:int|float|double|char|short|long)\b/.test(cleaned)
|
|
584
|
+
? 'number'
|
|
585
|
+
: '';
|
|
586
|
+
if (!kind)
|
|
587
|
+
continue;
|
|
588
|
+
const slot = nextVariableSlot++;
|
|
589
|
+
currentFunctionVariables.set(name, {
|
|
590
|
+
slot,
|
|
591
|
+
kind,
|
|
592
|
+
fromLine: index + 1,
|
|
593
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
594
|
+
});
|
|
595
|
+
this.debugVariableMetadata[currentFunctionId] = [
|
|
596
|
+
...(this.debugVariableMetadata[currentFunctionId] || []),
|
|
597
|
+
{
|
|
598
|
+
slot,
|
|
599
|
+
name,
|
|
600
|
+
kind,
|
|
601
|
+
fromLine: index + 1,
|
|
602
|
+
toLine: Number.MAX_SAFE_INTEGER
|
|
603
|
+
}
|
|
604
|
+
];
|
|
605
|
+
instrumented.push(`${indent} ${kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${slot}, ${name});`);
|
|
606
|
+
}
|
|
607
|
+
pendingFunctionHeader = undefined;
|
|
608
|
+
}
|
|
609
|
+
else if (functionDepth === 0 &&
|
|
610
|
+
normalized.includes('(') &&
|
|
611
|
+
normalized.includes(')') &&
|
|
612
|
+
!normalized.includes('{') &&
|
|
613
|
+
!normalized.endsWith(';') &&
|
|
614
|
+
!/^(if|for|while|switch|catch)\b/.test(normalized) &&
|
|
615
|
+
!/^(class|struct|namespace|enum|union)\b/.test(normalized)) {
|
|
616
|
+
const beforeParen = normalized.slice(0, normalized.indexOf('(')).trim();
|
|
617
|
+
pendingFunctionHeader = {
|
|
618
|
+
functionName: beforeParen.split(/\s+/).pop() || 'anonymous',
|
|
619
|
+
parameters: normalized.slice(normalized.indexOf('(') + 1, normalized.lastIndexOf(')'))
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
else if (normalized && normalized !== '{') {
|
|
623
|
+
pendingFunctionHeader = undefined;
|
|
624
|
+
}
|
|
625
|
+
if (functionDepth > 0 && braceDepth < functionDepth) {
|
|
626
|
+
functionDepth = 0;
|
|
627
|
+
currentFunctionId = 0;
|
|
628
|
+
currentFunctionVariables = new Map();
|
|
629
|
+
currentFunctionContainers = new Map();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
source = instrumented.join('\n');
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
this.debugVariableMetadata = {};
|
|
636
|
+
}
|
|
637
|
+
const code = toUtf8(source);
|
|
108
638
|
await this.ready;
|
|
109
639
|
this.memfs.addFile(input, code);
|
|
110
640
|
const clang = await this.getModule(clangUrl(this.path));
|
|
111
|
-
|
|
641
|
+
const compilerArgs = [
|
|
642
|
+
'-cc1',
|
|
643
|
+
'-emit-obj',
|
|
644
|
+
...clangCommonArgs,
|
|
645
|
+
'-O' + opt,
|
|
646
|
+
'-o',
|
|
647
|
+
obj,
|
|
648
|
+
clangLanguageStandardArg,
|
|
649
|
+
'-x',
|
|
650
|
+
'c++',
|
|
651
|
+
input,
|
|
652
|
+
...(options.args || [])
|
|
653
|
+
];
|
|
654
|
+
this.trace(`compile ${input} -> ${obj}`);
|
|
655
|
+
return await this.run(clang, true, 'clang', ...compilerArgs);
|
|
112
656
|
}
|
|
113
|
-
async link(obj, wasm) {
|
|
657
|
+
async link(obj, wasm, debug = false) {
|
|
114
658
|
const stackSize = 1024 * 1024;
|
|
115
659
|
const libdir = 'lib/wasm32-wasi';
|
|
116
660
|
const crt1 = `${libdir}/crt1.o`;
|
|
117
661
|
await this.ready;
|
|
118
662
|
const lld = await this.getModule(lldUrl(this.path));
|
|
663
|
+
this.trace(`link ${obj} -> ${wasm}`);
|
|
119
664
|
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);
|
|
665
|
+
...(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
666
|
}
|
|
122
667
|
async run(module, out, ...args) {
|
|
123
668
|
this.memfs.out = out;
|
|
124
669
|
this.hostLog(`${args.join(' ')}\n`);
|
|
670
|
+
this.trace(`run ${args.join(' ')}`);
|
|
125
671
|
const start = +new Date();
|
|
126
672
|
const app = new App(module, this.memfs, args[0], ...args.slice(1));
|
|
673
|
+
app.trace = (message) => this.trace(message);
|
|
674
|
+
app.debugSession = {
|
|
675
|
+
buffer: this.debugBuffer,
|
|
676
|
+
interruptBuffer: this.debugInterruptBuffer,
|
|
677
|
+
breakpoints: new Set(this.debugBreakpoints),
|
|
678
|
+
pauseOnEntry: this.debugPauseOnEntry,
|
|
679
|
+
stepArmed: this.debugPauseOnEntry,
|
|
680
|
+
nextLineArmed: false,
|
|
681
|
+
stepOutArmed: false,
|
|
682
|
+
callDepth: 0,
|
|
683
|
+
stepOutDepth: 0,
|
|
684
|
+
currentFunctionId: 0,
|
|
685
|
+
currentLine: 0,
|
|
686
|
+
resumeSkipActive: false,
|
|
687
|
+
resumeSkipFunctionId: 0,
|
|
688
|
+
resumeSkipLine: 0,
|
|
689
|
+
nextLineFunctionId: 0,
|
|
690
|
+
nextLineLine: 0,
|
|
691
|
+
variableMetadata: this.debugVariableMetadata,
|
|
692
|
+
functionMetadata: this.debugFunctionMetadata,
|
|
693
|
+
frames: [],
|
|
694
|
+
onPause: (event) => this.onDebugEvent?.(event)
|
|
695
|
+
};
|
|
127
696
|
const instantiate = +new Date();
|
|
128
697
|
const stillRunning = await app.run();
|
|
129
698
|
const end = +new Date();
|
|
@@ -133,16 +702,35 @@ export default class Clang {
|
|
|
133
702
|
this.stdout(`${green}(${start - instantiate}ms/${end - instantiate}ms)${normal}\n`);
|
|
134
703
|
return stillRunning ? app : null;
|
|
135
704
|
}
|
|
136
|
-
async compileLink(code,
|
|
705
|
+
async compileLink(code, options = {}) {
|
|
706
|
+
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
137
707
|
const input = `test.cc`, obj = `test.o`, wasm = `test.wasm`;
|
|
138
|
-
|
|
708
|
+
this.beginTrace(debug);
|
|
709
|
+
this.debugBreakpoints = new Set(debug ? breakpoints : []);
|
|
710
|
+
this.debugPauseOnEntry = debug && pauseOnEntry;
|
|
711
|
+
this.debugBuffer = debugBuffer;
|
|
712
|
+
this.debugInterruptBuffer = interruptBuffer;
|
|
713
|
+
const buildKey = JSON.stringify({ code, args, debug });
|
|
714
|
+
if (this.lastBuildKey === buildKey) {
|
|
715
|
+
this.trace(`reuse ${wasm}`);
|
|
139
716
|
return this.wasm;
|
|
140
|
-
|
|
141
|
-
await this.
|
|
142
|
-
this.
|
|
143
|
-
|
|
717
|
+
}
|
|
718
|
+
await this.compile({ input, code, obj, args, debug });
|
|
719
|
+
await this.link(obj, wasm, debug);
|
|
720
|
+
this.lastBuildKey = buildKey;
|
|
721
|
+
const wasmBytes = Uint8Array.from(this.memfs.getFileContents(wasm));
|
|
722
|
+
return (this.wasm = await this.hostLogAsync(`Compiling ${wasm}`, WebAssembly.compile(wasmBytes)));
|
|
144
723
|
}
|
|
145
|
-
async compileLinkRun(code,
|
|
146
|
-
|
|
724
|
+
async compileLinkRun(code, options = {}) {
|
|
725
|
+
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
726
|
+
this.debug = debug;
|
|
727
|
+
return await this.run(await this.compileLink(code, {
|
|
728
|
+
args,
|
|
729
|
+
debug,
|
|
730
|
+
breakpoints,
|
|
731
|
+
pauseOnEntry,
|
|
732
|
+
debugBuffer,
|
|
733
|
+
interruptBuffer
|
|
734
|
+
}), true, `test.wasm`);
|
|
147
735
|
}
|
|
148
736
|
}
|