File: RWI_ARCHITECTURE.md

package info (click to toggle)
webkit2gtk 2.51.3-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 477,912 kB
  • sloc: cpp: 3,898,343; javascript: 198,215; ansic: 165,229; python: 50,371; asm: 21,819; ruby: 18,095; perl: 16,953; xml: 4,623; sh: 2,398; yacc: 2,356; java: 2,019; lex: 1,358; pascal: 372; makefile: 197
file content (269 lines) | stat: -rw-r--r-- 13,052 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# WebKit WebAssembly Debugging Architecture

**Process-Wide RWI Integration with WorkQueue-Based Infinite Loop Support**

> **Related Documentation:**
> - **This document**: RWI mode architecture (WebKit integration)
> - **[README.md](./README.md)**: JSC debug server implementation (both Standalone and RWI modes)

---

## Overview

WebKit's WebAssembly debugging uses a **singleton `WasmDebugServer`** that manages all Wasm modules across the entire WebContent process. This design provides a unified debugging view similar to LLDB debugging multi-threaded C++ programs.

### Key Design Principles

1. **Process-Wide Singleton**: One `WasmDebugServer::singleton()` per WebContent process
2. **WorkQueue-Based IPC**: Debugging commands processed on background thread (supports debugging infinite loops)
3. **RWI Integration**: One `WasmDebuggerDebuggable` target per WebContent process
4. **GDB Remote Serial Protocol**: LLDB communicates using standard GDB packets

### Full Stack Architecture

```
┌──────────────────────────────────────────────────────────────────┐
│ External IDE (VS Code / CLion)                                   │
│ ↕ LLDB Protocol                                                  │
│ LLDB Client                                                      │
│ ↕ GDB Remote Serial Protocol (TCP)                               │
│ WasmDebuggerRWIClient (Relay)                                    │
│ ↕ WebInspector.framework RWI Protocol                            │
│ webinspectord (System Daemon)                                    │
│ ↕ RemoteInspector IPC                                            │
│ WasmDebuggerDebuggable (UI Process)                              │
│ ↕ WebKit IPC                                                     │
│ WasmDebuggerDispatcher (WorkQueue thread)                        │
│ ↕ Direct call                                                    │
│ WasmDebugServer (JavaScriptCore)                                 │
│ ↕ Breakpoint control                                             │
│ Wasm Execution (IPInt interpreter)                               │
└──────────────────────────────────────────────────────────────────┘
```

---

## Component Architecture

### External Client

**WasmDebuggerRWIClient** - Relay between LLDB and WebKit
- External standalone application (separate repository)
- Creates TCP server for LLDB connections (default port 9222)
- Uses WebInspector.framework to communicate with webinspectord
- Provides target discovery and connection management
- Forwards raw GDB packets bidirectionally (no protocol translation)

### UI Process (Safari)

**WebProcessProxy** - Manages one WebContent process
- Creates `WasmDebuggerDebuggable` on construction (if `--wasm-debugger` enabled)
- Forwards debugging commands via IPC
- Routes responses back to LLDB

**WasmDebuggerDebuggable** - RWI target for process
- Target ID: Auto-assigned numeric value by RemoteInspector
- Target Name: `"WebAssembly Debugger (WebContent PID {osProcessID})"` (displays actual OS PID)
- Type: `RemoteControllableTarget::Type::WasmDebugger`
- Bridges LLDB ↔ WebContent process
- Implements `RemoteInspectionTarget` interface (required by RWI framework)
- Called by RWI framework (`RemoteConnectionToTarget`) when clients connect/disconnect

### WebContent Process (WebKit)

**WasmDebuggerDispatcher** - WorkQueue-based IPC receiver
- Receives debugging commands on **WorkQueue thread** (not main thread)
- Enables debugging when main thread blocked in infinite loop
- Matches `WebInspectorInterruptDispatcher` pattern (JavaScript debugging)

**WebProcess** - Process singleton
- Owns `WasmDebuggerDispatcher` instance
- Initializes `WasmDebugServer` on startup
- Sets up response handler for IPC communication

### JavaScriptCore (JSC)

