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.
Files changed (54) hide show
  1. package/dist/clang/app.d.ts +44 -0
  2. package/dist/clang/app.js +229 -2
  3. package/dist/clang/index.d.ts +40 -9
  4. package/dist/clang/index.js +591 -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 +182 -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 +38 -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,563 @@ 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 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
- return await this.run(clang, true, 'clang', '-cc1', '-emit-obj', ...clangCommonArgs, '-O' + opt, '-o', obj, '-std=c++17', '-x', 'c++', input, ...(options.args || []));
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, { args = [] } = {}) {
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
- if (this.lastCode === code)
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
- 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))));
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, { args = [] } = {}) {
146
- return await this.run(await this.compileLink(code, { args }), true, `test.wasm`);
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
  }