File: VMManager.h

package info (click to toggle)
webkit2gtk 2.51.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 455,340 kB
  • sloc: cpp: 3,865,253; javascript: 197,710; ansic: 165,177; python: 49,241; asm: 21,868; ruby: 18,095; perl: 16,926; xml: 4,623; sh: 2,409; yacc: 2,356; java: 2,019; lex: 1,330; pascal: 372; makefile: 210
file content (385 lines) | stat: -rw-r--r-- 17,601 bytes parent folder | download
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
 * Copyright (C) 2025 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include <JavaScriptCore/StopTheWorldCallback.h>
#include <JavaScriptCore/VMThreadContext.h>
#include <wtf/Atomics.h>
#include <wtf/Condition.h>
#include <wtf/DoublyLinkedList.h>
#include <wtf/Lock.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/ScopedLambda.h>
#include <wtf/Seconds.h>
#include <wtf/StdLibExtras.h>

namespace JSC {

class VM;

// Understanding Stop the World (STW)
// ==================================
//
// Intuition on how to think about things?
// =======================================
// The actors in play for a Stop the World story are:
// 1. VMManager
// 2. VM
// 3. a Conductor Agent
//
// Events / actions involved in the Stop the World story are:
// 1. Stop requests (with a given StopReason)
// 2. Stop callback handlers
//
// An intuitive way to think about the Stop the World story is:
//
// 1. VMManager is an abstraction representing the process. There is only 1 singleton
//    VMManager instance, and it coordinates the tracking and scheduling of VMs.
//
// 2. VM (and its VMThreadContext) represents a thread. A VM instance may actually
//    run on different machine threads at different times (JSC's API allows this).
//    However, from VMManager's perspective, each VM is like a thread that can be
//    suspended / stopped, and resumed.
//
//    FIXME: the current VMManager does NOT yet handle cases where more than one VM
//    is run on the same machine thread e.g. one VM1 calls into C++, which in turn
//    calls into VM2. VM2 now has control of the CPU, but VMManager does not know
//    that VM1 is in a way "deactivated". This scenario cannot manifest in WebKit
//    with web workloads though.
//
// 3. The Conductor Agent is something like a Debugger agent that tells VMManager
//    to stop or resume VMs / threads.
//
// 4. Stop requests are like interrupts. A stop being requested is analogous to an
//    interrupt firing.
//
// 5. Stop callback handlers are like interrupt handlers that masked out all interrupts
//    so that no other interrupts can fire while the current one is being handled.
//
//    When a stop request occurs, VMManager::notifyVMStop() will dispatch a callback
//    to the appropriate handler for that request. Similar to the interrupt
//    analogy, only one request can be serviced at one time. All other requests
//    regardless of priority will be blocked (and held in pending) until the current
//    request is done being serviced.
//
// World Execution Modes
// =====================
// The VMManager has a notion of a world mode (see VMManager::Mode). These modes are:
// 1. RunAll - all threads can run or are running.
// 2. RunOne - only one thread can run, like when a debugger is single stepping.
// 3. Stopping - a stop has been requested, and VMs are in the process of stopping.
// 4. Stopped - all threads have been stopped, and the highest priority stop request
//              can now be serviced.
//
// Querying VMManager Info
// =======================
// VMManager::info() provides a view into a few pieces of VMManager state:
// 1. numberOfVMs - the number of VMs that have been constructed and are alive.
// 2. numberOfActiveVMs - the number of VMs that are activated i.e. their threads
//        have entered the VM. This numberOfActiveVMs is only available while the
//        world is NOT in Mode::RunAll (i.e. must be in some form of stoppage).
// 3. numberOfStoppedVMs - the number of VMs that have reached the stopping point
//        in VMManager::notifyVMStop(). The currently executing targetVM is counted
//        as stopped when single stepping in Mode::RunOne.
// 3. worldMode - this is the current VM world mode (as described above).
//
// Currently, this info is mainly used for testing purposes only.
//
// Initiating Stop the World
// =========================
// Stop the World begins with some agent calling VMManager::requestStopAll() with a
// StopReason. This agent can be from mutator threads or from a helper thread like
// those employed by debuggers.
//
// More than one agent can request STW at the same time. Hence, there can be multiple
// stop requests queued up while the world is being stopped.
//
// StopReason and their Priority
// =============================
// Current StopReasons are:
//     None - no requests
//     GC - requesting stop for Global GC
//     WasmDebugger - requesting stop for Wasm Debugger (like Ctrl-C in lldb)
//     MemoryDebugger - similar to WasmDebugger, but for the Memory Debugger.
//
// The priority of these requests are defined by their order of declaration.
// See definition of FOR_EACH_STOP_THE_WORLD_REASON and enum class StopReason.
// StopReason::None is a special case and has no priority.
// StopReason::GC is the current highest priority request.
// StopReason::MemoryDebugger is the current lowest priority request.
//
// StopReason is synonymous with "StopRequest".
// From the client's perspective, it is the reason for a stop request.
// From the VMManager's perspective, it is the type of stop request.
//
// Servicing Order: one request at a time
// ======================================
// The order the stop requests came in does not matter. Once the world is finally
// stopped, the higher priority request is serviced first.
// See fetchTopPriorityStopReason() and m_currentStopReason.
//
// While this request is being serviced, other requests will be ignored. During
// this time of service, new stop requests can be added to m_pendingStopRequestBits,
// but they will be ignored even if they are higher priority. We will service them
// only after the current request has resumed with RunOne or RunAll mode.
//
// StopTheWorldCallback (i.e. stop request handlers)
// =================================================
// When the world is stopped, VMManager will call back to a request handler
// based on what StopReason is in m_currentStopReason. See VMManager::notifyVMStop().
//
// The handler for GC should be static but is not currently implemented yet.
// The handlers for WasmDebugger and MemoryDebugger may be overridden. They are
// made to be overrideable only to enable testing. See VMManagerStopTheWorldTest.cpp
// for how they are used in testing.
//
// Each handler must be of the shape StopTheWorldCallback. See StopTheWorldCallback.h
// The handler will be called with a StopTheWorldEvent. The StopTheWorldEvent indicates
// where the handler is called from. This may have a use later on, but for now, the
// StopTheWorldEvent is only informational.
//
// After the handler is done, it controls how execution will proceed thereafter by
// returning one of the StopTheWorldCallback return values (see StopTheWorldCallback.h).
// The possible return values are:
//
// 1. STW_CONTINUE()
//    - this is only used for testing purposes where we want to loop inside
//      VMManager::notifyVMStop() while waiting for more things to handle.
//    - VMManager::m_worldMode will remain in Mode::Stopped.
// 2. STW_CONTEXT_SWITCH(targetVM)
//    - this is used to switch control of the handler to another VM on a different
//      thread without resuming any execution. lldb's  "thread select ..." can be
//      implemented this way.
//    - VMManager::m_worldMode will remain in Mode::Stopped.
// 3. STW_RESUME_ONE()
//    - this is used to resume only the current VM thread in RunOne mode. This is
//      useful for debuggers that wish to single step in the current VM. It keeps
//      other threads paused / stopped while this thread executes. It is up to the
//      client to detect potential resource deadlocks (e.g. using a timeout) that
//      may arise from only resuming 1 thread.
//    - VMManager::m_worldMode will transition from Mode::Stopped to Mode::RunOne.
// 4. STW_RESUME_ALL()
//    - forces all threads to resume from a stop.
//    - VMManager::m_worldMode will transition from Mode::Stopped to Mode::RunAll.
// 5. STW_RESUME()
//    - Return to whatever run mode we were executing with before the current Stop
//      the World request. That may be either Mode::RunOne or Mode::RunAll.
//    - This allows the GC to run (with its own Stop the World requests) even while
//      while we're single stepping in a debugger with Mode::RunOne.
//
// Edge Cases and Special Circumstances
// ====================================
// While in Mode::RunOne, if the VM that is running either exits the VM (aka
// deactivates) or its VM is destructed (aka shutdown), the VMManager will transition
// the world mode back to RunAll (unblocking other VMs and threads)  since the current
// VM is no longer viable for continuing execution.

#define FOR_EACH_STOP_THE_WORLD_REASON(v) \
    v(GC) \
    v(WasmDebugger) \
    v(MemoryDebugger) \

class VMManager {
    WTF_MAKE_NONCOPYABLE(VMManager);

#define DECLARE_STOP_THE_WORLD_REASON_BIT_SHIFT(reason__) reason__##BitShift,
    enum StopReasonBitShift {
        FOR_EACH_STOP_THE_WORLD_REASON(DECLARE_STOP_THE_WORLD_REASON_BIT_SHIFT)
    };
#undef DECLARE_STOP_THE_WORLD_REASON_BIT_SHIFT

#define COUNT_STOP_REASON(event) + 1
    static constexpr unsigned NumberOfStopReasons = FOR_EACH_STOP_THE_WORLD_REASON(COUNT_STOP_REASON);
#undef COUNT_STOP_REASON

public:
    using StopRequestBits = uint32_t;
    static_assert(NumberOfStopReasons <= (sizeof(StopRequestBits) * CHAR_BIT));

#define DECLARE_STOP_THE_WORLD_REASON(reason__) reason__ = (1 << reason__##BitShift),
    enum class StopReason : StopRequestBits {
        None = 0,
        FOR_EACH_STOP_THE_WORLD_REASON(DECLARE_STOP_THE_WORLD_REASON)
    };
#undef DECLARE_STOP_THE_WORLD_REASON

