warpo 3.1.3 → 3.2.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 (132) hide show
  1. package/assemblyscript/std/README.md +5 -0
  2. package/assemblyscript/std/assembly/array.ts +542 -0
  3. package/assemblyscript/std/assembly/arraybuffer.ts +72 -0
  4. package/assemblyscript/std/assembly/builtins.ts +2312 -0
  5. package/assemblyscript/std/assembly/compat.ts +2 -0
  6. package/assemblyscript/std/assembly/dataview.ts +144 -0
  7. package/assemblyscript/std/assembly/date.ts +383 -0
  8. package/assemblyscript/std/assembly/diagnostics.ts +11 -0
  9. package/assemblyscript/std/assembly/error.ts +39 -0
  10. package/assemblyscript/std/assembly/function.ts +44 -0
  11. package/assemblyscript/std/assembly/iterator.ts +24 -0
  12. package/assemblyscript/std/assembly/map.ts +304 -0
  13. package/assemblyscript/std/assembly/math.ts +3401 -0
  14. package/assemblyscript/std/assembly/memory.ts +110 -0
  15. package/assemblyscript/std/assembly/number.ts +388 -0
  16. package/assemblyscript/std/assembly/object.ts +30 -0
  17. package/assemblyscript/std/assembly/polyfills.ts +24 -0
  18. package/assemblyscript/std/assembly/process.ts +7 -0
  19. package/assemblyscript/std/assembly/promise.ts +172 -0
  20. package/assemblyscript/std/assembly/reference.ts +48 -0
  21. package/assemblyscript/std/assembly/regexp.ts +16 -0
  22. package/assemblyscript/std/assembly/rt/common.ts +79 -0
  23. package/assemblyscript/std/assembly/rt/index-incremental.ts +4 -0
  24. package/assemblyscript/std/assembly/rt/index-radical.ts +4 -0
  25. package/assemblyscript/std/assembly/rt/index.d.ts +37 -0
  26. package/assemblyscript/std/assembly/rt/itcms.ts +415 -0
  27. package/assemblyscript/std/assembly/rt/tcms.ts +276 -0
  28. package/assemblyscript/std/assembly/rt/tlsf.ts +564 -0
  29. package/assemblyscript/std/assembly/rt.ts +71 -0
  30. package/assemblyscript/std/assembly/set.ts +261 -0
  31. package/assemblyscript/std/assembly/shared/feature.ts +69 -0
  32. package/assemblyscript/std/assembly/shared/host.ts +12 -0
  33. package/assemblyscript/std/assembly/shared/runtime.ts +9 -0
  34. package/assemblyscript/std/assembly/shared/target.ts +9 -0
  35. package/assemblyscript/std/assembly/shared/tsconfig.json +9 -0
  36. package/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
  37. package/assemblyscript/std/assembly/staticarray.ts +446 -0
  38. package/assemblyscript/std/assembly/string.ts +892 -0
  39. package/assemblyscript/std/assembly/symbol.ts +61 -0
  40. package/assemblyscript/std/assembly/table.ts +15 -0
  41. package/assemblyscript/std/assembly/tsconfig.json +4 -0
  42. package/assemblyscript/std/assembly/tuple.ts +36 -0
  43. package/assemblyscript/std/assembly/typedarray.ts +2000 -0
  44. package/assemblyscript/std/assembly/uri.ts +17 -0
  45. package/assemblyscript/std/assembly/util/bytes.ts +90 -0
  46. package/assemblyscript/std/assembly/util/casemap.ts +264 -0
  47. package/assemblyscript/std/assembly/util/error.ts +54 -0
  48. package/assemblyscript/std/assembly/util/hash.ts +117 -0
  49. package/assemblyscript/std/assembly/util/math.ts +1922 -0
  50. package/assemblyscript/std/assembly/util/memory.ts +309 -0
  51. package/assemblyscript/std/assembly/util/number.ts +884 -0
  52. package/assemblyscript/std/assembly/util/sort.ts +282 -0
  53. package/assemblyscript/std/assembly/util/string.ts +1075 -0
  54. package/assemblyscript/std/assembly/util/uri.ts +251 -0
  55. package/assemblyscript/std/assembly/vector.ts +3 -0
  56. package/dist/common/leb128.d.ts +5 -0
  57. package/dist/common/leb128.js +17 -0
  58. package/dist/common/leb128.js.map +1 -0
  59. package/dist/debug_server/debugSession.d.ts +31 -1
  60. package/dist/debug_server/debugSession.js +477 -99
  61. package/dist/debug_server/debugSession.js.map +1 -1
  62. package/dist/debug_server/debugTrace.d.ts +2 -0
  63. package/dist/debug_server/debugTrace.js +38 -0
  64. package/dist/debug_server/debugTrace.js.map +1 -0
  65. package/dist/debug_server/debugger.d.ts +5 -1
  66. package/dist/debug_server/debuggerWasmModule.d.ts +9 -0
  67. package/dist/debug_server/debuggerWasmModule.js +182 -4
  68. package/dist/debug_server/debuggerWasmModule.js.map +1 -1
  69. package/dist/debug_server/nodeDebugger.d.ts +12 -1
  70. package/dist/debug_server/nodeDebugger.js +74 -28
  71. package/dist/debug_server/nodeDebugger.js.map +1 -1
  72. package/dist/debug_server/tests/debugSession.test.js +556 -22
  73. package/dist/debug_server/tests/debugSession.test.js.map +1 -1
  74. package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.d.ts +1 -0
  75. package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.js +12 -0
  76. package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.js.map +1 -0
  77. package/dist/debug_server/tests/testModule/debugger_closure_stepping.d.ts +1 -0
  78. package/dist/debug_server/tests/testModule/debugger_closure_stepping.js +12 -0
  79. package/dist/debug_server/tests/testModule/debugger_closure_stepping.js.map +1 -0
  80. package/dist/debug_server/tests/testModule/debugger_function_closure.d.ts +1 -0
  81. package/dist/debug_server/tests/testModule/debugger_function_closure.js +24 -0
  82. package/dist/debug_server/tests/testModule/debugger_function_closure.js.map +1 -0
  83. package/dist/debug_server/tests/testModule/debugger_import.case.d.ts +1 -0
  84. package/dist/debug_server/tests/testModule/debugger_import.case.js +9 -0
  85. package/dist/debug_server/tests/testModule/debugger_import.case.js.map +1 -0
  86. package/dist/debug_server/tests/testModule/debugger_import.d.ts +1 -0
  87. package/dist/debug_server/tests/testModule/debugger_import.js +6 -0
  88. package/dist/debug_server/tests/testModule/debugger_import.js.map +1 -0
  89. package/dist/debug_server/tests/testModule/debugger_pause.d.ts +1 -0
  90. package/dist/debug_server/tests/testModule/debugger_pause.js +9 -0
  91. package/dist/debug_server/tests/testModule/debugger_pause.js.map +1 -0
  92. package/dist/debug_server/tests/testModule/debugger_std_array.d.ts +1 -0
  93. package/dist/debug_server/tests/testModule/debugger_std_array.js +6 -0
  94. package/dist/debug_server/tests/testModule/debugger_std_array.js.map +1 -0
  95. package/dist/debug_server/tests/testModule/debugger_stepping.d.ts +3 -0
  96. package/dist/debug_server/tests/testModule/debugger_stepping.js +28 -0
  97. package/dist/debug_server/tests/testModule/debugger_stepping.js.map +1 -0
  98. package/dist/debug_server/tests/testModule/debugger_string_stepping.d.ts +1 -0
  99. package/dist/debug_server/tests/testModule/debugger_string_stepping.js +9 -0
  100. package/dist/debug_server/tests/testModule/debugger_string_stepping.js.map +1 -0
  101. package/dist/debug_server/tests/testModule/debugger_typed_array.d.ts +1 -0
  102. package/dist/debug_server/tests/testModule/debugger_typed_array.js +52 -0
  103. package/dist/debug_server/tests/testModule/debugger_typed_array.js.map +1 -0
  104. package/dist/debug_server/tests/testModule/debugger_utf8_const_str.d.ts +1 -0
  105. package/dist/debug_server/tests/testModule/debugger_utf8_const_str.js +10 -0
  106. package/dist/debug_server/tests/testModule/debugger_utf8_const_str.js.map +1 -0
  107. package/dist/dwarf/dwarfParser.js +6 -13
  108. package/dist/dwarf/dwarfParser.js.map +1 -1
  109. package/dist/dwarf/functionDebugInfo.d.ts +3 -0
  110. package/dist/dwarf/functionDebugInfo.js +24 -0
  111. package/dist/dwarf/functionDebugInfo.js.map +1 -1
  112. package/package.json +7 -7
  113. package/warpo_extension/std/tsconfig.json +4 -0
  114. package/warpo_extension/std/warpo/ffi.ts +16 -0
  115. package/warpo_extension/std/warpo/test/comparison.ts +122 -0
  116. package/warpo_extension/std/warpo/test/env.ts +33 -0
  117. package/warpo_extension/std/warpo/test/expect.ts +101 -0
  118. package/warpo_extension/std/warpo/test/formatPrint.ts +99 -0
  119. package/warpo_extension/std/warpo/test/implement.ts +41 -0
  120. package/warpo_extension/std/warpo/test/index.d.ts +0 -0
  121. package/warpo_extension/std/warpo/test/index.ts +39 -0
  122. package/warpo_extension/std/warpo/test/mockInstrument.ts +26 -0
  123. package/warpo_extension/std/warpo/utf8/const_str.ts +17 -0
  124. package/warpo_extension/std/wasi_snapshot_preview1/LICENSE +202 -0
  125. package/warpo_extension/std/wasi_snapshot_preview1/README.md +1 -0
  126. package/warpo_extension/std/wasi_snapshot_preview1/bindings/wasi_snapshot_preview1.ts +1731 -0
  127. package/warpo_extension/std/wasi_snapshot_preview1/wasi_console.ts +96 -0
  128. package/warpo_extension/std/wasi_snapshot_preview1/wasi_crypto.ts +8 -0
  129. package/warpo_extension/std/wasi_snapshot_preview1/wasi_date.ts +13 -0
  130. package/warpo_extension/std/wasi_snapshot_preview1/wasi_internal.ts +119 -0
  131. package/warpo_extension/std/wasi_snapshot_preview1/wasi_performance.ts +11 -0
  132. package/warpo_extension/std/wasi_snapshot_preview1/wasi_process.ts +191 -0
