forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathVMLite.h
More file actions
592 lines (542 loc) · 33.3 KB
/
Copy pathVMLite.h
File metadata and controls
592 lines (542 loc) · 33.3 KB
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
/*
* Copyright (C) 2026 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
// VMLite — per-thread execution state for N threads sharing one logical VM
// (SPEC-vmstate §6, frozen layout §6.3).
//
// Phase A (this milestone): VMLites are CARRIERS ONLY (§6.1). Under the GIL,
// every JS-visible piece of execution state still lives in VM members under
// today's names; no interpreter/JIT/runtime path consults a VMLite. The
// carriers exist so threads can be tagged (tid), registered (VMLiteRegistry,
// VMLiteShared.h §6.5.1), TLS-installed (setCurrent, L4), and so the frozen
// VMLitePrimitives layout can be asserted layout-identical to the matching VM
// member block (§6.4 M6 equivalence asserts).
//
// Phase B (UNOWNED — SPEC-vmstate Dev 10; frozen contract only): a pinned
// register/TLS base makes `VM::field` accesses VMLitePrimitives-relative, and
// per-thread VMThreadContext/VMTraps land per §6.8. The layout below is the
// ABI Phase B consumes; freeze rules L1-L5 (§6.3) apply.
#include "DFGDoesGCCheck.h" // AB18-C per-lite DoesGC validation word (only forward-declares VM — no cycle with the "must not include VM.h" rule below).
#include "Interpreter.h" // JSOrWasmInstruction (interpreter/Interpreter.h:61); brings JSCJSValue.h (EncodedJSValue).
#include "JSExportMacros.h"
#include "VMExceptionScopeVerificationState.h" // Obligation 10: per-lite EXCEPTION_SCOPE_VERIFICATION state (debug-only L2 tail append below).
#include "VMThreadContext.h" // §A.2.1 per-lite traps/stack limits (brings VMTraps.h; VMLite is only forward-declared there — no cycle).
#include "WriteBarrier.h" // AB-17 sort-scratch reroute: Group-3 m_cachedSortScratch slot type (no VM.h dependency).
#include <atomic>
#include <memory>
#include <type_traits>
#include <wtf/BumpPointerAllocator.h>
#include <wtf/Lock.h>
#include <wtf/Noncopyable.h>
#include <wtf/RefPtr.h>
#include <wtf/StdLibExtras.h>
#include <wtf/TZoneMalloc.h>
#include <wtf/ThreadSafetyAnalysis.h>
#include <wtf/Vector.h>
namespace JSC {
class Allocator;
class CallFrame;
class Exception;
class MicrotaskQueue;
class QueuedTask;
class RegExp;
class VM;
class VMEntryScope;
struct EntryFrame;
struct ScratchBuffer; // Defined in VM.h; VMLite.h must not include VM.h (VM.h includes us via M6).
namespace GCClient {
class Heap;
}
// 15-bit thread-ID payload of the tagged butterfly word (SPEC-objectmodel
// §9.1; THREAD.md "2^15 thread-ID space"). ConcurrentButterfly.h declares the
// identical alias — a type-alias redeclaration naming the same type is legal.
using ButterflyTID = uint16_t;
class VMLite;
// =============================================================================
// g_jscCurrentVMLite — the SOLE per-thread "installed VMLite" word (L4 backing
// store; M2-alloc-tax-residual (a) collapses the prior dual-store).
//
// DEFINED in runtime/VM.cpp (full rationale block there). This was originally
// the JIT/LLInt-visible MIRROR of a file-static `t_currentVMLite` in
// VMLite.cpp; alloctax2 attribution showed the out-of-line
// VMLite::currentIfExists() (JS_EXPORT_PRIVATE, reading the file-static via a
// general-dynamic TLS resolution) cost +0.913G cyc on the W=1 GIL-off pc-loop
// alone — every VM::group3Primitives() / trapsForCurrentThread() call paid a
// real call + __tls_get_addr. Promoting the mirror to the authoritative store
// and reading it from an ALWAYS_INLINE header accessor lets the compiler emit
// a single `movq %fs:@TPOFF` (Linux IE-TLS) at every call site, and removes
// the dual-write in setCurrent.
//
// L4 (§6.3, frozen): "plain C++ thread_local, NOT pthread_getspecific; accessor
// SIGNATURES frozen; backing store replaceable" — this IS the sanctioned
// backing-store replacement; currentIfExists()/current()/setCurrent()
// signatures are unchanged.
//
// COHERENCE: VMLite::setCurrent (VMLite.cpp) is still the SOLE writer. On
// Darwin it additionally pthread_setspecific's the value into the
// g_jscConfig.vmLiteTLSKey slot for generated code (Mach-O TLV has no constant
// offset); on ELF the JIT bakes this symbol's TPOFF directly. extern "C"
// linkage so the offlineasm/JIT emitter and this header agree on the unmangled
// name regardless of the enclosing namespace.
// =============================================================================
#if OS(LINUX)
extern "C" __attribute__((tls_model("initial-exec"))) thread_local VMLite* g_jscCurrentVMLite;
#else
extern "C" thread_local VMLite* g_jscCurrentVMLite;
#endif
// =============================================================================
// VMLitePrimitives — frozen ABI artifact (SPEC-vmstate §6.3, Groups 1-3).
//
// Names/types EXACTLY mirror the current VM declarations (including m_
// prefixes) — the ABI is consumed via offsetof and .asm. This X-macro is
// authoritative: VM (§6.4 M6) expands the SAME macro to declare its Group 1-3
// member block, and per-field static_asserts (M6, in VM.h) pin
// OBJECT_OFFSETOF(VM, field) - OBJECT_OFFSETOF(VM, topCallFrame)
// == OBJECT_OFFSETOF(VMLitePrimitives, field)
// so the two layouts cannot drift.
//
// Freeze rules (§6.3): L1 — field order frozen; add/remove/reorder is a spec
// revision. L5 — no numeric offsets are frozen beyond "Group 1 pair at
// 0x00/0x08"; everything else goes through OBJECT_OFFSETOF; padding is the
// ABI's choice.
//
// Deliberately NOT in VMLitePrimitives (§6.3; M6 relocates these just outside
// the VM block, names/types/sites unchanged): m_terminationException
// (cross-thread by design), maybeReturnPC, topJSPIContext,
// m_currentSoftReservedZoneSize (interleaved in today's Group-3 range; the
// §6.4(2) span assert forces it out), m_executingRegExp.
// =============================================================================
#define FOR_EACH_VMLITE_PRIMITIVE_FIELD(v) \
/* Group 1: pair; order+adjacency ABI (loadpairq/storepairq of */ \
/* "VM::topCallFrame" in LowLevelInterpreter.asm; VM.h pair assert) */ \
v(CallFrame*, topCallFrame) \
v(EntryFrame*, topEntryFrame) \
/* Group 2: exception/unwind state (VM.h:395,397,878-890) */ \
v(Exception*, m_exception) \
v(Exception*, m_lastException) \
v(CallFrame*, callFrameForCatch) \
v(void*, targetMachinePCForThrow) \
v(JSOrWasmInstruction, targetInterpreterPCForThrow) \
v(uintptr_t, targetInterpreterMetadataPCForThrow) \
v(void*, targetMachinePCAfterCatch) \
v(CallFrame*, newCallFrameReturnValue) \
v(EncodedJSValue, encodedHostCallReturnValue) \
v(uint32_t, targetTryDepthForThrow) \
v(uint32_t, osrExitIndex) \
v(unsigned, varargsLength) \
v(void*, osrExitJumpDestination) \
/* Group 3: VM stack bookkeeping (VM.h:1237-1240). NOT the limits */ \
/* generated code checks — those are VMTraps::m_stack (§6.8); one */ \
/* authority, no duplicate here. */ \
v(void*, m_stackPointerAtVMEntry) \
v(void*, m_stackLimit) \
v(void*, m_lastStackTop) \
/* AB-17 annex (SPEC-ungil-history) sort-scratch reroute: the DFG/FTL */ \
/* ArraySortCompact/Commit JSCellButterfly scratch cache. The compiled */ \
/* code BAKES this slot's address (DFGSpeculativeJIT.cpp compileArray- */ \
/* SortCompact/Commit; FTL twins), so GIL-off it MUST be per-lite: N */ \
/* threads sharing one VM-resident slot hand the same 16-slot scratch */ \
/* to concurrent sorts (tagged-garbage reads / silent wrong results — */ \
/* JSTests/threads/dw1-sort-comparator-osr.js). L1 spec-revision */ \
/* append (the sanctioned varargsLength/5c0e51c precedent): tail of */ \
/* Group 3, nothing above moves; VM.h span assert updated in lockstep. */ \
/* GC: the VM-block copy keeps its visitAggregateImpl append; gilOff */ \
/* lites' copies are appended via the registry walk next to it. Owner- */ \
/* thread-only mutation (the thread's own compiled sort code), so no */ \
/* atomicity; marker reads race the owner's plain JIT stores exactly */ \
/* like today's single-mutator concurrent-marking reads of the VM slot. */ \
v(WriteBarrier<JSCell>, m_cachedSortScratch)
struct VMLitePrimitives {
#define VMLITE_DECLARE_FIELD(type, name) type name { };
FOR_EACH_VMLITE_PRIMITIVE_FIELD(VMLITE_DECLARE_FIELD)
#undef VMLITE_DECLARE_FIELD
// Per-field offsets, VMLitePrimitives-relative (Phase B consumes; L5:
// never frozen numerically, always via OBJECT_OFFSETOF).
#define VMLITE_DECLARE_FIELD_OFFSET(type, name) \
static constexpr ptrdiff_t offsetOf_##name() { return OBJECT_OFFSETOF(VMLitePrimitives, name); }
FOR_EACH_VMLITE_PRIMITIVE_FIELD(VMLITE_DECLARE_FIELD_OFFSET)
#undef VMLITE_DECLARE_FIELD_OFFSET
};
// L3 (§6.3): deliberately NO standard-layout assert — targetInterpreterPCForThrow
// is a Variant (rev 7). OBJECT_OFFSETOF stays valid via __builtin_offsetof
// (§0); the §6.4(2) per-field equivalence asserts in VM.h are the real
// layout contract.
static_assert(std::is_trivially_copyable_v<VMLitePrimitives>); // variant of trivials
static_assert(OBJECT_OFFSETOF(VMLitePrimitives, topCallFrame) == 0);
static_assert(OBJECT_OFFSETOF(VMLitePrimitives, topEntryFrame) == sizeof(void*),
"pair-load contract");
// =============================================================================
// ScratchBufferRegistry — process-wide baked-scratch-buffer index space
// (SPEC-ungil §A.1.6, ANNEX A16, BINDING; UNGIL U-T1, dark until U-T4 emits
// against it).
//
// GIL-off, scratchBufferForSize ADDRESSES baked into DFG/FTL code would be
// shared by N threads. Instead, every baked site becomes
// `loadVMLite -> segment -> [index]`: codegen allocates a process-wide
// monotonic INDEX here (with an index->size map, never freed), and each
// VMLite holds an append-only segmented pointer table (lock-free reads,
// below). A buffer must exist at (lite, index) BEFORE the compiled code can
// run: VM::allocateBakedScratchBufferIndex() fans the install to the VM's
// registered lites, and carrier/spawn registration backfills
// (VMLite::backfillBakedScratchBuffers). Indices are process-wide but only
// the single m_gilOff VM (U0b/U0c) ever allocates them, so cross-VM index
// collision cannot arise in v1.
//
// Lock rank (§LK): m_lock sits OUTSIDE VMLiteRegistry::lock — the install
// nesting ScratchBufferRegistry -> VMLiteRegistry::lock ->
// VMLite::scratchBufferLock is LEGAL (§LK.6 re-rank; SUPERSESSION vs vmstate
// §6.5.1/§7 "registry lock is a leaf", both sides — recorded in
// INTEGRATE-ungil.md (i)).
// =============================================================================
class ScratchBufferRegistry {
WTF_MAKE_NONCOPYABLE(ScratchBufferRegistry);
public:
ScratchBufferRegistry() = default; // Construction reserved to singleton() (NeverDestroyed).
JS_EXPORT_PRIVATE static ScratchBufferRegistry& singleton();
// Allocates the next monotonic index and records its size. Never freed,
// never reused (A16: "monotonic indices + an index->size map, never
// freed").
JS_EXPORT_PRIVATE unsigned allocateIndex(size_t);
JS_EXPORT_PRIVATE size_t sizeForIndex(unsigned) const;
JS_EXPORT_PRIVATE unsigned indexCount() const;
private:
mutable Lock m_lock; // §LK rank: outside VMLiteRegistry::lock (see class comment).
Vector<size_t> m_sizes WTF_GUARDED_BY_LOCK(m_lock); // index -> size; append-only.
};
// =============================================================================
// VMLite — the per-thread carrier (SPEC-vmstate §6.3).
//
// Lifetime/registration (§6.5.1, VMLiteShared.h): registerLite(lite, vm) is
// the sole writer of `vm` (immutable after); a lite is unregistered before it
// is destroyed and before its thread's teardown setCurrent(nullptr); a VM
// must not die while a registered lite's `vm` points at it (§6.4.4 ~VM
// assert). The main thread's carrier is VM::m_mainVMLite (tid 0), installed/
// restored by JSLock mirroring the atom-table swap (§6.4.4 — M4 hunks in
// INTEGRATE-vmstate.md).
// =============================================================================
class VMLite {
WTF_MAKE_NONCOPYABLE(VMLite);
WTF_MAKE_TZONE_ALLOCATED(VMLite);
public:
VMLitePrimitives primitives; // FIRST member, offset 0 (asserted below). FROZEN (L1).
uint16_t tid { 0 }; // ButterflyTID; 0 = main thread. §6.7: ThreadManager (api WS)
// is the sole allocator; spawn writes it BEFORE setCurrent;
// immutable while installed, so reads need no sync.
VM* vm { nullptr }; // Set by VMLiteRegistry::registerLite(lite, vm) (§6.5.1, sole
// writer); immutable after.
// Group 4: regexp, lazy:
RegExp* executingRegExp { nullptr };
std::unique_ptr<BumpPointerAllocator> regExpAllocator;
// Group 5: scratch buffers (§6.6; Phase A inert — reserved for the frozen
// Phase-B contract: baked DFG/FTL scratch-buffer pointers become
// VMLite-relative):
Lock scratchBufferLock; // leaf rank (§7): fastMalloc only under it
Vector<ScratchBuffer*> scratchBuffers;
// Group 6: microtasks (§6.5), lazy (Phase A: exercised only by unit tests;
// VM::queueMicrotask/drainMicrotasks are NOT rerouted):
RefPtr<MicrotaskQueue> defaultMicrotaskQueue;
// L2 (§6.3): new fields append after Group 6 only. Appended (task 7,
// §6.6 plumbing): logically Group-5 state — L2 forbids inserting it next
// to scratchBuffers above. Guarded by scratchBufferLock.
size_t sizeOfLastScratchBuffer { 0 };
// ---- UNGIL U-T1 L2 appends (SPEC-ungil; dark while every VM has
// m_gilOff == 0). Append-only per L1/L2; nothing above moves. ----
// §A.1.3 two-level discriminator, level 2: copied from vm->m_gilOff at
// lite registration (every registration site copies it immediately after
// VMLiteRegistry::registerLite, before the lite can be installed). LLInt
// loads this byte when the process-level JSCConfig gilOffProcess byte
// (level 1, U-T3) is set: 0 => VM Group-3 storage (a second, GIL-on VM's
// protocol stays intact — U0b), 1 => lite storage.
uint8_t gilOff { 0 };
// ANNEX EXIT1/A36 (r31/r32) carrier/lite state machine. EVERY transition
// AND every read is under VMLiteRegistry::lock. LIVE -> TEARDOWN (owner's
// TLS destructor, live path) | LIVE -> COLLECTED -> DETACHED (~VM walk);
// TEARDOWN and DETACHED are terminal. U-T1 lands the storage + the LIVE/
// TEARDOWN marks on the paths it owns; U-T6 owns the full teardown
// protocol (COLLECTED/DETACHED, the EXIT1.9 fence, the deferred dtor).
enum class State : uint8_t { Live, Teardown, Collected, Detached };
State state { State::Live };
// ANNEX A36 (r32): registration-time-fixed under the registry lock — set
// iff WTF::isMainThread() at §F.1 first-entry registration; immutable
// thereafter; read under the registry lock like the state byte. A static
// structural fact ("this lite's owner thread runs no TLS destructors"),
// NEVER a liveness probe.
bool ownerHasNoTlsDtor { false };
// ANNEX EXIT1/A36C: the per-thread GCClient this carrier enters with;
// null => not-entered (conductor predicate, EXIT1.2). U-T1 stamps the
// VM's single clientHeap; U-T6 replaces with per-thread clients.
GCClient::Heap* clientHeap { nullptr };
// H-VMLITE-TLCPTR (SPEC-heap §5.3/§B.4; GILOFF-TAX #1/#2): cached mirror of
// this lite's GCThreadLocalCache flat table pointer + bound — the
// PROVISIONAL offsetOfTable/offsetOfTableBound contract (GCThreadLocalCache.h)
// collapsed to one lite-relative hop. Stamped by GCThreadLocalCache::growTable
// (owner thread, I2) and at the §10A.1 client-slot stamp
// (GCClient::Heap::setCurrentThreadClient — covers attach + the A36C
// carrier swap), so per-tier inline-allocate emitters resolve
// `tlcTable[tlcIndexBase_const + sizeClassIndex]` instead of baking the
// null Allocator that allocatorForConcurrently returns under gilOff (the
// IT-9 {} return) and falling to the lazy-slow-path generation thunk on
// EVERY allocation (operationCompileFTLLazySlowPath: 46.6M GIL-off vs 53
// GIL-on, intcs W=1). Same-thread program order (I11 + I2): the JIT reader
// is the owner thread, so no cross-thread fences; bound is stamped LAST so
// a bound-first reader never indexes past the published table (a stale
// bound is always <=, never > — grow-only, §5.3). GIL-on/flag-off: never
// read (every emitter is behind a vm.gilOff() codegen gate); zero-init.
Allocator* tlcTable { nullptr };
unsigned tlcTableBound { 0 };
// §A.1.5 per-entry record: GIL-off, VM::entryScope/VM::isEntered() and
// the entry-scope service bits live per-lite; VM-wide consumers iterate
// the registry under its lock. Written by VMEntryScope::setUpSlow/
// tearDownSlow on the owning thread.
//
// UNGIL review fix: atomic, and GIL-off the setUpSlow/tearDownSlow
// stores additionally run under VMLiteRegistry::lock. The registry-lock
// walks (anyOtherLiteOfVMEntered, fanOutTerminationToSiblingLites,
// VM::isAnyThreadEntered) read OTHER threads' records under that lock;
// a plain owner-thread store was (a) a data race on the pointer (TSAN
// no-JIT gate breakage) and (b) unserialized against the TERM1.2
// retire/leave-set decision — with the store under the lock, a
// sibling's entered<->not-entered transition cannot interleave a
// retire walk, so "no other lite entered" observed under the lock is a
// real snapshot, not a stale read. Same-thread fast-path reads
// (VMEntryScopeInlines ctor/dtor gates, VM::currentThreadEntryScope)
// use relaxed loads of the own-thread record. GIL-on: the VM-member
// shadow is the only record; this field stays null.
std::atomic<VMEntryScope*> entryScope { nullptr };
// §A.1.5 service bits, same packing as VM::EntryScopeServicesBits
// (byte 0 = EntryScopeService, byte 1 = ConcurrentEntryScopeService) —
// interpreted ONLY by VM's accessors (VM.h owns the packing). Atomic:
// VM-wide fan-out (under the registry lock) writes other threads' lites
// while their owners read/clear concurrently.
std::atomic<uint16_t> entryScopeServicesRawBits { 0 };
// ANNEX A16 segmented baked-scratch-buffer table: lock-free reads
// (`loadVMLite -> segment -> [index]`, all tiers, U-T4); writes under
// scratchBufferLock. Buffers installed here are ALSO appended to
// `scratchBuffers` above (the repurposed Group-5 ownership list), which
// backs the jit-R2 registry GC scan and teardown free.
static constexpr size_t scratchSegmentShift = 6;
static constexpr size_t scratchSegmentSize = static_cast<size_t>(1) << scratchSegmentShift; // 64 entries
static constexpr size_t maxScratchSegments = 256; // 16384 baked indices
std::atomic<std::atomic<ScratchBuffer*>*> scratchSegments[maxScratchSegments] { };
// ---- UNGIL §A.2.1 (AB-17 item 1; L2 append — nothing above moves). ----
// Per-thread VMThreadContext: own VMTraps trap word + StackManager stack
// limits. Generated code reaches them via the chained offset
// offsetOfThreadContext() + VMThreadContext::offsetOfTraps() (+
// VMTraps::offsetOfSoftStackLimit() / offsetOfTrapsBits()) — §6.8.
// Live only for gilOff lites (perThreadTrapsIfExists, VMLite.cpp);
// GIL-on lites keep VM-word semantics (U0b second-VM intact).
VMThreadContext threadContext;
#if ENABLE(EXCEPTION_SCOPE_VERIFICATION)
// ---- UNGIL obligation 10 (INTEGRATE-ungil.md; U-T8b; L2 tail append
// AFTER threadContext — nothing above moves). Debug-only per-lite
// EXCEPTION_SCOPE_VERIFICATION bookkeeping: the ExceptionScope chain
// anchor + simulated-throw state. NOT part of the frozen
// VMLitePrimitives ABI; no generated-code offset reaches it. Selected
// ONLY through VM::exceptionScopeVerificationState() (the
// group3Primitives()-style mode split): GIL-off lites use this copy, so
// a spawned thread's scope chain can never link into the carrier's
// stack (the deterministic GIL-off ExceptionScope::stackPosition()
// stack-use-after-return; VMEntryScope.cpp status item (i)). GIL-on /
// flag-off / second-VM U0b: this copy stays untouched (VM member is
// authoritative).
VMExceptionScopeVerificationState exceptionScopeVerificationState;
#endif
#if ENABLE(DFG_DOES_GC_VALIDATION)
// ---- UNGIL AB18-C (L2 tail append AFTER exceptionScopeVerificationState
// — nothing above moves). Per-mutator DoesGC validation word: GIL-off,
// every DFG/FTL setDoesGCExpectation store and every verifyCanGC read
// targets the CURRENT thread's slot (VM::doesGCCheckSlot() mode split;
// emission: loadVMLite -> offsetOfDoesGC()). Default-constructs to
// encode(true, Special::Uninitialized) (DoesGCCheck ctor), so a fresh
// lite trivially passes until its first DFG/FTL store. Owner-thread-only
// (written and read by the installing thread only), so plain stores —
// including the emission-side two-store32 pair form — need no atomicity.
// NOT part of the frozen VMLitePrimitives ABI. GIL-on / flag-off /
// second-VM U0b: untouched (VM::m_doesGC is authoritative).
DFG::DoesGCCheck doesGC;
static constexpr ptrdiff_t offsetOfDoesGC() { return OBJECT_OFFSETOF(VMLite, doesGC); }
#else
// Stub mirrors VM::addressOfDoesGC()'s !ENABLE arm so `if constexpr
// (validateDFGDoesGC)` emission splits type-check in all configurations.
static ptrdiff_t offsetOfDoesGC() { UNREACHABLE_FOR_PLATFORM(); return 0; }
#endif
// B14 / MC-DOS S7 amendment note: a per-lite observedRetireEpoch slot was
// proposed here as an "explicit per-LITE witness regardless of the
// lite<->client mapping" for GCSafepointEpoch (SPEC-jit §4.4). Dropped at
// adversarial review: bumpAndReclaim()'s min scan reads ONLY
// GCClient::Heap::m_localEpoch (GCSafepointEpoch.cpp:148), so a per-lite
// slot with no reader is dead code, and its in-stop stamping walk was a
// new release-build lock-site (VMLiteRegistry::lock) inside the conductor
// window for zero functional gain. The §4.4 soundness rests on U-T6
// (per-thread clients: every JS-executing lite has its own GCClient::Heap
// registered in clientSet()) — see epochCoversEveryJSThread() in
// RetiredJITArtifacts.cpp for the full argument and the standing
// obligation on any future change that lets a lite execute without a
// distinct registered client.
static constexpr ptrdiff_t offsetOfPrimitives() { return OBJECT_OFFSETOF(VMLite, primitives); }
static constexpr ptrdiff_t offsetOfTID() { return OBJECT_OFFSETOF(VMLite, tid); }
static constexpr ptrdiff_t offsetOfGilOff() { return OBJECT_OFFSETOF(VMLite, gilOff); } // LLInt level-2 byte (U-T3).
static constexpr ptrdiff_t offsetOfVM() { return OBJECT_OFFSETOF(VMLite, vm); } // Reader-side same-VM guard in the Group-3 host-call-return-value discriminators (LLIntThunks.cpp / LowLevelInterpreter64.asm).
static constexpr ptrdiff_t offsetOfScratchSegments() { return OBJECT_OFFSETOF(VMLite, scratchSegments); } // A16 emission (U-T4).
static constexpr ptrdiff_t offsetOfThreadContext() { return OBJECT_OFFSETOF(VMLite, threadContext); } // §A.2.1 chained-offset emission (U-T3/U-T4).
static constexpr ptrdiff_t offsetOfTlcTable() { return OBJECT_OFFSETOF(VMLite, tlcTable); } // H-VMLITE-TLCPTR §B.4 inline-allocate emission.
static constexpr ptrdiff_t offsetOfTlcTableBound() { return OBJECT_OFFSETOF(VMLite, tlcTableBound); }
// TLS accessors (L4): backed by g_jscCurrentVMLite (see banner above the
// class). Signatures frozen; the backing store is the L4-sanctioned
// replaceable part. M2-alloc-tax-residual (a): ALWAYS_INLINE so the
// ~7 VM.h hot selectors (group3Primitives / trapsForCurrentThread /
// exceptionScopeVerificationState / currentThreadEntryScope / …) compile
// to one IE-TLS load instead of an out-of-line call. Flag-off identity:
// every hot caller is behind gilOffWithProcessGate(), so flag-off code
// never reaches the read; the read itself is a plain TLS load with no
// option dependency.
ALWAYS_INLINE static VMLite* currentIfExists() { return g_jscCurrentVMLite; }
ALWAYS_INLINE static VMLite& current() { ASSERT(g_jscCurrentVMLite); return *g_jscCurrentVMLite; }
// Installs `lite` (may be null = uninstall) and returns the previous
// value. Debug (I18/I20): asserts lite->tid != notTTLTID and that the
// lite is registered. After the TLS write it invokes the TID-tag hook
// (below) with `lite ? lite->tid : 0` — including for null — so the JIT's
// per-thread butterfly TID tag stays coherent across §6.4.4
// install/restore and multi-VM switches (jit CS3/I19).
JS_EXPORT_PRIVATE static VMLite* setCurrent(VMLite*); // returns previous
JS_EXPORT_PRIVATE VMLite();
JS_EXPORT_PRIVATE ~VMLite(); // I20: asserts not installed here, not registered; poisons in debug.
// Helpers defined in VMLiteInlines.h (I11 owner asserts build on these).
inline bool isInstalledOnCurrentThread() const;
inline BumpPointerAllocator& ensureRegExpAllocator(); // Group 4 lazy
// ---- §6.5 Group 6: per-thread default microtask queue (Phase A inert —
// VM::queueMicrotask/drainMicrotasks are NOT rerouted; Phase B routes
// enqueue/drain to the current thread's queue; cross-thread enqueue stays
// out of scope).
//
// COVERAGE STATUS (honest gap, tracked): these facilities (and the §6.6
// scratch-buffer ones below) are JS-UNREACHABLE in Phase A, so the
// JSTests/threads/vmstate suite cannot execute them; the task-7 C++ test
// obligations (owner-only enqueue/drain, lazy-creation idempotence,
// drain-exactly-once, scratchBufferForSize(0)==nullptr, geometric-growth
// reuse, dtor-frees-under-ASAN) are an UNMET PENDING item in
// INTEGRATE-vmstate.md and MUST land before any Phase-B routing consults
// a VMLite. Do not treat this block as gated by the existing suites.
// HARD GATE (review round 3): until that test lands, ANY new caller of
// these facilities in any workstream's diff is a blocker by construction
// — see the PENDING entry in INTEGRATE-vmstate.md. ----
//
// Lazy creation. GC visibility (§6.5): the queue is built with
// MicrotaskQueue::create(*vm), whose constructor appends it to
// VM::m_microtaskQueues — the single registration list GC markers
// traverse. Both the append (M12) and the markers' iteration (M11) take
// VMLiteRegistry::singleton().lock once those hunks are applied, so lazy
// creation on a spawned thread cannot corrupt LIST MEMBERSHIP under
// concurrent marking. The registry lock deliberately does NOT cover queue
// CONTENTS: inside the locked forEach the marker calls visitAggregate,
// which walks the queue's Deque lock-free. That is sound for the same
// reason it is sound today (MicrotaskQueue.cpp:158-160): deque contents
// are only visited at collector phases that suspend the mutator
// (FixPoint/Begin — the "Sh" constraint runs in the fixpoint, world
// stopped). Under useJSThreads this invariant must mean ALL mutators
// stopped — verified, not assumed: M11 rationale + cross-WS checklist
// item 12 in INTEGRATE-vmstate.md make it an explicit integration gate.
// I11/I14: caller must be the installed owner thread AND hold the VM's
// JSLock (asserted).
JS_EXPORT_PRIVATE MicrotaskQueue& ensureDefaultMicrotaskQueue();
// I11 helpers (VMLiteInlines.h): the per-thread queue is enqueued/drained
// only by its owner — both debug-assert isInstalledOnCurrentThread().
inline void enqueueMicrotaskToDefaultQueue(QueuedTask&&);
// Runs a full microtask checkpoint on the default queue (no-op if the
// queue was never created). Requires the JSLock (it enters the VM).
inline void drainDefaultMicrotaskQueue();
// ---- §6.6 Group 5: per-thread scratch buffers. This signature is the
// §6.6-reserved one and must not change; UNGIL §A.1.6 (annex A16) makes
// it the GIL-off NON-BAKED scratch path: VM::scratchBufferForSize
// dispatches here when vm.m_gilOff (size-class policy mirrors VM's,
// including geometric growth).
//
// GC (UNGIL U-T1, supersedes the Phase-A caveat that used to live here):
// VM::gatherScratchBufferRoots now ALSO walks the VMLiteRegistry and
// scans every same-VM lite's `scratchBuffers` ownership list (jit R2),
// so buffers handed out here MAY carry values needing marking, exactly
// like VM's.
JS_EXPORT_PRIVATE ScratchBuffer* scratchBufferForSize(size_t);
JS_EXPORT_PRIVATE void clearScratchBuffers(); // setActiveLength(0) on all; owner-only.
// ---- ANNEX A16 baked-index table (UNGIL U-T1; emission lands at
// U-T4a/U-T4b). ----
// Lock-free read: `segment acquire-load -> entry acquire-load`. Null
// until installed; compiled code never runs before the install/backfill
// contract has populated its indices (A16).
ScratchBuffer* scratchBufferAtIndex(unsigned index) const
{
ASSERT(index < maxScratchSegments * scratchSegmentSize);
auto* segment = scratchSegments[index >> scratchSegmentShift].load(std::memory_order_acquire);
if (!segment)
return nullptr;
return segment[index & (scratchSegmentSize - 1)].load(std::memory_order_acquire);
}
// Idempotent install: creates the buffer (of the registry-recorded size)
// at `index` if absent, appending it to the `scratchBuffers` ownership
// list. Takes scratchBufferLock; legal under VMLiteRegistry::lock
// (§LK.6 re-rank — the VM-side install fan holds it).
JS_EXPORT_PRIVATE void ensureScratchBufferAtIndex(unsigned index, size_t);
// Registration backfill (A16: "a buffer exists at (lite, index) BEFORE
// the code runs — install fans to the VM's lites; registration
// backfills"). Called after VMLiteRegistry::registerLite at every
// GIL-off registration site; idempotent vs a racing install fan.
JS_EXPORT_PRIVATE void backfillBakedScratchBuffers();
};
static_assert(OBJECT_OFFSETOF(VMLite, primitives) == 0);
// =============================================================================
// §6.7 — currentButterflyTID()
//
// SOLE defining TU is VMLite.cpp (INTEGRATE verifies ODR; the
// ConcurrentButterfly.h §9.1 shim and the jit ConcurrentButterflyOperations.cpp
// shim are both #if !__has_include("VMLite.h")-guarded and compile away now
// that this header exists). Returns the installed carrier's tid, or 0 when no
// VMLite is installed (I18: main thread / embedder threads / GIL phase — a
// never-entering thread touches no JS objects, so 0 is unobservable there);
// never notTTLTID (0x7fff).
// =============================================================================
JS_EXPORT_PRIVATE ButterflyTID currentButterflyTID();
// TID-tag hook (§6.7; jit CS3/I19 provider). Null by default; jit task 1b
// (ConcurrentButterflyOperations.cpp, initializeButterflyTIDTagForCurrentThread)
// registers its P5 tag-update body so every lite switch keeps
// g_jscButterflyTIDTag coherent without a runtime/ -> jit/ include. A null
// hook is a no-op (Phase-A standalone builds). The hook runs AFTER the TLS
// write in setCurrent, with `lite ? lite->tid : 0` (including uninstalls);
// api §5.2's explicit P5/clear calls remain and are idempotent with it.
JS_EXPORT_PRIVATE void setVMLiteTIDTagHook(void (*)(uint16_t));
// Carrier-TID allocation hooks (UNGIL §A.3.6 / ANNEX A36; U-T1). GIL-off,
// every main/embedder carrier lite needs a TM-allocated unique nonzero TID
// from the SAME 2^15 space as spawned threads (I17 exhaustion accounting
// includes carriers; main/embedder ThreadState.tid STAYS 0 — the carrier TID
// is a separate allocation, r9 F4). ThreadManager (api workstream; IU row)
// registers the provider pair at initialization; runtime/ must not include
// ThreadManager.h here for the same layering reason as the TID-tag hook
// above. allocateCarrierTID() RELEASE_ASSERTs a provider is installed —
// reaching it without one is an integration bug (the GIL-off entry path is
// unreachable until the U-T6/U-T9 activation tasks, so dark builds never
// trip it).
JS_EXPORT_PRIVATE void setCarrierTIDHooks(uint16_t (*allocate)(), void (*release)(uint16_t));
JS_EXPORT_PRIVATE uint16_t allocateCarrierTID();
JS_EXPORT_PRIVATE void releaseCarrierTIDIfHooked(uint16_t);
} // namespace JSC