    enum class Error : uint8_t {
        None,
        TimedOut
    };

    JS_EXPORT_PRIVATE static VMManager& singleton();

    ALWAYS_INLINE static bool isValidVM(VM* vm)
    {
        return vm == s_recentVM ? true : isValidVMSlow(vm);
    }

    // StopTheWorld APIs ======================================================

    enum class Mode : uint8_t {
        RunAll, // no threads are stopped.
        RunOne, // all threads are stopped except for the 1 thread the debugger wants to run.
        Stopping, // still waiting for the right thread to service the stop.
        Stopped, // all threads have stopped, and the right thread is now servicing the stop.
    };

    struct Info {
        unsigned numberOfVMs;
        unsigned numberOfActiveVMs;
        unsigned numberOfStoppedVMs;
        Mode worldMode;
    };

    JS_EXPORT_PRIVATE static Info info();
    static unsigned numberOfVMs() { return singleton().m_numberOfVMs; }

    JS_EXPORT_PRIVATE static void setWasmDebuggerCallback(StopTheWorldCallback);
    JS_EXPORT_PRIVATE static void setMemoryDebuggerCallback(StopTheWorldCallback);

    ALWAYS_INLINE CONCURRENT_SAFE static void requestStopAll(StopReason reason)
    {
        singleton().requestStopAllInternal(reason);
    }
    ALWAYS_INLINE CONCURRENT_SAFE static void requestResumeAll(StopReason reason)
    {
        singleton().requestResumeAllInternal(reason);
    }

