forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathLazyPropertyInlines.h
More file actions
276 lines (255 loc) · 12.9 KB
/
Copy pathLazyPropertyInlines.h
File metadata and controls
276 lines (255 loc) · 12.9 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
/*
* Copyright (C) 2016-2022 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. AND ITS CONTRIBUTORS ``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 ITS 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 "DeferTermination.h"
#include "Heap.h"
#include "LazyProperty.h"
#include "VMTraps.h"
#include <wtf/Condition.h>
#include <wtf/HashMap.h>
#include <wtf/Lock.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Scope.h>
#include <wtf/Seconds.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Threading.h>
namespace JSC {
template<typename OwnerType, typename ElementType>
void LazyProperty<OwnerType, ElementType>::Initializer::set(ElementType* value) const
{
property.set(vm, owner, value);
}
template<typename OwnerType, typename ElementType>
template<typename Func>
void LazyProperty<OwnerType, ElementType>::initLater(const Func&)
{
static_assert(isStatelessLambda<Func>());
// Logically we just want to stuff the function pointer into m_pointer, but then we'd be sad
// because a function pointer is not guaranteed to be a multiple of anything. The tag bits
// may be used for things. We address this problem by indirecting through a global const
// variable. The "theFunc" variable is guaranteed to be native-aligned, i.e. at least a
// multiple of 4.
static constexpr FuncType theFunc = &callFunc<Func>;
WTF::atomicStore(&m_pointer, lazyTag | std::bit_cast<uintptr_t>(&theFunc), std::memory_order_relaxed); // THREADS: see getInitializedOnMainThread().
}
template<typename OwnerType, typename ElementType>
void LazyProperty<OwnerType, ElementType>::setMayBeNull(VM& vm, const OwnerType* owner, ElementType* value)
{
// THREADS: release store — publishes the fully initialized element to
// concurrent relaxed/dependent readers on other mutators.
uintptr_t pointer = std::bit_cast<uintptr_t>(value);
RELEASE_ASSERT(!(pointer & lazyTag));
WTF::atomicStore(&m_pointer, pointer, std::memory_order_release);
vm.writeBarrier(owner, value);
}
template<typename OwnerType, typename ElementType>
void LazyProperty<OwnerType, ElementType>::set(VM& vm, const OwnerType* owner, ElementType* value)
{
RELEASE_ASSERT(value);
setMayBeNull(vm, owner, value);
}
template<typename OwnerType, typename ElementType>
template<typename Visitor>
void LazyProperty<OwnerType, ElementType>::visit(Visitor& visitor)
{
uintptr_t pointer = WTF::atomicLoad(&m_pointer, std::memory_order_relaxed); // THREADS: concurrent marker vs mutator publication.
if (pointer && !(pointer & lazyTag))
visitor.appendUnbarriered(std::bit_cast<ElementType*>(pointer));
}
template<typename OwnerType, typename ElementType>
void LazyProperty<OwnerType, ElementType>::dump(PrintStream& out) const
{
uintptr_t pointer = WTF::atomicLoad(const_cast<uintptr_t*>(&m_pointer), std::memory_order_relaxed);
if (!pointer) {
out.print("<null>");
return;
}
if (pointer & lazyTag) {
out.print("Lazy:", RawHex(pointer & ~lazyTag));
if (pointer & initializingTag)
out.print("(Initializing)");
return;
}
out.print(RawHex(pointer));
}
// UNGIL §K.3 / ANNEX LZ1 (BINDING) side tables: the initializing CAS RECORDS
// the OWNER; foreign threads wait park-capably; OWNER re-entry (and LZ1.2
// cross-thread ownership cycles) return null — exactly the landed recursion
// contract extended per the annex. One process-wide leaf lock: first-touch
// initialization is cold by construction, and LZ2 forbids first-touch sites
// from holding any ranked lock, so this lock is a leaf in every legal
// caller. GIL-on / flag-off: the foreign arms are unreachable (phase-1 GIL
// initializers never yield the GIL across the window), so behavior reduces
// to the landed owner-recursion-null contract.
namespace LazyPropertyInternal {
struct InitTables {
Lock lock;
Condition condition;
UncheckedKeyHashMap<const void*, Thread*> owners; // property address -> winner thread
UncheckedKeyHashMap<Thread*, const void*> waits; // waiter thread -> property it waits on
};
inline InitTables& initTables()
{
static LazyNeverDestroyed<InitTables> tables;
static std::once_flag onceFlag;
std::call_once(onceFlag, [] { tables.construct(); });
return tables.get();
}
} // namespace LazyPropertyInternal
template<typename OwnerType, typename ElementType>
template<typename Func>
ElementType* LazyProperty<OwnerType, ElementType>::callFunc(const Initializer& initializer)
{
LazyProperty& property = initializer.property;
uintptr_t* slot = &property.m_pointer;
// Flag-off / GIL-on: the landed contract verbatim — same-thread recursion
// returns null, no side tables, no process-global lock. The §K.3/LZ1
// protocol below is gilOffProcess-ONLY: its foreign-wait and cycle-walk
// arms are unreachable under the GIL anyway, and entering the process-
// global initTables() lock on every first-touch lazy init in every mode
// would be new unconditional flag-off work (charter violation) and would
// contend ALL VMs in the process on one lock.
if (!VM::isGILOffProcess()) [[likely]] {
uintptr_t current = WTF::atomicLoad(slot, std::memory_order_relaxed);
if (!(current & lazyTag))
return std::bit_cast<ElementType*>(current);
if (current & initializingTag)
return nullptr; // Same-thread recursion: the landed null contract.
WTF::atomicStore(slot, current | initializingTag, std::memory_order_relaxed);
DeferTerminationForAWhile deferTerminationForAWhile { initializer.vm };
callStatelessLambda<void, Func>(initializer);
uintptr_t result = WTF::atomicLoad(slot, std::memory_order_relaxed);
RELEASE_ASSERT(!(result & lazyTag));
RELEASE_ASSERT(!(result & initializingTag));
return std::bit_cast<ElementType*>(result);
}
auto& tables = LazyPropertyInternal::initTables();
Thread* self = &Thread::currentSingleton();
const void* key = static_cast<const void*>(&property);
for (;;) {
uintptr_t current = WTF::atomicLoad(slot, std::memory_order_acquire);
if (!(current & lazyTag))
return std::bit_cast<ElementType*>(current); // A foreign winner published while we raced here.
if (current & initializingTag) {
// Pre-threads this arm was "return nullptr" unconditionally —
// sound only for same-thread recursion (the only way to observe
// the tag under the GIL). GIL-off a FOREIGN first-toucher landing
// here returned null into callers that dereference the result
// unconditionally (e.g. constructObjectFromPropertyDescriptor's
// descriptor-object structures) => SEGV; observed via
// mc-init-cloned-arguments-specials once the §10.4 liveness
// fixes let shared GCs stretch the init window across a pause.
{
Locker locker { tables.lock };
// Re-test under the lock: the winner publishes (release
// store) BEFORE erasing its owner record, so a cleared tag
// or absent record means re-test and adopt.
uintptr_t reread = WTF::atomicLoad(slot, std::memory_order_acquire);
if (!(reread & lazyTag))
return std::bit_cast<ElementType*>(reread);
if (reread & initializingTag) {
// LZ1.2 cycle escape (owner re-entry is the 1-node case):
// follow owner-of -> waits-on edges; reaching SELF means
// this get() must return null to break the cycle. The
// walk is bounded: at most one in-flight init per thread,
// so the chain is a function and terminates at a running
// owner, an absent record, or a repeat.
// Hop bound: a cycle NOT involving self (its members
// detect and null it themselves) must not spin this walk
// under the lock; chains are <= live-thread count, so a
// generous cap only ever truncates a foreign cycle into
// "wait one quantum and re-walk".
const void* probe = key;
for (unsigned hops = 0; hops < 256; ++hops) {
auto ownerIt = tables.owners.find(probe);
if (ownerIt == tables.owners.end())
break; // Abandoned or just-finished: re-test.
Thread* ownerThread = ownerIt->value;
if (ownerThread == self)
return nullptr; // Recursion / cross-thread cycle: landed null contract.
auto waitIt = tables.waits.find(ownerThread);
if (waitIt == tables.waits.end())
break; // Owner is running its initializer: wait below.
probe = waitIt->value;
}
tables.waits.set(self, key); // LZ1.1 wait-for edge, published before the first park quantum.
}
}
// K.3 foreign wait: park-capable bounded quantum with heap access
// RELEASED (a waiter spinning WITH access while the winner's
// allocating initializer triggers a collection is the r6 F2
// three-way deadlock). Re-acquisition funnels through the
// §A.3.2b/F8-gated AHA, which polls BOTH stop families (GSP leg
// + §A.3 stop word + Mode leg) and parks across open windows.
GCClient::Heap* client = GCClient::Heap::currentThreadClient();
bool releasedAccess = client && client->hasHeapAccess();
if (releasedAccess)
client->releaseHeapAccess();
{
Locker locker { tables.lock };
uintptr_t reread = WTF::atomicLoad(slot, std::memory_order_acquire);
if ((reread & lazyTag) && (reread & initializingTag))
tables.condition.waitFor(tables.lock, Seconds::fromMilliseconds(1));
tables.waits.remove(self);
}
if (releasedAccess)
client->acquireHeapAccess();
continue; // Re-test the acquire load.
}
// First-touch claim: CAS records the claim in the slot; the owner
// identity goes to the side table (r16 F2). Weak CAS: spurious
// failure just re-loops.
if (!WTF::atomicCompareExchangeWeak(slot, current, current | initializingTag, std::memory_order_acquire))
continue;
{
Locker locker { tables.lock };
tables.owners.set(key, self);
}
{
// LZ1.3 abandonment + winner cleanup: on ANY exit, erase the
// owner record and wake waiters; if the initializer did NOT
// publish (non-normal exit — exception, termination), restore
// the pre-claim word so a later toucher re-runs it
// ("initializers publish only on success; partial work is
// garbage").
uintptr_t preClaim = current;
auto cleanup = WTF::makeScopeExit([&] {
Locker locker { tables.lock };
uintptr_t now = WTF::atomicLoad(slot, std::memory_order_relaxed);
if (now & lazyTag)
WTF::atomicStore(slot, preClaim, std::memory_order_release);
tables.owners.remove(key);
tables.condition.notifyAll();
});
DeferTerminationForAWhile deferTerminationForAWhile { initializer.vm };
callStatelessLambda<void, Func>(initializer);
uintptr_t result = WTF::atomicLoad(slot, std::memory_order_relaxed);
RELEASE_ASSERT(!(result & lazyTag));
RELEASE_ASSERT(!(result & initializingTag));
return std::bit_cast<ElementType*>(result);
}
}
}
} // namespace JSC