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
//! Per-worker arena pool: one arena reused across files (Davinci P1-11).
//!
//! Batch compilation is file-parallel over rayon, and before this the arena
//! was built and torn down per file — every compile started from an empty
//! arena and climbed oxc's chunk ladder again (64 KiB, 128 KiB, 256 KiB …),
//! so a batch paid the growth for every file and held every rung until that
//! file finished. The pool keeps the arena instead: `Allocator::reset`
//! recycles the bytes (oxc keeps the current chunk and returns the rest), and
//! the next file allocates into memory that is already mapped.
//!
//! # Why thread-local
//!
//! Davinci's architecture note fixes the shape — "each rayon worker owns a
//! pooled arena, so arenas are never shared across threads or files" — and a
//! `thread_local!` free list is exactly that with no coordination at all: no
//! mutex on the acquire path (oxc's own `AllocatorPool` locks a shared stack),
//! no worker registry to thread through the CLI's `par_iter`, and it serves
//! every embedder that compiles a file (CLI batch, LSP, napi) rather than only
//! the one call site that could pass a pool handle down. Rayon worker threads
//! outlive the batch, so a worker's arena is reused for every file that worker
//! takes. The cost is that the pool is invisible in signatures; that is what
//! the debug bookkeeping below is for.
//!
//! # Lifetime contract
//!
//! [`acquire`] hands out a [`PooledAllocator`] guard that *owns* its arena for
//! the compile. Arena-backed values borrow the guard, so the borrow checker
//! rejects any value that would outlive it — the reset happens in the guard's
//! `Drop`, after the compile has converted everything it keeps into its owned
//! form. Nothing may hold a guard beyond one file: a guard parked in a cache
//! keeps an arena checked out, which [`checked_out`] reports and the CLI's
//! per-file `debug_assert` turns into a failure.
use ;
use Deref;
use crateAllocator;
/// Idle arenas kept per worker.
///
/// One is the steady state — a compile acquires, resets and returns before the
/// next file starts. Nested compiles (a template compile inside an SFC compile
/// that already holds one) push the depth up briefly, so the list keeps a few;
/// beyond that the arena is dropped and its memory returned to the process
/// rather than parked forever on a worker that will not need it again.
const MAX_IDLE_PER_WORKER: usize = 4;
thread_local!
/// Takes an arena from this worker's pool, or builds one if the pool is empty.
///
/// The returned guard resets the arena and returns it to the pool when
/// dropped, so the caller's compile owns the whole arena for its duration.
/// Number of arenas this worker has checked out and not yet returned.
///
/// Zero between files: every compile drops its guard before its owned
/// artifacts cross the file boundary, so a non-zero count at a file boundary
/// means a guard was parked somewhere it must not be (a cache, a static, a
/// `mem::forget`) and an arena is pinned across files.
/// Number of arenas parked on this worker, ready for the next file.
/// Drops this worker's idle arenas, returning their memory to the process.
///
/// Nothing in a batch needs this — the pool is bounded by
/// `MAX_IDLE_PER_WORKER` arenas per worker and worker exit drops them — but a
/// long-lived process that has finished compiling (an LSP going idle, a test
/// asserting on pool state) can hand the memory back.
/// An arena borrowed from this worker's pool for one compile.
///
/// Derefs to the [`Allocator`] the compile allocates from; on drop the arena
/// is reset (invalidating every arena-backed value, running every parked
/// destructor) and returned to the pool.