    void notifyVMConstruction(VM&);
    void notifyVMDestruction(VM&);
    void notifyVMActivation(VM&);
    void notifyVMDeactivation(VM&);
    void notifyVMStop(VM&, StopTheWorldEvent);

    void handleVMDestructionWhileWorldStopped(VM&);

    // Interation APIs ======================================================

    using IteratorCallback = IterationStatus(VM&);
    using TestCallback = bool(VM&);

    static inline VM* findMatchingVM(const Invocable<TestCallback> auto& test)
    {
        SUPPRESS_FORWARD_DECL_ARG return singleton().findMatchingVMImpl(scopedLambda<TestCallback>(test));
    }

    static inline void forEachVM(const Invocable<IteratorCallback> auto& functor)
    {
        SUPPRESS_FORWARD_DECL_ARG singleton().forEachVMImpl(scopedLambda<IteratorCallback>(functor));
    }

    static inline Error forEachVMWithTimeout(Seconds timeout, const Invocable<IteratorCallback> auto& functor)
    {
        SUPPRESS_FORWARD_DECL_ARG return singleton().forEachVMWithTimeoutImpl(timeout, scopedLambda<IteratorCallback>(functor));
    }

    JS_EXPORT_PRIVATE static void dumpVMs();

private:
    VMManager() = default;

    bool hasPendingStopRequests() const { return m_pendingStopRequestBits.loadRelaxed(); }

