1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
---|---|
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #ifndef RUNTIME_VM_HEAP_SAFEPOINT_H_ |
6 | #define RUNTIME_VM_HEAP_SAFEPOINT_H_ |
7 | |
8 | #include "vm/globals.h" |
9 | #include "vm/isolate.h" |
10 | #include "vm/lockers.h" |
11 | #include "vm/thread.h" |
12 | #include "vm/thread_stack_resource.h" |
13 | |
14 | namespace dart { |
15 | |
16 | // A stack based scope that can be used to perform an operation after getting |
17 | // all threads to a safepoint. At the end of the operation all the threads are |
18 | // resumed. |
19 | class SafepointOperationScope : public ThreadStackResource { |
20 | public: |
21 | explicit SafepointOperationScope(Thread* T); |
22 | ~SafepointOperationScope(); |
23 | |
24 | private: |
25 | DISALLOW_COPY_AND_ASSIGN(SafepointOperationScope); |
26 | }; |
27 | |
28 | // A stack based scope that can be used to perform an operation after getting |
29 | // all threads to a safepoint. At the end of the operation all the threads are |
30 | // resumed. Allocations in the scope will force heap growth. |
31 | class ForceGrowthSafepointOperationScope : public ThreadStackResource { |
32 | public: |
33 | explicit ForceGrowthSafepointOperationScope(Thread* T); |
34 | ~ForceGrowthSafepointOperationScope(); |
35 | |
36 | private: |
37 | bool current_growth_controller_state_; |
38 | |
39 | DISALLOW_COPY_AND_ASSIGN(ForceGrowthSafepointOperationScope); |
40 | }; |
41 | |
42 | // Implements handling of safepoint operations for all threads in an |
43 | // IsolateGroup. |
44 | class SafepointHandler { |
45 | public: |
46 | explicit SafepointHandler(IsolateGroup* I); |
47 | ~SafepointHandler(); |
48 | |
49 | void EnterSafepointUsingLock(Thread* T); |
50 | void ExitSafepointUsingLock(Thread* T); |
51 | |
52 | void BlockForSafepoint(Thread* T); |
53 | |
54 | bool IsOwnedByTheThread(Thread* thread) { return owner_ == thread; } |
55 | |
56 | private: |
57 | void SafepointThreads(Thread* T); |
58 | void ResumeThreads(Thread* T); |
59 | |
60 | IsolateGroup* isolate_group() const { return isolate_group_; } |
61 | Monitor* threads_lock() const { return isolate_group_->threads_lock(); } |
62 | bool SafepointInProgress() const { |
63 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
64 | return ((safepoint_operation_count_ > 0) && (owner_ != NULL)); |
65 | } |
66 | void SetSafepointInProgress(Thread* T) { |
67 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
68 | ASSERT(owner_ == NULL); |
69 | ASSERT(safepoint_operation_count_ == 0); |
70 | safepoint_operation_count_ = 1; |
71 | owner_ = T; |
72 | } |
73 | void ResetSafepointInProgress(Thread* T) { |
74 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
75 | ASSERT(owner_ == T); |
76 | ASSERT(safepoint_operation_count_ == 1); |
77 | safepoint_operation_count_ = 0; |
78 | owner_ = NULL; |
79 | } |
80 | int32_t safepoint_operation_count() const { |
81 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
82 | return safepoint_operation_count_; |
83 | } |
84 | void increment_safepoint_operation_count() { |
85 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
86 | ASSERT(safepoint_operation_count_ < kMaxInt32); |
87 | safepoint_operation_count_ += 1; |
88 | } |
89 | void decrement_safepoint_operation_count() { |
90 | ASSERT(threads_lock()->IsOwnedByCurrentThread()); |
91 | ASSERT(safepoint_operation_count_ > 0); |
92 | safepoint_operation_count_ -= 1; |
93 | } |
94 | |
95 | IsolateGroup* isolate_group_; |
96 | |
97 | // Monitor used by thread initiating a safepoint operation to track threads |
98 | // not at a safepoint and wait for these threads to reach a safepoint. |
99 | Monitor safepoint_lock_; |
100 | int32_t number_threads_not_at_safepoint_; |
101 | |
102 | // Count that indicates if a safepoint operation is currently in progress |
103 | // and also tracks the number of recursive safepoint operations on the |
104 | // same thread. |
105 | int32_t safepoint_operation_count_; |
106 | |
107 | // If a safepoint operation is currently in progress, this field contains |
108 | // the thread that initiated the safepoint operation, otherwise it is NULL. |
109 | Thread* owner_; |
110 | |
111 | friend class Isolate; |
112 | friend class IsolateGroup; |
113 | friend class SafepointOperationScope; |
114 | friend class ForceGrowthSafepointOperationScope; |
115 | friend class HeapIterationScope; |
116 | }; |
117 | |
118 | /* |
119 | * Set of StackResource classes to track thread execution state transitions: |
120 | * |
121 | * kThreadInGenerated transitioning to |
122 | * ==> kThreadInVM: |
123 | * - set_execution_state(kThreadInVM). |
124 | * - block if safepoint is requested. |
125 | * ==> kThreadInNative: |
126 | * - set_execution_state(kThreadInNative). |
127 | * - EnterSafepoint(). |
128 | * ==> kThreadInBlockedState: |
129 | * - Invalid transition |
130 | * |
131 | * kThreadInVM transitioning to |
132 | * ==> kThreadInGenerated |
133 | * - set_execution_state(kThreadInGenerated). |
134 | * ==> kThreadInNative |
135 | * - set_execution_state(kThreadInNative). |
136 | * - EnterSafepoint. |
137 | * ==> kThreadInBlockedState |
138 | * - set_execution_state(kThreadInBlockedState). |
139 | * - EnterSafepoint. |
140 | * |
141 | * kThreadInNative transitioning to |
142 | * ==> kThreadInGenerated |
143 | * - ExitSafepoint. |
144 | * - set_execution_state(kThreadInGenerated). |
145 | * ==> kThreadInVM |
146 | * - ExitSafepoint. |
147 | * - set_execution_state(kThreadInVM). |
148 | * ==> kThreadInBlocked |
149 | * - Invalid transition. |
150 | * |
151 | * kThreadInBlocked transitioning to |
152 | * ==> kThreadInVM |
153 | * - ExitSafepoint. |
154 | * - set_execution_state(kThreadInVM). |
155 | * ==> kThreadInNative |
156 | * - Invalid transition. |
157 | * ==> kThreadInGenerated |
158 | * - Invalid transition. |
159 | */ |
160 | class TransitionSafepointState : public ThreadStackResource { |
161 | public: |
162 | explicit TransitionSafepointState(Thread* T) : ThreadStackResource(T) {} |
163 | ~TransitionSafepointState() {} |
164 | |
165 | SafepointHandler* handler() const { |
166 | ASSERT(thread()->isolate() != NULL); |
167 | ASSERT(thread()->isolate()->safepoint_handler() != NULL); |
168 | return thread()->isolate()->safepoint_handler(); |
169 | } |
170 | |
171 | private: |
172 | DISALLOW_COPY_AND_ASSIGN(TransitionSafepointState); |
173 | }; |
174 | |
175 | // TransitionGeneratedToVM is used to transition the safepoint state of a |
176 | // thread from "running generated code" to "running vm code" and ensures |
177 | // that the state is reverted back to "running generated code" when |
178 | // exiting the scope/frame. |
179 | class TransitionGeneratedToVM : public TransitionSafepointState { |
180 | public: |
181 | explicit TransitionGeneratedToVM(Thread* T) : TransitionSafepointState(T) { |
182 | ASSERT(T == Thread::Current()); |
183 | ASSERT(T->execution_state() == Thread::kThreadInGenerated); |
184 | T->set_execution_state(Thread::kThreadInVM); |
185 | // Fast check to see if a safepoint is requested or not. |
186 | // We do the more expensive operation of blocking the thread |
187 | // only if a safepoint is requested. |
188 | if (T->IsSafepointRequested()) { |
189 | handler()->BlockForSafepoint(T); |
190 | } |
191 | } |
192 | |
193 | ~TransitionGeneratedToVM() { |
194 | ASSERT(thread()->execution_state() == Thread::kThreadInVM); |
195 | thread()->set_execution_state(Thread::kThreadInGenerated); |
196 | } |
197 | |
198 | private: |
199 | DISALLOW_COPY_AND_ASSIGN(TransitionGeneratedToVM); |
200 | }; |
201 | |
202 | // TransitionGeneratedToNative is used to transition the safepoint state of a |
203 | // thread from "running generated code" to "running native code" and ensures |
204 | // that the state is reverted back to "running generated code" when |
205 | // exiting the scope/frame. |
206 | class TransitionGeneratedToNative : public TransitionSafepointState { |
207 | public: |
208 | explicit TransitionGeneratedToNative(Thread* T) |
209 | : TransitionSafepointState(T) { |
210 | // Native code is considered to be at a safepoint and so we mark it |
211 | // accordingly. |
212 | ASSERT(T->execution_state() == Thread::kThreadInGenerated); |
213 | T->set_execution_state(Thread::kThreadInNative); |
214 | T->EnterSafepoint(); |
215 | } |
216 | |
217 | ~TransitionGeneratedToNative() { |
218 | // We are returning to generated code and so we are not at a safepoint |
219 | // anymore. |
220 | ASSERT(thread()->execution_state() == Thread::kThreadInNative); |
221 | thread()->ExitSafepoint(); |
222 | thread()->set_execution_state(Thread::kThreadInGenerated); |
223 | } |
224 | |
225 | private: |
226 | DISALLOW_COPY_AND_ASSIGN(TransitionGeneratedToNative); |
227 | }; |
228 | |
229 | // TransitionVMToBlocked is used to transition the safepoint state of a |
230 | // thread from "running vm code" to "blocked on a monitor" and ensures |
231 | // that the state is reverted back to "running vm code" when |
232 | // exiting the scope/frame. |
233 | class TransitionVMToBlocked : public TransitionSafepointState { |
234 | public: |
235 | explicit TransitionVMToBlocked(Thread* T) : TransitionSafepointState(T) { |
236 | // A thread blocked on a monitor is considered to be at a safepoint. |
237 | ASSERT(T->execution_state() == Thread::kThreadInVM); |
238 | T->set_execution_state(Thread::kThreadInBlockedState); |
239 | T->EnterSafepoint(); |
240 | } |
241 | |
242 | ~TransitionVMToBlocked() { |
243 | // We are returning to vm code and so we are not at a safepoint anymore. |
244 | ASSERT(thread()->execution_state() == Thread::kThreadInBlockedState); |
245 | thread()->ExitSafepoint(); |
246 | thread()->set_execution_state(Thread::kThreadInVM); |
247 | } |
248 | |
249 | private: |
250 | DISALLOW_COPY_AND_ASSIGN(TransitionVMToBlocked); |
251 | }; |
252 | |
253 | // TransitionVMToNative is used to transition the safepoint state of a |
254 | // thread from "running vm code" to "running native code" and ensures |
255 | // that the state is reverted back to "running vm code" when |
256 | // exiting the scope/frame. |
257 | class TransitionVMToNative : public TransitionSafepointState { |
258 | public: |
259 | explicit TransitionVMToNative(Thread* T) : TransitionSafepointState(T) { |
260 | // A thread running native code is considered to be at a safepoint. |
261 | ASSERT(T->execution_state() == Thread::kThreadInVM); |
262 | T->set_execution_state(Thread::kThreadInNative); |
263 | T->EnterSafepoint(); |
264 | } |
265 | |
266 | ~TransitionVMToNative() { |
267 | // We are returning to vm code and so we are not at a safepoint anymore. |
268 | ASSERT(thread()->execution_state() == Thread::kThreadInNative); |
269 | thread()->ExitSafepoint(); |
270 | thread()->set_execution_state(Thread::kThreadInVM); |
271 | } |
272 | |
273 | private: |
274 | DISALLOW_COPY_AND_ASSIGN(TransitionVMToNative); |
275 | }; |
276 | |
277 | // TransitionVMToGenerated is used to transition the safepoint state of a |
278 | // thread from "running vm code" to "running generated code" and ensures |
279 | // that the state is reverted back to "running vm code" when |
280 | // exiting the scope/frame. |
281 | class TransitionVMToGenerated : public TransitionSafepointState { |
282 | public: |
283 | explicit TransitionVMToGenerated(Thread* T) : TransitionSafepointState(T) { |
284 | ASSERT(T == Thread::Current()); |
285 | ASSERT(T->execution_state() == Thread::kThreadInVM); |
286 | T->set_execution_state(Thread::kThreadInGenerated); |
287 | } |
288 | |
289 | ~TransitionVMToGenerated() { |
290 | ASSERT(thread()->execution_state() == Thread::kThreadInGenerated); |
291 | thread()->set_execution_state(Thread::kThreadInVM); |
292 | // Fast check to see if a safepoint is requested or not. |
293 | // We do the more expensive operation of blocking the thread |
294 | // only if a safepoint is requested. |
295 | if (thread()->IsSafepointRequested()) { |
296 | handler()->BlockForSafepoint(thread()); |
297 | } |
298 | } |
299 | |
300 | private: |
301 | DISALLOW_COPY_AND_ASSIGN(TransitionVMToGenerated); |
302 | }; |
303 | |
304 | // TransitionNativeToVM is used to transition the safepoint state of a |
305 | // thread from "running native code" to "running vm code" and ensures |
306 | // that the state is reverted back to "running native code" when |
307 | // exiting the scope/frame. |
308 | class TransitionNativeToVM : public TransitionSafepointState { |
309 | public: |
310 | explicit TransitionNativeToVM(Thread* T) : TransitionSafepointState(T) { |
311 | // We are about to execute vm code and so we are not at a safepoint anymore. |
312 | ASSERT(T->execution_state() == Thread::kThreadInNative); |
313 | if (T->no_callback_scope_depth() == 0) { |
314 | T->ExitSafepoint(); |
315 | } |
316 | T->set_execution_state(Thread::kThreadInVM); |
317 | } |
318 | |
319 | ~TransitionNativeToVM() { |
320 | // We are returning to native code and so we are at a safepoint. |
321 | ASSERT(thread()->execution_state() == Thread::kThreadInVM); |
322 | thread()->set_execution_state(Thread::kThreadInNative); |
323 | if (thread()->no_callback_scope_depth() == 0) { |
324 | thread()->EnterSafepoint(); |
325 | } |
326 | } |
327 | |
328 | private: |
329 | DISALLOW_COPY_AND_ASSIGN(TransitionNativeToVM); |
330 | }; |
331 | |
332 | // TransitionToGenerated is used to transition the safepoint state of a |
333 | // thread from "running vm code" or "running native code" to |
334 | // "running generated code" and ensures that the state is reverted back |
335 | // to "running vm code" or "running native code" when exiting the |
336 | // scope/frame. |
337 | class TransitionToGenerated : public TransitionSafepointState { |
338 | public: |
339 | explicit TransitionToGenerated(Thread* T) |
340 | : TransitionSafepointState(T), execution_state_(T->execution_state()) { |
341 | ASSERT(T == Thread::Current()); |
342 | ASSERT((execution_state_ == Thread::kThreadInVM) || |
343 | (execution_state_ == Thread::kThreadInNative)); |
344 | if (execution_state_ == Thread::kThreadInNative) { |
345 | T->ExitSafepoint(); |
346 | } |
347 | T->set_execution_state(Thread::kThreadInGenerated); |
348 | } |
349 | |
350 | ~TransitionToGenerated() { |
351 | ASSERT(thread()->execution_state() == Thread::kThreadInGenerated); |
352 | if (execution_state_ == Thread::kThreadInNative) { |
353 | thread()->set_execution_state(Thread::kThreadInNative); |
354 | thread()->EnterSafepoint(); |
355 | } else { |
356 | ASSERT(execution_state_ == Thread::kThreadInVM); |
357 | thread()->set_execution_state(Thread::kThreadInVM); |
358 | } |
359 | } |
360 | |
361 | private: |
362 | uint32_t execution_state_; |
363 | DISALLOW_COPY_AND_ASSIGN(TransitionToGenerated); |
364 | }; |
365 | |
366 | // TransitionToVM is used to transition the safepoint state of a |
367 | // thread from "running native code" to "running vm code" |
368 | // and ensures that the state is reverted back to "running native code" |
369 | // when exiting the scope/frame. |
370 | // This transition helper is mainly used in the error path of the |
371 | // Dart API implementations where we sometimes do not have an explicit |
372 | // transition set up. |
373 | class TransitionToVM : public TransitionSafepointState { |
374 | public: |
375 | explicit TransitionToVM(Thread* T) |
376 | : TransitionSafepointState(T), execution_state_(T->execution_state()) { |
377 | ASSERT(T == Thread::Current()); |
378 | ASSERT((execution_state_ == Thread::kThreadInVM) || |
379 | (execution_state_ == Thread::kThreadInNative)); |
380 | if (execution_state_ == Thread::kThreadInNative) { |
381 | T->ExitSafepoint(); |
382 | T->set_execution_state(Thread::kThreadInVM); |
383 | } |
384 | ASSERT(T->execution_state() == Thread::kThreadInVM); |
385 | } |
386 | |
387 | ~TransitionToVM() { |
388 | ASSERT(thread()->execution_state() == Thread::kThreadInVM); |
389 | if (execution_state_ == Thread::kThreadInNative) { |
390 | thread()->set_execution_state(Thread::kThreadInNative); |
391 | thread()->EnterSafepoint(); |
392 | } |
393 | } |
394 | |
395 | private: |
396 | uint32_t execution_state_; |
397 | DISALLOW_COPY_AND_ASSIGN(TransitionToVM); |
398 | }; |
399 | |
400 | } // namespace dart |
401 | |
402 | #endif // RUNTIME_VM_HEAP_SAFEPOINT_H_ |
403 |