v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
stack-guard.h
Go to the documentation of this file.
1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_EXECUTION_STACK_GUARD_H_
6#define V8_EXECUTION_STACK_GUARD_H_
7
10#include "src/common/globals.h"
11
12namespace v8 {
13namespace internal {
14
15class ExecutionAccess;
16class InterruptsScope;
17class Isolate;
18class Object;
19class RootVisitor;
20
21// StackGuard contains the handling of the limits that are used to limit the
22// number of nested invocations of JavaScript and the stack size used in each
23// invocation.
25 public:
26 StackGuard(const StackGuard&) = delete;
27 StackGuard& operator=(const StackGuard&) = delete;
28
29 explicit StackGuard(Isolate* isolate) : isolate_(isolate) {}
30
31 // Pass the address beyond which the stack should not grow. The stack
32 // is assumed to grow downwards.
33 // When executing on the simulator, we set the stack limits to the limits of
34 // the simulator's stack instead of using {limit}.
35 void SetStackLimit(uintptr_t limit);
36
37 // Try to compare and swap the given jslimit without the ExecutionAccess lock.
38 // Expects potential concurrent writes of the interrupt limit, and of the
39 // interrupt limit only.
40 void SetStackLimitForStackSwitching(uintptr_t limit);
41
42#ifdef USE_SIMULATOR
43 // The simulator uses a separate JS stack. Limits on the JS stack might have
44 // to be adjusted in order to reflect overflows of the C stack, because we
45 // cannot rely on the interleaving of frames on the simulator.
46 void AdjustStackLimitForSimulator();
47 // Reset the limit to the real limit after the stack overflow, if any.
48 void ResetStackLimitForSimulator();
49#endif
50
51 // Threading support.
52 char* ArchiveStackGuard(char* to);
53 char* RestoreStackGuard(char* from);
54 static int ArchiveSpacePerThread() { return sizeof(ThreadLocal); }
55 void FreeThreadResources();
56 // Sets up the default stack guard for this thread.
57 void InitThread(const ExecutionAccess& lock);
58
59 // Code locations that check for interrupts might only handle a subset of the
60 // available interrupts, expressed as an `InterruptLevel`. These levels are
61 // also associated with side effects that are allowed for the respective
62 // level. The levels are inclusive, which is specified using the order in the
63 // enum. For example, a site that handles `kAnyEffect` will also handle the
64 // preceding levels.
65 enum class InterruptLevel { kNoGC, kNoHeapWrites, kAnyEffect };
66 static constexpr int kNumberOfInterruptLevels = 3;
67
68#define INTERRUPT_LIST(V) \
69 V(TERMINATE_EXECUTION, TerminateExecution, 0, InterruptLevel::kNoGC) \
70 V(GC_REQUEST, GC, 1, InterruptLevel::kNoHeapWrites) \
71 V(INSTALL_CODE, InstallCode, 2, InterruptLevel::kAnyEffect) \
72 V(INSTALL_BASELINE_CODE, InstallBaselineCode, 3, InterruptLevel::kAnyEffect) \
73 V(API_INTERRUPT, ApiInterrupt, 4, InterruptLevel::kNoHeapWrites) \
74 V(DEOPT_MARKED_ALLOCATION_SITES, DeoptMarkedAllocationSites, 5, \
75 InterruptLevel::kNoHeapWrites) \
76 V(GROW_SHARED_MEMORY, GrowSharedMemory, 6, InterruptLevel::kAnyEffect) \
77 V(LOG_WASM_CODE, LogWasmCode, 7, InterruptLevel::kAnyEffect) \
78 V(WASM_CODE_GC, WasmCodeGC, 8, InterruptLevel::kNoHeapWrites) \
79 V(INSTALL_MAGLEV_CODE, InstallMaglevCode, 9, InterruptLevel::kAnyEffect) \
80 V(GLOBAL_SAFEPOINT, GlobalSafepoint, 10, InterruptLevel::kNoHeapWrites) \
81 V(START_INCREMENTAL_MARKING, StartIncrementalMarking, 11, \
82 InterruptLevel::kNoHeapWrites)
83
84#define V(NAME, Name, id, interrupt_level) \
85 inline bool Check##Name() { return CheckInterrupt(NAME); } \
86 inline void Request##Name() { RequestInterrupt(NAME); } \
87 inline void Clear##Name() { ClearInterrupt(NAME); }
89#undef V
90
91 // Flag used to set the interrupt causes.
92 enum InterruptFlag : uint32_t {
93#define V(NAME, Name, id, interrupt_level) NAME = (1 << id),
95#undef V
96#define V(NAME, Name, id, interrupt_level) NAME |
97 ALL_INTERRUPTS = INTERRUPT_LIST(V) 0
98#undef V
99 };
100 static_assert(InterruptFlag::ALL_INTERRUPTS <
101 std::numeric_limits<uint32_t>::max());
102
104#define V(NAME, Name, id, interrupt_level) \
105 | (interrupt_level <= level ? NAME : 0)
106 return static_cast<InterruptFlag>(0 INTERRUPT_LIST(V));
107#undef V
108 }
109
110 uintptr_t climit() {
111#ifdef USE_SIMULATOR
112 return thread_local_.climit();
113#else
114 return thread_local_.jslimit();
115#endif
116 }
117 uintptr_t jslimit() { return thread_local_.jslimit(); }
118 // This provides an asynchronous read of the stack limits for the current
119 // thread. There are no locks protecting this, but it is assumed that you
120 // have the global V8 lock if you are using multiple V8 threads.
121 uintptr_t real_climit() {
122#ifdef USE_SIMULATOR
123 return thread_local_.real_climit_;
124#else
125 return thread_local_.real_jslimit_;
126#endif
127 }
128 uintptr_t real_jslimit() { return thread_local_.real_jslimit_; }
130 return reinterpret_cast<Address>(&thread_local_.jslimit_);
131 }
133 return reinterpret_cast<Address>(&thread_local_.real_jslimit_);
134 }
136 return reinterpret_cast<Address>(
137 &thread_local_.interrupt_requested_[static_cast<int>(level)]);
138 }
139
140 static constexpr int jslimit_offset() {
141 return offsetof(StackGuard, thread_local_) +
142 offsetof(ThreadLocal, jslimit_);
143 }
144
145 static constexpr int real_jslimit_offset() {
146 return offsetof(StackGuard, thread_local_) +
147 offsetof(ThreadLocal, real_jslimit_);
148 }
149
150 // If the stack guard is triggered, but it is not an actual
151 // stack overflow, then handle the interruption accordingly.
152 // Only interrupts that match the given `InterruptLevel` will be handled,
153 // leaving other interrupts pending as if this method had not been called.
154 Tagged<Object> HandleInterrupts(
155 InterruptLevel level = InterruptLevel::kAnyEffect);
156
157 // Special case of {HandleInterrupts}: checks for termination requests only.
158 // This is guaranteed to never cause GC, so can be used to interrupt
159 // long-running computations that are not GC-safe.
160 bool HasTerminationRequest();
161
162 static constexpr int kSizeInBytes = 8 * kSystemPointerSize;
163
164 static char* Iterate(RootVisitor* v, char* thread_storage) {
165 return thread_storage + ArchiveSpacePerThread();
166 }
167
168 private:
169 bool CheckInterrupt(InterruptFlag flag);
170 void RequestInterrupt(InterruptFlag flag);
171 void ClearInterrupt(InterruptFlag flag);
172 int FetchAndClearInterrupts(InterruptLevel level);
173
174 void SetStackLimitInternal(const ExecutionAccess& lock, uintptr_t limit,
175 uintptr_t jslimit);
176
177 // You should hold the ExecutionAccess lock when calling this method.
179 return thread_local_.interrupt_flags_ != 0;
180 }
181
182 // You should hold the ExecutionAccess lock when calling this method.
183 inline void update_interrupt_requests_and_stack_limits(
184 const ExecutionAccess& lock);
185
186#if V8_TARGET_ARCH_64_BIT
187 static const uintptr_t kInterruptLimit = uintptr_t{0xfffffffffffffffe};
188 static const uintptr_t kIllegalLimit = uintptr_t{0xfffffffffffffff8};
189#else
190 static const uintptr_t kInterruptLimit = 0xfffffffe;
191 static const uintptr_t kIllegalLimit = 0xfffffff8;
192#endif
193
194 void PushInterruptsScope(InterruptsScope* scope);
195 void PopInterruptsScope();
196
197 class ThreadLocal final {
198 public:
200
201 void Initialize(Isolate* isolate, const ExecutionAccess& lock);
202
203 // The stack limit has two values: the one with the real_ prefix is the
204 // actual stack limit set for the VM. The one without the real_ prefix has
205 // the same value as the actual stack limit except when there is an
206 // interruption (e.g. debug break or preemption) in which case it is lowered
207 // to make stack checks fail. Both the generated code and the runtime system
208 // check against the one without the real_ prefix.
209 // For simulator builds, we also use a separate C++ stack limit.
210
211 // Actual JavaScript stack limit set for the VM.
212 uintptr_t real_jslimit_ = kIllegalLimit;
213#ifdef USE_SIMULATOR
214 // Actual C++ stack limit set for the VM.
215 uintptr_t real_climit_ = kIllegalLimit;
216#else
217 // Padding to match the missing {real_climit_} field, renamed to make it
218 // explicit that this field is unused in this configuration. But the padding
219 // field is needed:
220 // - To keep the isolate's LinearAllocationArea fields from crossing cache
221 // lines (see Isolate::CheckIsolateLayout).
222 // - To ensure that jslimit_offset() is the same in mksnapshot and in V8:
223 // When cross-compiling V8, mksnapshot's host and target may be different
224 // even if they are the same for V8, which results in a different value for
225 // USE_SIMULATOR. Without this padding, this causes the builtins to use the
226 // wrong jslimit_offset() for stack checks.
227 uintptr_t padding1_;
228#endif
229
230 // jslimit_ and climit_ can be read without any lock.
231 // Writing requires the ExecutionAccess lock, or may be updated with a
232 // strong compare-and-swap (e.g. for stack-switching).
233 base::AtomicWord jslimit_ = kIllegalLimit;
234#ifdef USE_SIMULATOR
235 base::AtomicWord climit_ = kIllegalLimit;
236#else
237 // See {padding1_}.
238 uintptr_t padding2_;
239#endif
240
241 uintptr_t jslimit() {
242 return base::bit_cast<uintptr_t>(base::Relaxed_Load(&jslimit_));
243 }
244 void set_jslimit(uintptr_t limit) {
245 return base::Relaxed_Store(&jslimit_,
246 static_cast<base::AtomicWord>(limit));
247 }
248#ifdef USE_SIMULATOR
249 uintptr_t climit() {
250 return base::bit_cast<uintptr_t>(base::Relaxed_Load(&climit_));
251 }
252 void set_climit(uintptr_t limit) {
253 return base::Relaxed_Store(&climit_,
254 static_cast<base::AtomicWord>(limit));
255 }
256#endif
257
258 // Interrupt request bytes can be read without any lock.
259 // Writing requires the ExecutionAccess lock.
260 base::Atomic8 interrupt_requested_[kNumberOfInterruptLevels] = {
261 false, false, false};
262
263 void set_interrupt_requested(InterruptLevel level, bool requested) {
264 base::Relaxed_Store(&interrupt_requested_[static_cast<int>(level)],
265 requested);
266 }
267
269 return base::Relaxed_Load(&interrupt_requested_[static_cast<int>(level)]);
270 }
271
272 InterruptsScope* interrupt_scopes_ = nullptr;
273 uint32_t interrupt_flags_ = 0;
274 };
275
276 // TODO(isolates): Technically this could be calculated directly from a
277 // pointer to StackGuard.
280
281 friend class Isolate;
282 friend class StackLimitCheck;
283 friend class InterruptsScope;
284
285 static_assert(std::is_standard_layout<ThreadLocal>::value);
286};
287
288static_assert(StackGuard::kSizeInBytes == sizeof(StackGuard));
289
290} // namespace internal
291} // namespace v8
292
293#endif // V8_EXECUTION_STACK_GUARD_H_
Isolate * isolate_
#define V(Name)
void set_interrupt_requested(InterruptLevel level, bool requested)
bool has_interrupt_requested(InterruptLevel level)
static constexpr int kSizeInBytes
static char * Iterate(RootVisitor *v, char *thread_storage)
static constexpr int jslimit_offset()
StackGuard & operator=(const StackGuard &)=delete
static constexpr InterruptFlag InterruptLevelMask(InterruptLevel level)
StackGuard(const StackGuard &)=delete
bool has_pending_interrupts(const ExecutionAccess &lock)
Address address_of_real_jslimit()
static int ArchiveSpacePerThread()
Definition stack-guard.h:54
Address address_of_interrupt_request(InterruptLevel level)
StackGuard(Isolate *isolate)
Definition stack-guard.h:29
static constexpr int real_jslimit_offset()
Atomic32 AtomicWord
Definition atomicops.h:76
char Atomic8
Definition atomicops.h:57
constexpr int kSystemPointerSize
Definition globals.h:410
#define V8_EXPORT_PRIVATE
Definition macros.h:460
#define INTERRUPT_LIST(V)
Definition stack-guard.h:68
#define V8_NODISCARD
Definition v8config.h:693