    JS_EXPORT_PRIVATE CONCURRENT_SAFE void requestStopAllInternal(StopReason);
    JS_EXPORT_PRIVATE CONCURRENT_SAFE void requestResumeAllInternal(StopReason);

    void resumeTheWorld() WTF_REQUIRES_LOCK(m_worldLock);
    void incrementActiveVMs(VM&) WTF_REQUIRES_LOCK(m_worldLock);
    void decrementActiveVMs(VM&) WTF_REQUIRES_LOCK(m_worldLock);

    JS_EXPORT_PRIVATE static bool isValidVMSlow(VM*);
    JS_EXPORT_PRIVATE VM* findMatchingVMImpl(const ScopedLambda<TestCallback>&);
    JS_EXPORT_PRIVATE void forEachVMImpl(const ScopedLambda<IteratorCallback>&);
    JS_EXPORT_PRIVATE Error forEachVMWithTimeoutImpl(Seconds timeout, const ScopedLambda<IteratorCallback>&);

    void iterateVMs(const Invocable<IterationStatus(VM&)> auto&) WTF_REQUIRES_LOCK(m_worldLock);

    DoublyLinkedList<VMThreadContext> m_vmList WTF_GUARDED_BY_LOCK(m_worldLock);
    Lock m_worldLock;
    Condition m_worldConditionVariable;

    // === Variables only relevant for StopTheWorld ===================================

    // Indicates if the world is running or stopped (see Modes for details).
    // Requires m_worldLock to write to this, but not to read it.
    Mode m_worldMode { Mode::RunAll };

    // Indicates if the world needs to be in RunOne mode (and if it should resume in RunOne mode
    // after stops).
    bool m_useRunOneMode { false };

    // Indicates if there are pending StopTheWorld requests (analogous to pending interrupts).
    // In RunOne mode, all VM threads (except one) will be stopped even when m_pendingStopRequestBits
    // is empty. Hence, m_pendingStopRequestBits says nothing about whether threads are / should be
    // running or not.
    // Can be written and read concurrently without m_worldLock.
    Atomic<StopRequestBits> m_pendingStopRequestBits { 0 };

    // We need to track a m_currentStopReason because we may need to continue servicing the current
    // request after a context switch to a different targetVM. Conceptually, if StopTheWorld requests
    // are analogous to interrupts, then when a specific interrupt is being serviced, all other
    // interrupts are blocked / disabled though their status remains pending. Similarly, all other
    // pending StopTheWorld requests will be blocked, and only serviced after the current one being
    // serviced is done.
    // Only notifyVMStop() may modify m_currentStopReason.
    StopReason m_currentStopReason { StopReason::None };

    // Indicates the targetVM that will service the StopTheWorld request, or the targetVM that may
    // continue running in RunOne mode.
    // Can obly be written to while holding the m_worldLock.
    // Can be read without the m_worldLock under some restricted circumstances.
    VM* m_targetVM { nullptr };

    // We'll set m_numberOfActiveVMs to 99999999 when it's not supposed to hold a valid value.
    // 99999999 is just some arbitrary token value that is easy to recognize but we're not likely
    // to see in any real world value of m_numberOfActiveVMs. The 99999999 value will easily
    // convey the idea that the value is invalid at any given point in time that info() is sampled.
    static constexpr unsigned invalidNumberOfActiveVMs = 99999999;

    // Indicated the number of VMs that have non-null EntryScopes.
    // This value is only valid while a StopTheWorld request is being processed. It is calculated
    // when the first requesting VM stops of all VMs. While a StopTheWorld request is being serviced,
    // it will be updated using the VM's ConcurrentEntryScopeService.
    //
    // The choice to not track a valid m_numberOfActiveVMs at all times is just an optimization so
    // that we can skip this work when not doing Stop the World.
    unsigned m_numberOfActiveVMs { invalidNumberOfActiveVMs };

    Atomic<unsigned> m_numberOfStoppedVMs { 0 };

    // === End of variables only relevant for StopTheWorld =================================

    unsigned m_numberOfVMs { 0 };

    JS_EXPORT_PRIVATE static VM* s_recentVM;

    friend class NeverDestroyed<VMManager>;
};

#undef FOR_EACH_STOP_THE_WORLD_REASON

} // namespace JSC