@@ -1,6 +1,6 @@
1
1
  // Copyright (C) 2025 wasm-ecosystem
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, Source, Scope, StackFrame, LoadedSourceEvent, OutputEvent, StoppedEvent, ContinuedEvent, TerminatedEvent, logger, } from "@vscode/debugadapter";
3
+ import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, BreakpointEvent, Source, Scope, StackFrame, LoadedSourceEvent, OutputEvent, StoppedEvent, ContinuedEvent, TerminatedEvent, logger, } from "@vscode/debugadapter";
4
4
  import assert from "node:assert/strict";
5
5
  import { existsSync } from "node:fs";
6
6
  import * as path from "node:path";
@@ -9,6 +9,14 @@ import { ARRAY_DATA_START_OFFSET, ARRAY_LENGTH_OFFSET, AS_STRING_CLASS_NAME, OBJ
9
9
  import { normalizeDebugPath } from "./debugPath.js";
10
10
  import { NodeDebugger } from "./nodeDebugger.js";
11
11
  import { loadConfig } from "../test_runner/config.js";
12
+ import { configureTrace, trace } from "./debugTrace.js";
13
+ var StepMode;
14
+ (function (StepMode) {
15
+ StepMode[StepMode["None"] = 0] = "None";
16
+ StepMode[StepMode["Into"] = 1] = "Into";
17
+ StepMode[StepMode["Over"] = 2] = "Over";
18
+ StepMode[StepMode["Out"] = 3] = "Out";
19
+ })(StepMode || (StepMode = {}));
12
20
  function formatUnknownError(error) {
13
21
  if (error instanceof Error) {
14
22
  return error.message;
@@ -18,6 +26,23 @@ function formatUnknownError(error) {
18
26
  }
19
27
  return "unknown error";
20
28
  }
29
+ const UTF8_CONST_STR_CLASS_NAME = "~lib/warpo/utf8/const_str/ConstStr";
30
+ function isDisplayableStringClass(typeName) {
31
+ return typeName === AS_STRING_CLASS_NAME || typeName === UTF8_CONST_STR_CLASS_NAME;
32
+ }
33
+ const TYPED_ARRAY_ELEMENT_TYPES = new Map([
34
+ ["~lib/typedarray/Int8Array", "i8"],
35
+ ["~lib/typedarray/Uint8Array", "u8"],
36
+ ["~lib/typedarray/Uint8ClampedArray", "u8"],
37
+ ["~lib/typedarray/Int16Array", "i16"],
38
+ ["~lib/typedarray/Uint16Array", "u16"],
39
+ ["~lib/typedarray/Int32Array", "i32"],
40
+ ["~lib/typedarray/Uint32Array", "u32"],
41
+ ["~lib/typedarray/Int64Array", "i64"],
42
+ ["~lib/typedarray/Uint64Array", "u64"],
43
+ ["~lib/typedarray/Float32Array", "f32"],
44
+ ["~lib/typedarray/Float64Array", "f64"],
45
+ ]);
21
46
  async function resolveUnitTestWasmPath(cwd) {
22
47
  const configPath = path.join(cwd, "as-test.config.js");
23
48
  let outputFolder = "coverage";
@@ -39,11 +64,15 @@ export class WarpoDebugSession extends LoggingDebugSession {
39
64
  nextVariablesReference = 1;
40
65
  loadedModule;
41
66
  runtime;
67
+ configurationDone = false;
42
68
  stoppedWasmBytecodeOffset;
43
69
  stoppedForTrap = false;
70
+ stepMode = StepMode.None;
71
+ stepStartSourceLocation;
44
72
  debugSessionLogging = false;
45
73
  log(msg) {
46
74
  logger.log(msg);
75
+ trace("debug-session", msg);
47
76
  if (this.debugSessionLogging) {
48
77
  this.sendEvent(new OutputEvent(`${msg}\n`, "stdout"));
49
78
  }
@@ -72,12 +101,24 @@ export class WarpoDebugSession extends LoggingDebugSession {
72
101
  }
73
102
  this.applyPendingBreakpointUpdates(runtime, () => {
74
103
  if (info.reason === "other") {
104
+ if (this.stepMode !== StepMode.None) {
105
+ void this.handleStepPause(runtime, info);
106
+ return;
107
+ }
75
108
  void runtime.resume().catch((error) => {
76
109
  const message = error instanceof Error ? error.message : "unknown error";
77
110
  this.log(`Failed to resume after breakpoint update: ${message}`);
78
111
  });
79
112
  return;
80
113
  }
114
+ if (info.reason === "breakpoint" || info.reason === "exception") {
115
+ this.handleNonStepPause(runtime, info);
116
+ return;
117
+ }
118
+ if (this.stepMode !== StepMode.None) {
119
+ void this.handleStepPause(runtime, info);
120
+ return;
121
+ }
81
122
  this.sendStoppedEvent(runtime, info);
82
123
  });
83
124
  }
@@ -88,33 +129,38 @@ export class WarpoDebugSession extends LoggingDebugSession {
88
129
  response.body.supportsBreakpointLocationsRequest = false;
89
130
  response.body.supportsLoadedSourcesRequest = true;
90
131
  response.body.supportsEvaluateForHovers = false;
132
+ response.body.supportsTerminateRequest = true;
133
+ response.body.supportsRestartRequest = true;
91
134
  this.sendResponse(response);
92
135
  this.sendEvent(new InitializedEvent());
93
136
  }
94
137
  setBreakPointsRequest(response, args) {
95
138
  const sourcePath = normalizeDebugPath(args.source.path || "");
96
139
  const clientLines = args.breakpoints || [];
97
- const bps = clientLines.map((bp) => {
140
+ const wasmModule = this.loadedModule;
141
+ this.log(`SetBreakpoints request: source=${sourcePath}, lines=${clientLines.map((breakpoint) => breakpoint.line).join(",") || "none"}`);
142
+ const bps = [];
143
+ for (const bp of clientLines) {
98
144
  const id = ++this.breakpointId;
99
- const info = {
145
+ bps.push({
100
146
  id,
101
147
  line: bp.line,
102
- verified: true,
148
+ verified: false,
103
149
  source: sourcePath,
104
- };
150
+ });
105
151
  this.log(`Breakpoint set: ${path.basename(sourcePath)}:${bp.line}`);
106
- return info;
107
- });
152
+ }
153
+ if (wasmModule !== undefined) {
154
+ for (const breakpoint of bps) {
155
+ breakpoint.verified = wasmModule.resolveBreakpointLocations(breakpoint).length > 0;
156
+ }
157
+ }
108
158
  this.requestedBreakpointsBySource.set(sourcePath, bps);
109
159
  this.pendingBreakpointSources.clear();
110
160
  for (const pendingSourcePath of this.requestedBreakpointsBySource.keys()) {
111
161
  this.pendingBreakpointSources.add(pendingSourcePath);
112
162
  }
113
- const breakpoints = bps.map((bp) => {
114
- const dbp = new Breakpoint(bp.verified, bp.line);
115
- dbp.id = bp.id;
116
- return dbp;
117
- });
163
+ const breakpoints = bps.map((bp) => this.toDebugProtocolBreakpoint(bp));
118
164
  response.body = { breakpoints };
119
165
  if (this.runtime?.isPaused() && this.loadedModule) {
120
166
  this.requestBreakpointUpdate(() => this.sendResponse(response));
@@ -125,12 +171,34 @@ export class WarpoDebugSession extends LoggingDebugSession {
125
171
  }
126
172
  }
127
173
  configurationDoneRequest(response, _args) {
174
+ this.configurationDone = true;
128
175
  super.configurationDoneRequest(response, _args);
129
176
  this.logAllBreakpoints();
177
+ this.maybeStartRuntime();
130
178
  }
131
179
  launchRequest(response, args) {
132
180
  void this.doLaunchRequest(response, args);
133
181
  }
182
+ restartRequest(response, _args) {
183
+ const runtime = this.runtime;
184
+ if (!runtime) {
185
+ this.sendErrorResponse(response, 1, "No active runtime to restart");
186
+ return;
187
+ }
188
+ this.log("Restart requested");
189
+ this.resetRuntimeState();
190
+ this.queueAllBreakpointUpdates();
191
+ void this.doRestartRequest(response, runtime);
192
+ }
193
+ async doRestartRequest(response, runtime) {
194
+ try {
195
+ await runtime.restart();
196
+ this.sendResponse(response);
197
+ }
198
+ catch (error) {
199
+ this.sendErrorResponse(response, 1, `Restart failed: ${formatUnknownError(error)}`);
200
+ }
201
+ }
134
202
  async doLaunchRequest(response, args) {
135
203
  try {
136
204
  this.debugSessionLogging = args.debugSessionLogging ?? false;
@@ -142,6 +210,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
142
210
  return;
143
211
  }
144
212
  const wasmFilePath = path.resolve(program);
213
+ configureTrace(args.debugSessionLogFile, cwd);
145
214
  this.log(`Launch requested for: ${program}`);
146
215
  if (launchType === "wasm file" || launchType === "unittest") {
147
216
  const runtimeName = args.runtime ?? "node";
@@ -156,6 +225,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
156
225
  runtime.onModuleLoad = (wasmModule) => {
157
226
  this.loadedModule = wasmModule;
158
227
  this.log(`Wasm module loaded: ${wasmModule.wasmFilePath} (reported as ${wasmModule.url}, scriptId: ${wasmModule.scriptId})`);
228
+ this.log(`Module load breakpoint state: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
159
229
  this.sendEvent(new LoadedSourceEvent("new", {
160
230
  name: path.basename(wasmModule.wasmFilePath),
161
231
  path: wasmModule.wasmFilePath,
@@ -164,16 +234,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
164
234
  name: path.basename(wasmModule.sourceMapFilePath),
165
235
  path: wasmModule.sourceMapFilePath,
166
236
  }));
167
- this.applyPendingBreakpointUpdates(runtime, () => {
168
- if (runtime.isPaused()) {
169
- void runtime.resume().catch((error) => {
170
- const message = error instanceof Error ? error.message : "unknown error";
171
- this.log(`Failed to resume after module load: ${message}`);
172
- });
173
- return;
174
- }
175
- runtime.finishModuleLoad();
176
- });
237
+ this.applyPendingBreakpointUpdates(runtime, () => this.maybeStartRuntime());
177
238
  };
178
239
  runtime.onPause = (info) => {
179
240
  this.handleRuntimePause(runtime, info);
@@ -188,7 +249,6 @@ export class WarpoDebugSession extends LoggingDebugSession {
188
249
  this.log(message);
189
250
  };
190
251
  this.disposeLoadedModule();
191
- this.runtime?.dispose();
192
252
  this.runtime = runtime;
193
253
  await runtime.launch(launchType === "unittest"
194
254
  ? {
@@ -236,9 +296,52 @@ export class WarpoDebugSession extends LoggingDebugSession {
236
296
  continueRequest(response, _args) {
237
297
  this.doContinueRequest(response);
238
298
  }
299
+ stepInRequest(response, _args) {
300
+ this.doStepRequest(response, StepMode.Into);
301
+ }
302
+ nextRequest(response, _args) {
303
+ this.doStepRequest(response, StepMode.Over);
304
+ }
305
+ pauseRequest(response, _args) {
306
+ const runtime = this.runtime;
307
+ if (!runtime) {
308
+ this.sendErrorResponse(response, 1, "No active runtime");
309
+ return;
310
+ }
311
+ if (runtime.isPaused()) {
312
+ this.sendResponse(response);
313
+ return;
314
+ }
315
+ this.cancelStep();
316
+ // StepMode.Into is used as a sentinel to reuse the step-pause handling path; no actual step-in is performed.
317
+ this.stepMode = StepMode.Into;
318
+ this.stepStartSourceLocation = undefined;
319
+ this.clearVariableContainers();
320
+ runtime.pause();
321
+ this.sendResponse(response);
322
+ }
323
+ terminateRequest(response, _args) {
324
+ const runtime = this.runtime;
325
+ if (runtime) {
326
+ void this.disposeRuntimeAndTerminate(runtime);
327
+ }
328
+ this.sendResponse(response);
329
+ }
330
+ stepOutRequest(response, _args) {
331
+ this.doStepRequest(response, StepMode.Out);
332
+ }
239
333
  disconnectRequest(response, _args) {
240
- this.runtime?.dispose();
334
+ void this.doDisconnectRequest(response);
335
+ }
336
+ async doDisconnectRequest(response) {
337
+ const runtime = this.runtime;
241
338
  this.runtime = undefined;
339
+ try {
340
+ await runtime?.dispose();
341
+ }
342
+ catch (error) {
343
+ this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
344
+ }
242
345
  this.resetRuntimeState();
243
346
  this.debugSessionLogging = false;
244
347
  this.dispose();
@@ -254,13 +357,10 @@ export class WarpoDebugSession extends LoggingDebugSession {
254
357
  const id = index + 1;
255
358
  this.pausedStackFramesById.set(id, { ...frame, frameIndex: index });
256
359
  const sourceLocation = this.resolveSourceLocation(frame.wasmBytecodeOffset);
257
- const sourceLabel = sourceLocation
258
- ? `${path.basename(sourceLocation.sourcePath)}:${sourceLocation.sourceLine}`
259
- : "unknown";
360
+ const sourcePath = sourceLocation ? this.loadedModule?.patchSourcePath(sourceLocation.sourcePath) : undefined;
361
+ const sourceLabel = sourceLocation && sourcePath ? `${path.basename(sourcePath)}:${sourceLocation.sourceLine}` : "unknown";
260
362
  this.log(` frame ${id}: index=${index}, name=${frame.functionName || "wasm"}, offset=${frame.wasmBytecodeOffset ?? "unknown"}, source=${sourceLabel}`);
261
- const source = sourceLocation
262
- ? new Source(path.basename(sourceLocation.sourcePath), sourceLocation.sourcePath)
263
- : undefined;
363
+ const source = sourcePath ? new Source(path.basename(sourcePath), sourcePath) : undefined;
264
364
  return new StackFrame(id, frame.functionName || "wasm", source, sourceLocation?.sourceLine ?? 0, 1);
265
365
  });
266
366
  const startFrame = args.startFrame ?? 0;
@@ -301,6 +401,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
301
401
  response.body = { variables: await this.decodeObjectAtAddress(variableContainer.address) };
302
402
  break;
303
403
  }
404
+ case "variables": {
405
+ response.body = { variables: variableContainer.variables };
406
+ break;
407
+ }
408
+ case "closure-scopes": {
409
+ response.body = {
410
+ variables: await this.resolveClosureScopeVariables(variableContainer.functionName, variableContainer.envAddress, variableContainer.scopeLevels),
411
+ };
412
+ break;
413
+ }
304
414
  case "map-entry": {
305
415
  response.body = {
306
416
  variables: [variableContainer.key, variableContainer.value].map((variable) => this.toDebugProtocolVariable(variable)),
@@ -366,24 +476,78 @@ export class WarpoDebugSession extends LoggingDebugSession {
366
476
  }
367
477
  const sourceVariables = sourceVariableScope.variables;
368
478
  const rawWasmValues = await this.resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime);
369
- const closureTupleValuesByLevel = await this.readClosureTupleChain(sourceVariableScope.rootClosureEnvLocalIndex, runtimeVariablesByIndex);
370
- const scopeTupleLevel = sourceVariableScope.tupleLevel ?? 0;
371
- const rawLocalValues = sourceVariables.map((variable) => {
372
- if (variable.kind === "closure") {
373
- const elementIndex = variable.fieldOffset / SMALL_TUPLE_SLOT_SIZE;
374
- const levelValues = closureTupleValuesByLevel.get(scopeTupleLevel);
375
- const tupleRawValue = levelValues?.get(elementIndex);
376
- if (tupleRawValue === undefined) {
377
- return { name: variable.name, value: "<unavailable>", typeName: variable.typeName };
479
+ const firstTupleAddress = this.resolveClosureTupleAddress(sourceVariableScope.rootClosureEnvLocalIndex, runtimeVariablesByIndex);
480
+ const decodedScopes = await this.decodeScopeChainVariables([sourceVariableScope], firstTupleAddress, rawWasmValues);
481
+ return decodedScopes[0] ?? [];
482
+ }
483
+ async decodeScopeChainVariables(scopeLevels, firstTupleAddress, rawWasmValues) {
484
+ const closureTupleValuesByLevel = new Map();
485
+ const visitedAddresses = new Set();
486
+ if (firstTupleAddress !== undefined && Number.isInteger(firstTupleAddress) && firstTupleAddress > 0) {
487
+ let currentAddress = firstTupleAddress;
488
+ let tupleLevel = 0;
489
+ while (!visitedAddresses.has(currentAddress)) {
490
+ visitedAddresses.add(currentAddress);
491
+ const tupleValues = await this.readTupleElementValues(currentAddress);
492
+ const levelValues = new Map();
493
+ for (const [index, value] of tupleValues.entries()) {
494
+ levelValues.set(index, value.value);
495
+ }
496
+ closureTupleValuesByLevel.set(tupleLevel, levelValues);
497
+ const parentTuple = tupleValues[0];
498
+ if (!parentTuple) {
499
+ break;
500
+ }
501
+ const parentAddress = Number(parentTuple.value);
502
+ if (!Number.isInteger(parentAddress) || parentAddress <= 0) {
503
+ break;
378
504
  }
379
- return { name: variable.name, value: tupleRawValue, typeName: variable.typeName };
505
+ currentAddress = parentAddress;
506
+ tupleLevel++;
380
507
  }
381
- const wasmRawValue = rawWasmValues.get(variable);
382
- assert(wasmRawValue !== undefined);
383
- return { name: variable.name, value: wasmRawValue, typeName: variable.typeName };
508
+ }
509
+ return Promise.all(scopeLevels.map(async (scopeLevel) => {
510
+ const rawLocalValues = scopeLevel.variables.map((variable) => {
511
+ if (variable.kind === "closure") {
512
+ const elementIndex = variable.fieldOffset / SMALL_TUPLE_SLOT_SIZE;
513
+ const levelValues = closureTupleValuesByLevel.get(scopeLevel.tupleLevel ?? 0);
514
+ const tupleRawValue = levelValues?.get(elementIndex);
515
+ if (tupleRawValue === undefined) {
516
+ return { name: variable.name, value: "<unavailable>", typeName: variable.typeName };
517
+ }
518
+ return { name: variable.name, value: tupleRawValue, typeName: variable.typeName };
519
+ }
520
+ const wasmRawValue = rawWasmValues.get(variable);
521
+ return {
522
+ name: variable.name,
523
+ value: wasmRawValue ?? "<unavailable>",
524
+ typeName: variable.typeName,
525
+ };
526
+ });
527
+ const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
528
+ return variables.map((variable) => this.toDebugProtocolVariable(variable));
529
+ }));
530
+ }
531
+ async resolveClosureScopeVariables(functionName, envAddress, scopeLevels) {
532
+ const wasmModule = this.loadedModule;
533
+ if (!wasmModule) {
534
+ this.log(`Warning: cannot expand closure scopes for ${functionName}: wasm module is not loaded`);
535
+ return [];
536
+ }
537
+ if (!scopeLevels) {
538
+ this.log(`Warning: cannot expand closure scopes for ${functionName}: DWARF function is unavailable`);
539
+ return [];
540
+ }
541
+ const decodedScopeVariables = await this.decodeScopeChainVariables(scopeLevels, envAddress, new Map());
542
+ return scopeLevels.map((scopeLevel, index) => {
543
+ const variable = {
544
+ kind: "synthetic",
545
+ name: index.toString(),
546
+ value: scopeLevel.name,
547
+ container: { kind: "variables", variables: decodedScopeVariables[index] ?? [] },
548
+ };
549
+ return this.toDebugProtocolVariable(variable);
384
550
  });
385
- const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
386
- return variables.map((variable) => this.toDebugProtocolVariable(variable));
387
551
  }
388
552
  async decodeObjectAtAddress(address) {
389
553
  const fields = await this.resolveObjectFields(address);
@@ -408,39 +572,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
408
572
  }
409
573
  return rawWasmValues;
410
574
  }
411
- async readClosureTupleChain(closureEnvLocalIndex, runtimeVariablesByIndex) {
412
- const closureTupleValuesByLevel = new Map();
575
+ resolveClosureTupleAddress(closureEnvLocalIndex, runtimeVariablesByIndex) {
413
576
  if (closureEnvLocalIndex === undefined) {
414
- return closureTupleValuesByLevel;
577
+ return undefined;
415
578
  }
416
579
  const closureEnvVariable = runtimeVariablesByIndex.get(closureEnvLocalIndex);
417
580
  if (closureEnvVariable === undefined) {
418
- return closureTupleValuesByLevel;
419
- }
420
- let address = Number(closureEnvVariable.value);
421
- if (!Number.isInteger(address) || address <= 0) {
422
- return closureTupleValuesByLevel;
423
- }
424
- let level = 0;
425
- while (true) {
426
- const tupleValues = await this.readTupleElementValues(address);
427
- const levelValues = new Map();
428
- for (const [index, value] of tupleValues.entries()) {
429
- levelValues.set(index, value.value);
430
- }
431
- closureTupleValuesByLevel.set(level, levelValues);
432
- const parentAddress = levelValues.get(0);
433
- if (parentAddress === undefined) {
434
- break;
435
- }
436
- const parentAddressNumber = Number(parentAddress);
437
- if (!Number.isInteger(parentAddressNumber) || parentAddressNumber <= 0) {
438
- break;
439
- }
440
- address = parentAddressNumber;
441
- level++;
581
+ return undefined;
442
582
  }
443
- return closureTupleValuesByLevel;
583
+ const address = Number(closureEnvVariable.value);
584
+ return Number.isInteger(address) && address > 0 ? address : undefined;
444
585
  }
445
586
  resolvePausedStackFrame(frameId) {
446
587
  return this.pausedStackFramesById.get(frameId);
@@ -452,7 +593,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
452
593
  if (typeName !== undefined) {
453
594
  const objectAddress = this.parseObjectAddress(typeName, value);
454
595
  if (objectAddress) {
455
- if (typeName === AS_STRING_CLASS_NAME) {
596
+ if (isDisplayableStringClass(typeName)) {
456
597
  const stringValue = await this.decodeStringAtAddress(typeName, objectAddress);
457
598
  return this.createClassVariable(name, typeName, objectAddress, stringValue);
458
599
  }
@@ -487,12 +628,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
487
628
  case "map-entry": {
488
629
  return "";
489
630
  }
631
+ case "synthetic": {
632
+ return variable.value;
633
+ }
490
634
  }
491
635
  }
492
636
  getVariableReference(variable, isExpandableClass) {
493
637
  if (isExpandableClass && variable.kind === "class") {
494
638
  return this.toObjectVariablesReference(variable.address);
495
639
  }
640
+ if (variable.kind === "synthetic") {
641
+ return variable.container === undefined ? 0 : this.createVariableContainer(variable.container);
642
+ }
496
643
  if (variable.kind === "map-entry") {
497
644
  return this.createVariableContainer({ kind: "map-entry", key: variable.key, value: variable.value });
498
645
  }
@@ -502,7 +649,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
502
649
  return { kind: "class", name, typeName, address, displayValue };
503
650
  }
504
651
  parseObjectAddress(typeName, value) {
505
- if (!this.loadedModule?.getClassLayout(typeName)) {
652
+ if (typeName !== UTF8_CONST_STR_CLASS_NAME && !this.loadedModule?.getClassLayout(typeName)) {
506
653
  return undefined;
507
654
  }
508
655
  const address = Number(value);
@@ -599,9 +746,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
599
746
  return [];
600
747
  }
601
748
  const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
749
+ if (classLayout.builtinKind === BuiltinContainerKind.Function) {
750
+ return this.decodeFunctionElements(view, classLayout);
751
+ }
602
752
  if (classLayout.builtinKind === BuiltinContainerKind.ArrayBuffer) {
603
753
  return this.decodeArrayBufferElements(view);
604
754
  }
755
+ const typedArrayElementType = TYPED_ARRAY_ELEMENT_TYPES.get(classLayout.name);
756
+ if (typedArrayElementType !== undefined) {
757
+ return this.decodeTypedArrayElements(address, view, classLayout, typedArrayElementType);
758
+ }
605
759
  if (classLayout.builtinKind === BuiltinContainerKind.Array) {
606
760
  return this.decodeArrayElements(address, view, classLayout);
607
761
  }
@@ -616,10 +770,49 @@ export class WarpoDebugSession extends LoggingDebugSession {
616
770
  }
617
771
  return Promise.all(classLayout.fields.map((field) => this.decodeFieldVariable(view, field)));
618
772
  }
773
+ decodeFunctionElements(view, classLayout) {
774
+ const indexField = classLayout.fields.find((field) => field.name === "_index" || field.name === "index");
775
+ let functionName;
776
+ if (indexField) {
777
+ const indexValue = this.decodeFieldValue(view, indexField);
778
+ const functionIndex = Number(indexValue);
779
+ if (Number.isInteger(functionIndex) && functionIndex >= 0) {
780
+ functionName = this.loadedModule?.getFunctionNameByTableIndex(functionIndex);
781
+ }
782
+ }
783
+ else {
784
+ this.log(`Warning: cannot expand function: index field is unavailable`);
785
+ }
786
+ const envField = classLayout.fields.find((field) => field.name === "_env" || field.name === "env");
787
+ let envAddress;
788
+ if (envField) {
789
+ const envValue = Number(this.decodeFieldValue(view, envField));
790
+ if (Number.isInteger(envValue) && envValue > 0) {
791
+ envAddress = envValue;
792
+ }
793
+ }
794
+ else {
795
+ this.log(`Warning: cannot expand function: env field is unavailable`);
796
+ }
797
+ const closureScopes = functionName === undefined ? undefined : this.loadedModule?.getClosureVariableScopesByFunctionName(functionName);
798
+ return [
799
+ { kind: "basic", name: "name", value: functionName ?? "<unavailable>", typeName: "string" },
800
+ {
801
+ kind: "synthetic",
802
+ name: "[[scopes]]",
803
+ value: `Scopes[${closureScopes?.length ?? 0}]`,
804
+ typeName: "Scopes",
805
+ container: functionName === undefined || envAddress === undefined
806
+ ? undefined
807
+ : { kind: "closure-scopes", functionName, envAddress, scopeLevels: closureScopes },
808
+ },
809
+ ];
810
+ }
619
811
  async resolveRuntimeObjectPayloadSize(address, classLayout) {
620
812
  if (classLayout.builtinKind !== BuiltinContainerKind.SmallTuple &&
621
813
  classLayout.builtinKind !== BuiltinContainerKind.StaticArray &&
622
- classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer) {
814
+ classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer &&
815
+ !TYPED_ARRAY_ELEMENT_TYPES.has(classLayout.name)) {
623
816
  return classLayout.byteSize;
624
817
  }
625
818
  const sizeMemory = await this.runtime?.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
@@ -659,7 +852,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
659
852
  this.log(`Error: cannot expand tuple reference at ${elementAddress}: runtime class layout is missing`);
660
853
  return { kind: "basic", name, value: elementAddress.toString(), typeName: undefined };
661
854
  }
662
- if (classLayout.name === AS_STRING_CLASS_NAME) {
855
+ if (isDisplayableStringClass(classLayout.name)) {
663
856
  const stringValue = await this.decodeStringAtAddress(classLayout.name, elementAddress);
664
857
  return this.createClassVariable(name, classLayout.name, elementAddress, stringValue);
665
858
  }
@@ -699,6 +892,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
699
892
  }
700
893
  return elements;
701
894
  }
895
+ async decodeTypedArrayElements(address, view, classLayout, elementTypeName) {
896
+ const dataStartField = classLayout.fields.find((field) => field.name === "dataStart");
897
+ const byteLengthField = classLayout.fields.find((field) => field.name === "byteLength");
898
+ if (!dataStartField || !byteLengthField) {
899
+ this.log(`Error: cannot expand typed array at ${address}: typed array header is unavailable`);
900
+ return [];
901
+ }
902
+ const dataStart = Number(this.decodeFieldValue(view, dataStartField));
903
+ const byteLength = Number(this.decodeFieldValue(view, byteLengthField));
904
+ const elementSize = resolveArrayElementSize(elementTypeName, false);
905
+ if (!Number.isInteger(dataStart) ||
906
+ dataStart < 0 ||
907
+ !Number.isInteger(byteLength) ||
908
+ byteLength < 0 ||
909
+ byteLength % elementSize !== 0) {
910
+ this.log(`Error: cannot expand typed array at ${address}: typed array data is invalid`);
911
+ return [];
912
+ }
913
+ const elementsMemory = await this.runtime?.readWasmMemory(dataStart, byteLength);
914
+ if (!elementsMemory) {
915
+ this.log(`Error: cannot expand typed array at ${address}: typed array data is unavailable`);
916
+ return [];
917
+ }
918
+ const elementsView = new DataView(elementsMemory.buffer, elementsMemory.byteOffset, elementsMemory.byteLength);
919
+ return this.decodeArrayElementVariables(elementsView, byteLength / elementSize, elementTypeName, elementSize, false);
920
+ }
702
921
  async decodeStaticArrayElements(address, view, classLayout) {
703
922
  assert.equal(classLayout.builtinKind, BuiltinContainerKind.StaticArray);
704
923
  const elementTypeName = classLayout.templateType;
@@ -857,7 +1076,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
857
1076
  const value = this.decodeFieldValue(view, field);
858
1077
  const address = field.isReference ? this.parseObjectAddress(field.typeName, value) : undefined;
859
1078
  if (address) {
860
- if (field.typeName === AS_STRING_CLASS_NAME) {
1079
+ if (isDisplayableStringClass(field.typeName)) {
861
1080
  const stringValue = await this.decodeStringAtAddress(field.typeName, address);
862
1081
  return this.createClassVariable(field.name, field.typeName, address, stringValue);
863
1082
  }
@@ -939,7 +1158,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
939
1158
  }
940
1159
  }
941
1160
  async decodeStringAtAddress(typeName, address) {
942
- assert.equal(typeName, AS_STRING_CLASS_NAME);
1161
+ assert.ok(isDisplayableStringClass(typeName));
943
1162
  if (!this.runtime) {
944
1163
  this.log(`Error: cannot decode string at ${address}: runtime is not active`);
945
1164
  return "<unavailable>";
@@ -956,7 +1175,8 @@ export class WarpoDebugSession extends LoggingDebugSession {
956
1175
  this.log(`Error: cannot decode string at ${address}: string payload memory is unavailable`);
957
1176
  return "<unavailable>";
958
1177
  }
959
- return JSON.stringify(Buffer.from(stringMemory).toString("utf16le"));
1178
+ const encoding = typeName === UTF8_CONST_STR_CLASS_NAME ? "utf8" : "utf16le";
1179
+ return JSON.stringify(Buffer.from(stringMemory).toString(encoding));
960
1180
  }
961
1181
  doContinueRequest(response) {
962
1182
  const runtime = this.runtime;
@@ -965,13 +1185,119 @@ export class WarpoDebugSession extends LoggingDebugSession {
965
1185
  return;
966
1186
  }
967
1187
  response.body = { allThreadsContinued: true };
968
- this.sendResponse(response);
969
1188
  if (this.stoppedForTrap) {
1189
+ this.sendResponse(response);
970
1190
  this.log("Continue requested from wasm trap; terminating debug session");
971
- this.disposeRuntimeAndTerminate(runtime);
1191
+ void this.disposeRuntimeAndTerminate(runtime);
972
1192
  return;
973
1193
  }
1194
+ this.cancelStep();
974
1195
  void this.resumeRuntime(runtime);
1196
+ this.sendResponse(response);
1197
+ }
1198
+ doStepRequest(response, mode) {
1199
+ const runtime = this.runtime;
1200
+ if (!runtime) {
1201
+ this.sendErrorResponse(response, 1, "No active runtime");
1202
+ return;
1203
+ }
1204
+ if (!runtime.isPaused()) {
1205
+ this.sendErrorResponse(response, 1, "Runtime is not paused");
1206
+ return;
1207
+ }
1208
+ if (this.stoppedForTrap) {
1209
+ this.sendResponse(response);
1210
+ this.log("Step requested from wasm trap; terminating debug session");
1211
+ void this.disposeRuntimeAndTerminate(runtime);
1212
+ return;
1213
+ }
1214
+ this.stepMode = mode;
1215
+ this.stepStartSourceLocation = this.resolveSourceLocation(this.stoppedWasmBytecodeOffset);
1216
+ this.clearVariableContainers();
1217
+ this.sendResponse(response);
1218
+ this.sendEvent(new ContinuedEvent(WarpoDebugSession.threadId, true));
1219
+ void this.advanceStep(runtime).catch((error) => {
1220
+ this.handleStepFailure(error);
1221
+ });
1222
+ }
1223
+ async handleStepPause(runtime, info) {
1224
+ try {
1225
+ const sourceLocation = this.resolveSourceLocation(info.wasmBytecodeOffset);
1226
+ if (this.stepStartSourceLocation &&
1227
+ !this.isLibrarySource(this.stepStartSourceLocation.sourcePath) &&
1228
+ sourceLocation &&
1229
+ this.isLibrarySource(sourceLocation.sourcePath)) {
1230
+ this.log("Skipping library source location during step");
1231
+ await runtime.stepOut();
1232
+ return;
1233
+ }
1234
+ if (this.hasReachedNextSourceLine(info.wasmBytecodeOffset)) {
1235
+ this.completeStep(runtime, info);
1236
+ return;
1237
+ }
1238
+ await this.advanceStep(runtime);
1239
+ }
1240
+ catch (error) {
1241
+ this.handleStepFailure(error);
1242
+ }
1243
+ }
1244
+ handleNonStepPause(runtime, info) {
1245
+ this.cancelStep();
1246
+ if (this.runtime !== runtime) {
1247
+ return;
1248
+ }
1249
+ this.sendStoppedEvent(runtime, info);
1250
+ }
1251
+ hasReachedNextSourceLine(wasmBytecodeOffset) {
1252
+ const sourceLocation = this.resolveSourceLocation(wasmBytecodeOffset);
1253
+ if (!sourceLocation) {
1254
+ return false;
1255
+ }
1256
+ const startSourceLocation = this.stepStartSourceLocation;
1257
+ return (startSourceLocation === undefined ||
1258
+ sourceLocation.sourcePath !== startSourceLocation.sourcePath ||
1259
+ sourceLocation.sourceLine !== startSourceLocation.sourceLine);
1260
+ }
1261
+ isLibrarySource(sourcePath) {
1262
+ return sourcePath.startsWith("~lib/");
1263
+ }
1264
+ async advanceStep(runtime) {
1265
+ if (this.runtime !== runtime || this.stepMode === StepMode.None) {
1266
+ return;
1267
+ }
1268
+ const wasmModule = this.loadedModule;
1269
+ const wasmBytecodeOffset = this.stoppedWasmBytecodeOffset;
1270
+ if (this.stepMode === StepMode.Out) {
1271
+ this.stepMode = StepMode.Into;
1272
+ await runtime.stepOut();
1273
+ }
1274
+ else if (this.stepMode === StepMode.Over &&
1275
+ wasmModule &&
1276
+ wasmBytecodeOffset !== undefined &&
1277
+ wasmModule.getNextBytecodeOffsetAfterCall(wasmBytecodeOffset) !== undefined) {
1278
+ await runtime.stepOver();
1279
+ }
1280
+ else {
1281
+ // Source-level stepping advances through runtime primitives until the source location changes. Calls use the
1282
+ // runtime's call-aware operation because their following bytecode may be a structural `end`, not an executable instruction.
1283
+ await runtime.stepInstruction();
1284
+ }
1285
+ }
1286
+ completeStep(runtime, info) {
1287
+ this.cancelStep();
1288
+ if (this.runtime !== runtime) {
1289
+ return;
1290
+ }
1291
+ this.clearVariableContainers();
1292
+ this.sendStoppedEvent(runtime, info);
1293
+ }
1294
+ handleStepFailure(error) {
1295
+ this.log(`Step failed: ${formatUnknownError(error)}`);
1296
+ this.cancelStep();
1297
+ }
1298
+ cancelStep() {
1299
+ this.stepMode = StepMode.None;
1300
+ this.stepStartSourceLocation = undefined;
975
1301
  }
976
1302
  async resumeRuntime(runtime) {
977
1303
  try {
@@ -992,13 +1318,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
992
1318
  if (this.runtime !== runtime) {
993
1319
  return;
994
1320
  }
995
- this.disposeRuntimeAndTerminate(runtime);
1321
+ void this.disposeRuntimeAndTerminate(runtime);
996
1322
  }
997
- disposeRuntimeAndTerminate(runtime) {
998
- runtime.dispose();
1323
+ async disposeRuntimeAndTerminate(runtime) {
999
1324
  if (this.runtime === runtime) {
1000
1325
  this.runtime = undefined;
1001
1326
  }
1327
+ try {
1328
+ await runtime.dispose();
1329
+ }
1330
+ catch (error) {
1331
+ this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
1332
+ }
1002
1333
  this.resetRuntimeState();
1003
1334
  this.sendEvent(new TerminatedEvent());
1004
1335
  }
@@ -1007,6 +1338,13 @@ export class WarpoDebugSession extends LoggingDebugSession {
1007
1338
  this.pendingBreakpointSources.clear();
1008
1339
  this.stoppedWasmBytecodeOffset = undefined;
1009
1340
  this.stoppedForTrap = false;
1341
+ this.stepMode = StepMode.None;
1342
+ this.stepStartSourceLocation = undefined;
1343
+ }
1344
+ queueAllBreakpointUpdates() {
1345
+ for (const sourcePath of this.requestedBreakpointsBySource.keys()) {
1346
+ this.pendingBreakpointSources.add(sourcePath);
1347
+ }
1010
1348
  }
1011
1349
  logAllBreakpoints() {
1012
1350
  this.log("=== Registered Breakpoints ===");
@@ -1017,8 +1355,15 @@ export class WarpoDebugSession extends LoggingDebugSession {
1017
1355
  }
1018
1356
  this.log("==============================");
1019
1357
  }
1358
+ toDebugProtocolBreakpoint(breakpoint) {
1359
+ const dbp = new Breakpoint(breakpoint.verified, breakpoint.line);
1360
+ dbp.id = breakpoint.id;
1361
+ return dbp;
1362
+ }
1020
1363
  requestBreakpointUpdate(onComplete) {
1364
+ this.log(`Breakpoint update requested: hasRuntime=${this.runtime !== undefined}, hasModule=${this.loadedModule !== undefined}, pendingSources=${this.pendingBreakpointSources.size}, runtimePaused=${this.runtime?.isPaused() ?? false}`);
1021
1365
  if (!this.runtime || !this.loadedModule || this.pendingBreakpointSources.size === 0) {
1366
+ this.log("Breakpoint update skipped: runtime, module, or pending source is unavailable");
1022
1367
  onComplete?.();
1023
1368
  return;
1024
1369
  }
@@ -1028,33 +1373,40 @@ export class WarpoDebugSession extends LoggingDebugSession {
1028
1373
  }
1029
1374
  this.runtime.pause();
1030
1375
  }
1376
+ maybeStartRuntime() {
1377
+ const runtime = this.runtime;
1378
+ if (!this.configurationDone || !runtime || !this.loadedModule) {
1379
+ return;
1380
+ }
1381
+ if (runtime.isPaused()) {
1382
+ void runtime.resume().catch((error) => {
1383
+ const message = error instanceof Error ? error.message : "unknown error";
1384
+ this.log(`Failed to resume after configuration: ${message}`);
1385
+ });
1386
+ return;
1387
+ }
1388
+ runtime.finishModuleLoad();
1389
+ }
1031
1390
  applyPendingBreakpointUpdates(runtime, onComplete) {
1032
1391
  const wasmModule = this.loadedModule;
1033
1392
  if (!wasmModule) {
1393
+ this.log("Breakpoint update skipped: no loaded wasm module");
1034
1394
  onComplete?.();
1035
1395
  return;
1036
1396
  }
1037
1397
  if (this.pendingBreakpointSources.size === 0) {
1398
+ this.log("Breakpoint update skipped: no pending breakpoint sources");
1038
1399
  onComplete?.();
1039
1400
  return;
1040
1401
  }
1402
+ this.log(`Applying breakpoint updates: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
1041
1403
  this.pendingBreakpointSources.clear();
1042
- const wasmBytecodeOffsets = new Set();
1043
- for (const [sourcePath, breakpoints] of this.requestedBreakpointsBySource) {
1044
- for (const breakpoint of breakpoints) {
1045
- const breakpointLocations = wasmModule.resolveBreakpointLocations(breakpoint);
1046
- if (breakpointLocations.length === 0) {
1047
- this.log(`No source-map match for breakpoint ${path.basename(sourcePath)}:${breakpoint.line}`);
1048
- }
1049
- for (const location of breakpointLocations) {
1050
- wasmBytecodeOffsets.add(location.wasmBytecodeOffset);
1051
- this.log(`Resolved breakpoint ${path.basename(sourcePath)}:${breakpoint.line} -> ${location.wasmBytecodeOffset}`);
1052
- }
1053
- }
1054
- }
1404
+ const wasmBytecodeOffsets = this.collectBreakpointOffsets(wasmModule);
1055
1405
  runtime.setWasmBreakpoints(wasmModule, Array.from(wasmBytecodeOffsets).toSorted((left, right) => left - right), {
1056
1406
  onSuccess: () => {
1057
- this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations`);
1407
+ this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations, offsets=${Array.from(wasmBytecodeOffsets)
1408
+ .toSorted((left, right) => left - right)
1409
+ .join(",") || "none"}`);
1058
1410
  onComplete?.();
1059
1411
  },
1060
1412
  onError: (error) => {
@@ -1063,6 +1415,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
1063
1415
  },
1064
1416
  });
1065
1417
  }
1418
+ collectBreakpointOffsets(wasmModule) {
1419
+ const wasmBytecodeOffsets = new Set();
1420
+ for (const [sourcePath, breakpoints] of this.requestedBreakpointsBySource) {
1421
+ for (const breakpoint of breakpoints) {
1422
+ this.collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets);
1423
+ }
1424
+ }
1425
+ return wasmBytecodeOffsets;
1426
+ }
1427
+ collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets) {
1428
+ const sourceMapHasSource = wasmModule.hasSource(sourcePath);
1429
+ this.log(`Resolving breakpoint: source=${sourcePath}, line=${breakpoint.line}, sourceMapHasSource=${sourceMapHasSource}`);
1430
+ const breakpointLocations = wasmModule.resolveBreakpointLocations(breakpoint);
1431
+ if (breakpointLocations.length > 0 && !breakpoint.verified) {
1432
+ breakpoint.verified = true;
1433
+ this.sendEvent(new BreakpointEvent("changed", this.toDebugProtocolBreakpoint(breakpoint)));
1434
+ }
1435
+ if (breakpointLocations.length === 0) {
1436
+ this.log(`Source-map lookup failed for breakpoint ${path.basename(sourcePath)}:${breakpoint.line}: ${sourceMapHasSource ? "source present but line has no mapping" : "source is absent"}`);
1437
+ }
1438
+ this.log(`Breakpoint resolution result: source=${sourcePath}, line=${breakpoint.line}, locations=${breakpointLocations.length}, offsets=${breakpointLocations.map((location) => location.wasmBytecodeOffset).join(",") || "none"}`);
1439
+ for (const location of breakpointLocations) {
1440
+ wasmBytecodeOffsets.add(location.wasmBytecodeOffset);
1441
+ this.log(`Resolved breakpoint ${path.basename(sourcePath)}:${breakpoint.line} -> ${location.wasmBytecodeOffset}`);
1442
+ }
1443
+ }
1066
1444
  disposeLoadedModule() {
1067
1445
  this.clearVariableContainers();
1068
1446
  this.loadedModule?.dispose();