**WasmDebugServer** - Process-wide debugging coordinator
- Singleton: `DebugServer::singleton()`
- Dual-mode operation: Standalone (TCP) and RWI (IPC)
- Thread-safe in RWI mode: WorkQueue is serial, guarantees no concurrent access to `handleRawPacket()`
- Tracks all Wasm modules and instances across entire process

> **Implementation Details**: See [README.md](./README.md) for debug server internals, protocol handlers, and virtual address encoding.

---

## Communication Pipeline

**High-Level Flow:**

```
LLDB (IDE)
WasmDebuggerRWIClient
webinspectord
WebProcessProxy (UI Process)
    ↕ IPC
WasmDebuggerDispatcher (WebContent Process, WorkQueue)
WasmDebugServer (JSC)
```

**Key Points:**
- External client relays raw GDB packets between LLDB and WebKit
- Target registration: WebProcessProxy creates `WasmDebuggerDebuggable` with auto-assigned numeric Target ID and name showing OS PID
- IPC routing: Messages dispatched to WorkQueue in WebContent Process, NOT main thread
- WorkQueue enables debugging when main thread blocked in infinite loop
- Pattern matches `WebInspectorInterruptDispatcher` (JavaScript debugging)

**Critical Architecture Decision: WorkQueue Threading**

Main thread can be blocked in infinite Wasm loop, but debugging must still work:
- Kernel queues IPC messages independently
- WorkQueue thread (in WebContent Process) processes debug commands concurrently
- Mutator thread waits on condition variable when paused
- WorkQueue thread signals condition variable to resume execution

---

## RWI Integration

**Target Type:** `RWIDebuggableTypeWasmDebugger` (distinct from `RWIDebuggableTypeWebPage`)

**Key Differences from Web Inspector:**
- Web Inspector: JSON-RPC protocol, per-page lifecycle, visual indicators
- Wasm Debugger: Raw GDB packets, process-lifetime, no visual UI

**Process-Lifetime Semantics:**
Unlike Web Inspector which initializes/tears down controllers per page, WebAssembly debugging runs for the entire process lifetime. The debug server starts with the process and continues running regardless of RWI connection state.

---

## Key Implementation Files

**UI Process:**
- `UIProcess/Inspector/WasmDebuggerDebuggable.{h,cpp}` - RWI target implementation
- `UIProcess/WebProcessProxy.{h,cpp}` - IPC forwarding and lifecycle

**WebContent Process:**
- `WebProcess/Inspector/WasmDebuggerDispatcher.{h,cpp,messages.in}` - WorkQueue dispatcher
- `WebProcess/WebProcess.cpp` - Initialization and response handler

**JavaScriptCore:**
- `wasm/debugger/WasmDebugServer.{h,cpp}` - Core debug server (dual-mode: TCP/RWI)
- `wasm/debugger/WasmExecutionHandler.cpp` - GDB execution control
- `wasm/debugger/WasmModuleManager.cpp` - Module/instance tracking

**RWI Framework:**
- `inspector/remote/RemoteInspector.{h,cpp}` - Target registration
- `inspector/remote/RemoteConnectionToTarget.cpp` - Connection lifecycle
- `inspector/remote/cocoa/RemoteInspectorCocoa.mm` - Platform implementation

---

## Design Rationale

### Why Process-Wide Singleton?

- **Simplicity**: One debug server, one RWI target per process
- **Natural fit**: Matches LLDB's process-level debugging model
- **Unified view**: All modules visible in single registry
- **Shared modules**: Multiple pages can share same Wasm module bytecode
- **Easier IPC**: Single communication channel for entire process

### Why WorkQueue Instead of Main Thread?

- **Infinite loop support**: Main thread can be blocked in `while(true)`
- **Proven pattern**: Matches JavaScript Inspector (`WebInspectorInterruptDispatcher`)
- **Kernel queuing**: Mach messages queued even when process doesn't process RunLoop
- **Thread safety**: WorkQueue is serial (processes one message at a time, no concurrent access)
- **No busy-wait**: Condition variables efficiently wake blocked threads

### Why Not Per-Page Targets?

