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.
Files changed (54) hide show
  1. package/dist/clang/app.d.ts +44 -0
  2. package/dist/clang/app.js +270 -2
  3. package/dist/clang/index.d.ts +40 -9
  4. package/dist/clang/index.js +603 -15
  5. package/dist/clang/memory/memFs.d.ts +3 -1
  6. package/dist/clang/memory/memFs.js +12 -1
  7. package/dist/clang/memory/memory.d.ts +8 -4
  8. package/dist/clang/memory/memory.js +14 -0
  9. package/dist/clang/memory/tar.d.ts +1 -1
  10. package/dist/clang/memory/tar.js +1 -1
  11. package/dist/clang/wasm.d.ts +1 -1
  12. package/dist/clang/wasm.js +13 -4
  13. package/dist/clangd/config.d.ts +18 -0
  14. package/dist/clangd/config.js +14 -0
  15. package/dist/clangd/jsonStream.d.ts +9 -0
  16. package/dist/clangd/jsonStream.js +48 -0
  17. package/dist/clangd/session.d.ts +14 -0
  18. package/dist/clangd/session.js +94 -0
  19. package/dist/clangd/worker.d.ts +1 -0
  20. package/dist/clangd/worker.js +130 -0
  21. package/dist/debug/editor/index.d.ts +1 -0
  22. package/dist/debug/editor/index.js +1 -0
  23. package/dist/debug/editor/monaco.d.ts +18 -0
  24. package/dist/debug/editor/monaco.js +105 -0
  25. package/dist/debug/expression.d.ts +2 -0
  26. package/dist/debug/expression.js +262 -0
  27. package/dist/debug/index.d.ts +5 -0
  28. package/dist/debug/index.js +4 -0
  29. package/dist/debug/language/cpp.d.ts +2 -0
  30. package/dist/debug/language/cpp.js +7 -0
  31. package/dist/debug/language/index.d.ts +2 -0
  32. package/dist/debug/language/index.js +1 -0
  33. package/dist/debug/language/types.d.ts +6 -0
  34. package/dist/debug/language/types.js +1 -0
  35. package/dist/debug/locals.d.ts +2 -0
  36. package/dist/debug/locals.js +8 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.js +1 -0
  39. package/dist/playground/clang.d.ts +8 -2
  40. package/dist/playground/clang.js +25 -4
  41. package/dist/playground/options.d.ts +39 -0
  42. package/dist/playground/options.js +1 -0
  43. package/dist/playground/python.d.ts +10 -2
  44. package/dist/playground/python.js +6 -3
  45. package/dist/playground/sandbox.d.ts +15 -2
  46. package/dist/playground/worker/clang.js +21 -4
  47. package/dist/playground/worker/python.js +196 -3
  48. package/dist/terminal/Terminal.svelte +35 -11
  49. package/dist/terminal/Terminal.svelte.d.ts +12 -9
  50. package/dist/terminal/index.d.ts +2 -0
  51. package/dist/terminal/theme.js +2 -2
  52. package/dist/terminal/types.d.ts +13 -0
  53. package/dist/terminal/types.js +1 -0
  54. package/package.json +42 -33
@@ -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
- lastCode = '';
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
- const code = toUtf8(options.code);
127
+ let source = options.code;
106
128
  const obj = options.obj;
107
- const opt = options.opt || '2';
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
- return await this.run(clang, true, 'clang', '-cc1', '-emit-obj', ...clangCommonArgs, '-O' + opt, '-o', obj, '-std=c++17', '-x', 'c++', input, ...(options.args || []));
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, { args = [] } = {}) {
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
- if (this.lastCode === code)
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
- await this.compile({ input, code, obj, args });
141
- await this.link(obj, wasm);
142
- this.lastCode = code;
143
- return (this.wasm = await this.hostLogAsync(`Compiling ${wasm}`, WebAssembly.compile(this.memfs.getFileContents(wasm))));
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, { args = [] } = {}) {
146
- return await this.run(await this.compileLink(code, { args }), true, `test.wasm`);
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
  }