**Virtual Address Space Fragmentation** (Critical Technical Limitation):
   LLDB requires a **unified virtual address space** for debugging. With per-page targets:
   - Each page would have its own Module ID 0, creating address collisions
   - LLDB breakpoint at virtual address `0x4000000000000100` - which page does this refer to?
   - Same module in different pages would report different IDs, breaking module sharing
   - Memory inspection fails (instance IDs would overlap between pages)
   - Stack traces and disassembly become ambiguous

   Process-wide target provides:
   - Global module ID allocation (Module A = ID 0 across ALL pages)
   - Unique instance IDs (each page's instance gets unique ID)
   - Coherent virtual address space LLDB expects
   - Stable addresses for breakpoints regardless of which page uses the module

   > **Technical Details**: See [README.md](./README.md) for virtual address encoding format.

**Module Sharing**: Same module bytecode used across multiple pages. Per-page targets would duplicate module registration, wasting IDs and creating address conflicts.

**LLDB Model Mismatch**: LLDB debugs processes with a single unified memory view, not isolated per-page address spaces. This is the fundamental architectural reason why virtual address fragmentation (above) becomes a problem - LLDB has no way to handle multiple disconnected address spaces within one process.

**Complex RWI Management**: Need to create/destroy targets as pages navigate, breaking active debug sessions when user navigates away from page being debugged.

**Coordination Overhead**: Breakpoints in shared modules would need cross-page synchronization, adding complexity and potential race conditions.

---

## Debugger Pause Model and Limitations

### Stop-the-World Design

WebAssembly debugging is designed with a **stop-the-world** pause model:
- When LLDB hits a Wasm breakpoint, the VM that hit the breakpoint pauses execution
- **Design Intent**: Stop ALL VMs in the process (main thread + all worker threads) for comprehensive debugging
- **Current Implementation**: Only stops the single VM that hit the breakpoint (multi-VM support not yet implemented)
- This matches standard native debugging behavior (LLDB/GDB debugging C++ code)
- Provides consistent state inspection guarantees during pause

### Asymmetric Interaction with JavaScript Debugger

The Wasm debugger and JavaScript debugger have different pause mechanisms:

| Aspect | **Wasm Debugger (LLDB)** | **JavaScript Debugger (Web Inspector)** |
|--------|--------------------------|------------------------------------------|
| Pause model | Stop-the-world (blocks main thread) | Nested event loop (keeps processing events) |
| When paused | Main thread completely frozen | Event loop continues, JS execution suspended |
| IPC handling | WorkQueue processes messages | Main thread event loop processes messages |

### Known Limitation: Simultaneous Debugging

**Current Restriction**: Developers should use **either** the JavaScript debugger (Web Inspector) **or** the WebAssembly debugger (LLDB), but **not both simultaneously** on the same WebContent process.

**Why This Limitation Exists**:

When the Wasm debugger pauses at a breakpoint (stop-the-world), the main thread blocks completely. This causes:
1. **JavaScript execution freezes** - JS and Wasm share the same main thread
2. **Web Inspector becomes unresponsive** - Web Inspector's IPC runs on the frozen main thread (unlike Wasm debugger which uses WorkQueue)
3. **From the user's perspective** - Web Inspector appears frozen or disconnected

The interaction is asymmetric:
- **Pausing in Wasm debugger** → Blocks main thread → **Freezes both JS execution and Web Inspector**
- **Pausing in JS debugger** → Runs nested event loop → **Wasm debugger continues working** (uses WorkQueue IPC)

**Why Stop-the-World Is Correct**:

This design is intentional and matches standard native debugging:
- **Thread safety**: Prevents race conditions when inspecting Wasm memory/state
- **Consistent state**: Guarantees no concurrent modifications during inspection
- **LLDB expectations**: Matches how LLDB debugs native C++ code (entire process pauses)

**Current Scope**:

The current use case targets specialized debugging scenarios, where users are typically:
- Debugging JSC Wasm engine implementation
- Doing low-level WebAssembly performance analysis with LLDB
- **Not** general web developers debugging mixed JS/Wasm applications

**Future Considerations**:

If this becomes more widely used for general web development:
1. **Document clearly**: Explain that pausing Wasm freezes Web Inspector
2. **UI indicators**: Show warnings when both debuggers are active
3. **Consider alternatives**: Though stop-the-world is likely the only feasible model for LLDB integration

**Note**: Web Inspector's JavaScript debugger does not currently support WebAssembly debugging. When JavaScript calls WebAssembly functions, Web Inspector treats them as opaque function calls and cannot step into Wasm code or inspect Wasm state.