1 | // Copyright (c) 2012, 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 | #include <map> |
6 | #include <memory> |
7 | #include <set> |
8 | #include <string> |
9 | |
10 | #include "platform/globals.h" |
11 | |
12 | #include "platform/assert.h" |
13 | #include "vm/class_finalizer.h" |
14 | #include "vm/dart_api_impl.h" |
15 | #include "vm/globals.h" |
16 | #include "vm/heap/become.h" |
17 | #include "vm/heap/heap.h" |
18 | #include "vm/message_handler.h" |
19 | #include "vm/object_graph.h" |
20 | #include "vm/port.h" |
21 | #include "vm/symbols.h" |
22 | #include "vm/unit_test.h" |
23 | |
24 | namespace dart { |
25 | |
26 | TEST_CASE(OldGC) { |
27 | const char* kScriptChars = |
28 | "main() {\n" |
29 | " return [1, 2, 3];\n" |
30 | "}\n" ; |
31 | NOT_IN_PRODUCT(FLAG_verbose_gc = true); |
32 | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); |
33 | Dart_Handle result = Dart_Invoke(lib, NewString("main" ), 0, NULL); |
34 | |
35 | EXPECT_VALID(result); |
36 | EXPECT(!Dart_IsNull(result)); |
37 | EXPECT(Dart_IsList(result)); |
38 | TransitionNativeToVM transition(thread); |
39 | GCTestHelper::CollectOldSpace(); |
40 | } |
41 | |
42 | #if !defined(PRODUCT) |
43 | TEST_CASE(OldGC_Unsync) { |
44 | // Finalize any GC in progress as it is unsafe to change FLAG_marker_tasks |
45 | // when incremental marking is in progress. |
46 | { |
47 | TransitionNativeToVM transition(thread); |
48 | GCTestHelper::CollectAllGarbage(); |
49 | } |
50 | FLAG_marker_tasks = 0; |
51 | |
52 | const char* kScriptChars = |
53 | "main() {\n" |
54 | " return [1, 2, 3];\n" |
55 | "}\n" ; |
56 | FLAG_verbose_gc = true; |
57 | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); |
58 | Dart_Handle result = Dart_Invoke(lib, NewString("main" ), 0, NULL); |
59 | |
60 | EXPECT_VALID(result); |
61 | EXPECT(!Dart_IsNull(result)); |
62 | EXPECT(Dart_IsList(result)); |
63 | TransitionNativeToVM transition(thread); |
64 | GCTestHelper::CollectOldSpace(); |
65 | } |
66 | #endif // !defined(PRODUCT) |
67 | |
68 | TEST_CASE(LargeSweep) { |
69 | const char* kScriptChars = |
70 | "main() {\n" |
71 | " return List.filled(8 * 1024 * 1024, null);\n" |
72 | "}\n" ; |
73 | NOT_IN_PRODUCT(FLAG_verbose_gc = true); |
74 | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); |
75 | Dart_EnterScope(); |
76 | Dart_Handle result = Dart_Invoke(lib, NewString("main" ), 0, NULL); |
77 | |
78 | EXPECT_VALID(result); |
79 | EXPECT(!Dart_IsNull(result)); |
80 | EXPECT(Dart_IsList(result)); |
81 | { |
82 | TransitionNativeToVM transition(thread); |
83 | GCTestHelper::CollectOldSpace(); |
84 | } |
85 | Dart_ExitScope(); |
86 | { |
87 | TransitionNativeToVM transition(thread); |
88 | GCTestHelper::CollectOldSpace(); |
89 | } |
90 | } |
91 | |
92 | #ifndef PRODUCT |
93 | static ClassPtr GetClass(const Library& lib, const char* name) { |
94 | const Class& cls = Class::Handle( |
95 | lib.LookupClass(String::Handle(Symbols::New(Thread::Current(), name)))); |
96 | EXPECT(!cls.IsNull()); // No ambiguity error expected. |
97 | return cls.raw(); |
98 | } |
99 | |
100 | TEST_CASE(ClassHeapStats) { |
101 | const char* kScriptChars = |
102 | "class A {\n" |
103 | " var a;\n" |
104 | " var b;\n" |
105 | "}\n" |
106 | "" |
107 | "main() {\n" |
108 | " var x = new A();\n" |
109 | " return new A();\n" |
110 | "}\n" ; |
111 | Dart_Handle h_lib = TestCase::LoadTestScript(kScriptChars, NULL); |
112 | Isolate* isolate = Isolate::Current(); |
113 | ClassTable* class_table = isolate->class_table(); |
114 | { |
115 | // GC before main so allocations during the tests don't cause unexpected GC. |
116 | TransitionNativeToVM transition(thread); |
117 | GCTestHelper::CollectAllGarbage(); |
118 | } |
119 | Dart_EnterScope(); |
120 | Dart_Handle result = Dart_Invoke(h_lib, NewString("main" ), 0, NULL); |
121 | EXPECT_VALID(result); |
122 | EXPECT(!Dart_IsNull(result)); |
123 | intptr_t cid; |
124 | { |
125 | TransitionNativeToVM transition(thread); |
126 | Library& lib = Library::Handle(); |
127 | lib ^= Api::UnwrapHandle(h_lib); |
128 | EXPECT(!lib.IsNull()); |
129 | const Class& cls = Class::Handle(GetClass(lib, "A" )); |
130 | ASSERT(!cls.IsNull()); |
131 | cid = cls.id(); |
132 | |
133 | { |
134 | // Verify preconditions: allocated twice in new space. |
135 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
136 | HeapIterationScope iter(thread); |
137 | iter.IterateObjects(&visitor); |
138 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
139 | EXPECT_EQ(2, visitor.new_count_[cid]); |
140 | EXPECT_EQ(0, visitor.old_count_[cid]); |
141 | } |
142 | |
143 | // Perform GC. |
144 | GCTestHelper::CollectNewSpace(); |
145 | |
146 | { |
147 | // Verify postconditions: Only one survived. |
148 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
149 | HeapIterationScope iter(thread); |
150 | iter.IterateObjects(&visitor); |
151 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
152 | EXPECT_EQ(1, visitor.new_count_[cid]); |
153 | EXPECT_EQ(0, visitor.old_count_[cid]); |
154 | } |
155 | |
156 | // Perform GC. The following is heavily dependent on the behaviour |
157 | // of the GC: Retained instance of A will be promoted. |
158 | GCTestHelper::CollectNewSpace(); |
159 | |
160 | { |
161 | // Verify postconditions: One promoted instance. |
162 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
163 | HeapIterationScope iter(thread); |
164 | iter.IterateObjects(&visitor); |
165 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
166 | EXPECT_EQ(0, visitor.new_count_[cid]); |
167 | EXPECT_EQ(1, visitor.old_count_[cid]); |
168 | } |
169 | |
170 | // Perform a GC on new space. |
171 | GCTestHelper::CollectNewSpace(); |
172 | |
173 | { |
174 | // Verify postconditions: |
175 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
176 | HeapIterationScope iter(thread); |
177 | iter.IterateObjects(&visitor); |
178 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
179 | EXPECT_EQ(0, visitor.new_count_[cid]); |
180 | EXPECT_EQ(1, visitor.old_count_[cid]); |
181 | } |
182 | |
183 | GCTestHelper::CollectOldSpace(); |
184 | |
185 | { |
186 | // Verify postconditions: |
187 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
188 | HeapIterationScope iter(thread); |
189 | iter.IterateObjects(&visitor); |
190 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
191 | EXPECT_EQ(0, visitor.new_count_[cid]); |
192 | EXPECT_EQ(1, visitor.old_count_[cid]); |
193 | } |
194 | } |
195 | // Exit scope, freeing instance. |
196 | Dart_ExitScope(); |
197 | { |
198 | TransitionNativeToVM transition(thread); |
199 | // Perform GC. |
200 | GCTestHelper::CollectOldSpace(); |
201 | { |
202 | // Verify postconditions: |
203 | CountObjectsVisitor visitor(thread, class_table->NumCids()); |
204 | HeapIterationScope iter(thread); |
205 | iter.IterateObjects(&visitor); |
206 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
207 | EXPECT_EQ(0, visitor.new_count_[cid]); |
208 | EXPECT_EQ(0, visitor.old_count_[cid]); |
209 | } |
210 | } |
211 | } |
212 | #endif // !PRODUCT |
213 | |
214 | class FindOnly : public FindObjectVisitor { |
215 | public: |
216 | explicit FindOnly(ObjectPtr target) : target_(target) { |
217 | #if defined(DEBUG) |
218 | EXPECT_GT(Thread::Current()->no_safepoint_scope_depth(), 0); |
219 | #endif |
220 | } |
221 | virtual ~FindOnly() {} |
222 | |
223 | virtual bool FindObject(ObjectPtr obj) const { return obj == target_; } |
224 | |
225 | private: |
226 | ObjectPtr target_; |
227 | }; |
228 | |
229 | class FindNothing : public FindObjectVisitor { |
230 | public: |
231 | FindNothing() {} |
232 | virtual ~FindNothing() {} |
233 | virtual bool FindObject(ObjectPtr obj) const { return false; } |
234 | }; |
235 | |
236 | ISOLATE_UNIT_TEST_CASE(FindObject) { |
237 | Isolate* isolate = Isolate::Current(); |
238 | Heap* heap = isolate->heap(); |
239 | Heap::Space spaces[2] = {Heap::kOld, Heap::kNew}; |
240 | for (size_t space = 0; space < ARRAY_SIZE(spaces); ++space) { |
241 | const String& obj = String::Handle(String::New("x" , spaces[space])); |
242 | { |
243 | HeapIterationScope iteration(thread); |
244 | NoSafepointScope no_safepoint; |
245 | FindOnly find_only(obj.raw()); |
246 | EXPECT(obj.raw() == heap->FindObject(&find_only)); |
247 | } |
248 | } |
249 | { |
250 | HeapIterationScope iteration(thread); |
251 | NoSafepointScope no_safepoint; |
252 | FindNothing find_nothing; |
253 | EXPECT(Object::null() == heap->FindObject(&find_nothing)); |
254 | } |
255 | } |
256 | |
257 | ISOLATE_UNIT_TEST_CASE(IterateReadOnly) { |
258 | const String& obj = String::Handle(String::New("x" , Heap::kOld)); |
259 | |
260 | // It is not safe to make the heap read-only if marking or sweeping is in |
261 | // progress. |
262 | GCTestHelper::WaitForGCTasks(); |
263 | |
264 | Heap* heap = Thread::Current()->isolate()->heap(); |
265 | EXPECT(heap->Contains(ObjectLayout::ToAddr(obj.raw()))); |
266 | heap->WriteProtect(true); |
267 | EXPECT(heap->Contains(ObjectLayout::ToAddr(obj.raw()))); |
268 | heap->WriteProtect(false); |
269 | EXPECT(heap->Contains(ObjectLayout::ToAddr(obj.raw()))); |
270 | } |
271 | |
272 | void TestBecomeForward(Heap::Space before_space, Heap::Space after_space) { |
273 | const String& before_obj = String::Handle(String::New("old" , before_space)); |
274 | const String& after_obj = String::Handle(String::New("new" , after_space)); |
275 | |
276 | EXPECT(before_obj.raw() != after_obj.raw()); |
277 | |
278 | // Allocate the arrays in old space to test the remembered set. |
279 | const Array& before = Array::Handle(Array::New(1, Heap::kOld)); |
280 | before.SetAt(0, before_obj); |
281 | const Array& after = Array::Handle(Array::New(1, Heap::kOld)); |
282 | after.SetAt(0, after_obj); |
283 | |
284 | Become::ElementsForwardIdentity(before, after); |
285 | |
286 | EXPECT(before_obj.raw() == after_obj.raw()); |
287 | |
288 | GCTestHelper::CollectAllGarbage(); |
289 | |
290 | EXPECT(before_obj.raw() == after_obj.raw()); |
291 | } |
292 | |
293 | ISOLATE_UNIT_TEST_CASE(BecomeFowardOldToOld) { |
294 | TestBecomeForward(Heap::kOld, Heap::kOld); |
295 | } |
296 | |
297 | ISOLATE_UNIT_TEST_CASE(BecomeFowardNewToNew) { |
298 | TestBecomeForward(Heap::kNew, Heap::kNew); |
299 | } |
300 | |
301 | ISOLATE_UNIT_TEST_CASE(BecomeFowardOldToNew) { |
302 | TestBecomeForward(Heap::kOld, Heap::kNew); |
303 | } |
304 | |
305 | ISOLATE_UNIT_TEST_CASE(BecomeFowardNewToOld) { |
306 | TestBecomeForward(Heap::kNew, Heap::kOld); |
307 | } |
308 | |
309 | ISOLATE_UNIT_TEST_CASE(BecomeForwardPeer) { |
310 | Isolate* isolate = Isolate::Current(); |
311 | Heap* heap = isolate->heap(); |
312 | |
313 | const Array& before_obj = Array::Handle(Array::New(0, Heap::kOld)); |
314 | const Array& after_obj = Array::Handle(Array::New(0, Heap::kOld)); |
315 | EXPECT(before_obj.raw() != after_obj.raw()); |
316 | |
317 | void* peer = reinterpret_cast<void*>(42); |
318 | void* no_peer = reinterpret_cast<void*>(0); |
319 | heap->SetPeer(before_obj.raw(), peer); |
320 | EXPECT_EQ(peer, heap->GetPeer(before_obj.raw())); |
321 | EXPECT_EQ(no_peer, heap->GetPeer(after_obj.raw())); |
322 | |
323 | const Array& before = Array::Handle(Array::New(1, Heap::kOld)); |
324 | before.SetAt(0, before_obj); |
325 | const Array& after = Array::Handle(Array::New(1, Heap::kOld)); |
326 | after.SetAt(0, after_obj); |
327 | Become::ElementsForwardIdentity(before, after); |
328 | |
329 | EXPECT(before_obj.raw() == after_obj.raw()); |
330 | EXPECT_EQ(peer, heap->GetPeer(before_obj.raw())); |
331 | EXPECT_EQ(peer, heap->GetPeer(after_obj.raw())); |
332 | } |
333 | |
334 | ISOLATE_UNIT_TEST_CASE(BecomeForwardRememberedObject) { |
335 | const String& new_element = String::Handle(String::New("new" , Heap::kNew)); |
336 | const String& old_element = String::Handle(String::New("old" , Heap::kOld)); |
337 | const Array& before_obj = Array::Handle(Array::New(1, Heap::kOld)); |
338 | const Array& after_obj = Array::Handle(Array::New(1, Heap::kOld)); |
339 | before_obj.SetAt(0, new_element); |
340 | after_obj.SetAt(0, old_element); |
341 | EXPECT(before_obj.raw()->ptr()->IsRemembered()); |
342 | EXPECT(!after_obj.raw()->ptr()->IsRemembered()); |
343 | |
344 | EXPECT(before_obj.raw() != after_obj.raw()); |
345 | |
346 | const Array& before = Array::Handle(Array::New(1, Heap::kOld)); |
347 | before.SetAt(0, before_obj); |
348 | const Array& after = Array::Handle(Array::New(1, Heap::kOld)); |
349 | after.SetAt(0, after_obj); |
350 | |
351 | Become::ElementsForwardIdentity(before, after); |
352 | |
353 | EXPECT(before_obj.raw() == after_obj.raw()); |
354 | EXPECT(!after_obj.raw()->ptr()->IsRemembered()); |
355 | |
356 | GCTestHelper::CollectAllGarbage(); |
357 | |
358 | EXPECT(before_obj.raw() == after_obj.raw()); |
359 | } |
360 | |
361 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadOldToNew) { |
362 | Isolate* isolate = Isolate::Current(); |
363 | Heap* heap = isolate->heap(); |
364 | |
365 | heap->CollectAllGarbage(); |
366 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
367 | intptr_t size_before = |
368 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
369 | |
370 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
371 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
372 | old.SetAt(0, neu); |
373 | old = Array::null(); |
374 | neu = Array::null(); |
375 | |
376 | heap->CollectAllGarbage(); |
377 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
378 | |
379 | intptr_t size_after = |
380 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
381 | |
382 | EXPECT_EQ(size_before, size_after); |
383 | } |
384 | |
385 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadNewToOld) { |
386 | Isolate* isolate = Isolate::Current(); |
387 | Heap* heap = isolate->heap(); |
388 | |
389 | heap->CollectAllGarbage(); |
390 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
391 | intptr_t size_before = |
392 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
393 | |
394 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
395 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
396 | neu.SetAt(0, old); |
397 | old = Array::null(); |
398 | neu = Array::null(); |
399 | |
400 | heap->CollectAllGarbage(); |
401 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
402 | |
403 | intptr_t size_after = |
404 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
405 | |
406 | EXPECT_EQ(size_before, size_after); |
407 | } |
408 | |
409 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadGenCycle) { |
410 | Isolate* isolate = Isolate::Current(); |
411 | Heap* heap = isolate->heap(); |
412 | |
413 | heap->CollectAllGarbage(); |
414 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
415 | intptr_t size_before = |
416 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
417 | |
418 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
419 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
420 | neu.SetAt(0, old); |
421 | old.SetAt(0, neu); |
422 | old = Array::null(); |
423 | neu = Array::null(); |
424 | |
425 | heap->CollectAllGarbage(); |
426 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
427 | |
428 | intptr_t size_after = |
429 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
430 | |
431 | EXPECT_EQ(size_before, size_after); |
432 | } |
433 | |
434 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewToOld) { |
435 | Isolate* isolate = Isolate::Current(); |
436 | Heap* heap = isolate->heap(); |
437 | |
438 | heap->CollectAllGarbage(); |
439 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
440 | intptr_t size_before = |
441 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
442 | |
443 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
444 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
445 | neu.SetAt(0, old); |
446 | old = Array::null(); |
447 | |
448 | heap->CollectAllGarbage(); |
449 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
450 | |
451 | intptr_t size_after = |
452 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
453 | |
454 | EXPECT(size_before < size_after); |
455 | } |
456 | |
457 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldToNew) { |
458 | Isolate* isolate = Isolate::Current(); |
459 | Heap* heap = isolate->heap(); |
460 | |
461 | heap->CollectAllGarbage(); |
462 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
463 | intptr_t size_before = |
464 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
465 | |
466 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
467 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
468 | old.SetAt(0, neu); |
469 | neu = Array::null(); |
470 | |
471 | heap->CollectAllGarbage(); |
472 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
473 | |
474 | intptr_t size_after = |
475 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
476 | |
477 | EXPECT(size_before < size_after); |
478 | } |
479 | |
480 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldDeadNew) { |
481 | Isolate* isolate = Isolate::Current(); |
482 | Heap* heap = isolate->heap(); |
483 | |
484 | heap->CollectAllGarbage(); |
485 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
486 | intptr_t size_before = |
487 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
488 | |
489 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
490 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
491 | neu = Array::null(); |
492 | old.SetAt(0, old); |
493 | |
494 | heap->CollectAllGarbage(); |
495 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
496 | |
497 | intptr_t size_after = |
498 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
499 | |
500 | EXPECT(size_before < size_after); |
501 | } |
502 | |
503 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewDeadOld) { |
504 | Isolate* isolate = Isolate::Current(); |
505 | Heap* heap = isolate->heap(); |
506 | |
507 | heap->CollectAllGarbage(); |
508 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
509 | intptr_t size_before = |
510 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
511 | |
512 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
513 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
514 | old = Array::null(); |
515 | neu.SetAt(0, neu); |
516 | |
517 | heap->CollectAllGarbage(); |
518 | heap->WaitForMarkerTasks(thread); // Finalize marking to get live size. |
519 | |
520 | intptr_t size_after = |
521 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
522 | |
523 | EXPECT(size_before < size_after); |
524 | } |
525 | |
526 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewToOldChain) { |
527 | Isolate* isolate = Isolate::Current(); |
528 | Heap* heap = isolate->heap(); |
529 | |
530 | heap->CollectAllGarbage(); |
531 | intptr_t size_before = |
532 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
533 | |
534 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
535 | Array& old2 = Array::Handle(Array::New(1, Heap::kOld)); |
536 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
537 | old.SetAt(0, old2); |
538 | neu.SetAt(0, old); |
539 | old = Array::null(); |
540 | old2 = Array::null(); |
541 | |
542 | heap->CollectAllGarbage(); |
543 | |
544 | intptr_t size_after = |
545 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
546 | |
547 | EXPECT(size_before < size_after); |
548 | } |
549 | |
550 | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldToNewChain) { |
551 | Isolate* isolate = Isolate::Current(); |
552 | Heap* heap = isolate->heap(); |
553 | |
554 | heap->CollectAllGarbage(); |
555 | intptr_t size_before = |
556 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
557 | |
558 | Array& old = Array::Handle(Array::New(1, Heap::kOld)); |
559 | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); |
560 | Array& neu2 = Array::Handle(Array::New(1, Heap::kOld)); |
561 | neu.SetAt(0, neu2); |
562 | old.SetAt(0, neu); |
563 | neu = Array::null(); |
564 | neu2 = Array::null(); |
565 | |
566 | heap->CollectAllGarbage(); |
567 | |
568 | intptr_t size_after = |
569 | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); |
570 | |
571 | EXPECT(size_before < size_after); |
572 | } |
573 | |
574 | static void NoopFinalizer(void* isolate_callback_data, void* peer) {} |
575 | |
576 | ISOLATE_UNIT_TEST_CASE(ExternalPromotion) { |
577 | Isolate* isolate = Isolate::Current(); |
578 | Heap* heap = isolate->heap(); |
579 | |
580 | heap->CollectAllGarbage(); |
581 | intptr_t size_before = kWordSize * (heap->new_space()->ExternalInWords() + |
582 | heap->old_space()->ExternalInWords()); |
583 | |
584 | Array& old = Array::Handle(Array::New(100, Heap::kOld)); |
585 | Array& neu = Array::Handle(); |
586 | for (intptr_t i = 0; i < 100; i++) { |
587 | neu = Array::New(1, Heap::kNew); |
588 | FinalizablePersistentHandle::New(isolate, neu, NULL, NoopFinalizer, 1 * MB, |
589 | /*auto_delete=*/true); |
590 | old.SetAt(i, neu); |
591 | } |
592 | |
593 | intptr_t size_middle = kWordSize * (heap->new_space()->ExternalInWords() + |
594 | heap->old_space()->ExternalInWords()); |
595 | EXPECT_EQ(size_before + 100 * MB, size_middle); |
596 | |
597 | old = Array::null(); |
598 | neu = Array::null(); |
599 | |
600 | heap->CollectAllGarbage(); |
601 | |
602 | intptr_t size_after = kWordSize * (heap->new_space()->ExternalInWords() + |
603 | heap->old_space()->ExternalInWords()); |
604 | |
605 | EXPECT_EQ(size_before, size_after); |
606 | } |
607 | |
608 | #if !defined(PRODUCT) |
609 | class HeapTestHelper { |
610 | public: |
611 | static void Scavenge(Thread* thread) { |
612 | thread->heap()->CollectNewSpaceGarbage(thread, Heap::kDebugging); |
613 | } |
614 | static void MarkSweep(Thread* thread) { |
615 | thread->heap()->CollectOldSpaceGarbage(thread, Heap::kMarkSweep, |
616 | Heap::kDebugging); |
617 | thread->heap()->WaitForMarkerTasks(thread); |
618 | thread->heap()->WaitForSweeperTasks(thread); |
619 | } |
620 | }; |
621 | |
622 | class MergeIsolatesHeapsHandler : public MessageHandler { |
623 | public: |
624 | explicit MergeIsolatesHeapsHandler(Isolate* owner) |
625 | : msg_(Utils::CreateCStringUniquePtr(nullptr)), owner_(owner) {} |
626 | |
627 | const char* name() const { return "merge-isolates-heaps-handler" ; } |
628 | |
629 | ~MergeIsolatesHeapsHandler() { PortMap::ClosePorts(this); } |
630 | |
631 | MessageStatus HandleMessage(std::unique_ptr<Message> message) { |
632 | // Parse the message. |
633 | Object& response_obj = Object::Handle(); |
634 | if (message->IsRaw()) { |
635 | response_obj = message->raw_obj(); |
636 | } else if (message->IsBequest()) { |
637 | Bequest* bequest = message->bequest(); |
638 | PersistentHandle* handle = bequest->handle(); |
639 | // Object in the receiving isolate's heap. |
640 | EXPECT(isolate()->heap()->Contains(ObjectLayout::ToAddr(handle->raw()))); |
641 | response_obj = handle->raw(); |
642 | isolate()->group()->api_state()->FreePersistentHandle(handle); |
643 | } else { |
644 | Thread* thread = Thread::Current(); |
645 | MessageSnapshotReader reader(message.get(), thread); |
646 | response_obj = reader.ReadObject(); |
647 | } |
648 | if (response_obj.IsString()) { |
649 | String& response = String::Handle(); |
650 | response ^= response_obj.raw(); |
651 | msg_.reset(Utils::StrDup(response.ToCString())); |
652 | } else { |
653 | ASSERT(response_obj.IsArray()); |
654 | Array& response_array = Array::Handle(); |
655 | response_array ^= response_obj.raw(); |
656 | ASSERT(response_array.Length() == 1); |
657 | ExternalTypedData& response = ExternalTypedData::Handle(); |
658 | response ^= response_array.At(0); |
659 | msg_.reset(Utils::StrDup(reinterpret_cast<char*>(response.DataAddr(0)))); |
660 | } |
661 | |
662 | return kOK; |
663 | } |
664 | |
665 | const char* msg() const { return msg_.get(); } |
666 | |
667 | virtual Isolate* isolate() const { return owner_; } |
668 | |
669 | private: |
670 | Utils::CStringUniquePtr msg_; |
671 | Isolate* owner_; |
672 | }; |
673 | |
674 | VM_UNIT_TEST_CASE(CleanupBequestNeverReceived) { |
675 | const char* TEST_MESSAGE = "hello, world" ; |
676 | Dart_Isolate parent = TestCase::CreateTestIsolate("parent" ); |
677 | EXPECT_EQ(parent, Dart_CurrentIsolate()); |
678 | { |
679 | MergeIsolatesHeapsHandler handler(Isolate::Current()); |
680 | Dart_Port port_id = PortMap::CreatePort(&handler); |
681 | EXPECT_EQ(PortMap::GetIsolate(port_id), Isolate::Current()); |
682 | Dart_ExitIsolate(); |
683 | |
684 | Dart_Isolate worker = TestCase::CreateTestIsolateInGroup("worker" , parent); |
685 | EXPECT_EQ(worker, Dart_CurrentIsolate()); |
686 | { |
687 | Thread* thread = Thread::Current(); |
688 | TransitionNativeToVM transition(thread); |
689 | StackZone zone(thread); |
690 | HANDLESCOPE(thread); |
691 | |
692 | String& string = String::Handle(String::New(TEST_MESSAGE)); |
693 | PersistentHandle* handle = |
694 | Isolate::Current()->group()->api_state()->AllocatePersistentHandle(); |
695 | handle->set_raw(string.raw()); |
696 | |
697 | reinterpret_cast<Isolate*>(worker)->bequeath( |
698 | std::unique_ptr<Bequest>(new Bequest(handle, port_id))); |
699 | } |
700 | } |
701 | Dart_ShutdownIsolate(); |
702 | Dart_EnterIsolate(parent); |
703 | Dart_ShutdownIsolate(); |
704 | } |
705 | |
706 | VM_UNIT_TEST_CASE(ReceivesSendAndExitMessage) { |
707 | const char* TEST_MESSAGE = "hello, world" ; |
708 | Dart_Isolate parent = TestCase::CreateTestIsolate("parent" ); |
709 | EXPECT_EQ(parent, Dart_CurrentIsolate()); |
710 | MergeIsolatesHeapsHandler handler(Isolate::Current()); |
711 | Dart_Port port_id = PortMap::CreatePort(&handler); |
712 | EXPECT_EQ(PortMap::GetIsolate(port_id), Isolate::Current()); |
713 | Dart_ExitIsolate(); |
714 | |
715 | Dart_Isolate worker = TestCase::CreateTestIsolateInGroup("worker" , parent); |
716 | EXPECT_EQ(worker, Dart_CurrentIsolate()); |
717 | { |
718 | Thread* thread = Thread::Current(); |
719 | TransitionNativeToVM transition(thread); |
720 | StackZone zone(thread); |
721 | HANDLESCOPE(thread); |
722 | |
723 | String& string = String::Handle(String::New(TEST_MESSAGE)); |
724 | |
725 | PersistentHandle* handle = |
726 | Isolate::Current()->group()->api_state()->AllocatePersistentHandle(); |
727 | handle->set_raw(string.raw()); |
728 | |
729 | reinterpret_cast<Isolate*>(worker)->bequeath( |
730 | std::unique_ptr<Bequest>(new Bequest(handle, port_id))); |
731 | } |
732 | |
733 | Dart_ShutdownIsolate(); |
734 | Dart_EnterIsolate(parent); |
735 | { |
736 | Thread* thread = Thread::Current(); |
737 | TransitionNativeToVM transition(thread); |
738 | StackZone zone(thread); |
739 | HANDLESCOPE(thread); |
740 | |
741 | EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage()); |
742 | } |
743 | EXPECT_STREQ(handler.msg(), TEST_MESSAGE); |
744 | Dart_ShutdownIsolate(); |
745 | } |
746 | |
747 | ISOLATE_UNIT_TEST_CASE(ExternalAllocationStats) { |
748 | Isolate* isolate = thread->isolate(); |
749 | Heap* heap = thread->heap(); |
750 | |
751 | Array& old = Array::Handle(Array::New(100, Heap::kOld)); |
752 | Array& neu = Array::Handle(); |
753 | for (intptr_t i = 0; i < 100; i++) { |
754 | neu = Array::New(1, Heap::kNew); |
755 | FinalizablePersistentHandle::New(isolate, neu, NULL, NoopFinalizer, 1 * MB, |
756 | /*auto_delete=*/true); |
757 | old.SetAt(i, neu); |
758 | |
759 | if ((i % 4) == 0) { |
760 | HeapTestHelper::MarkSweep(thread); |
761 | } else { |
762 | HeapTestHelper::Scavenge(thread); |
763 | } |
764 | |
765 | CountObjectsVisitor visitor(thread, isolate->class_table()->NumCids()); |
766 | HeapIterationScope iter(thread); |
767 | iter.IterateObjects(&visitor); |
768 | isolate->group()->VisitWeakPersistentHandles(&visitor); |
769 | EXPECT_LE(visitor.old_external_size_[kArrayCid], |
770 | heap->old_space()->ExternalInWords() * kWordSize); |
771 | EXPECT_LE(visitor.new_external_size_[kArrayCid], |
772 | heap->new_space()->ExternalInWords() * kWordSize); |
773 | } |
774 | } |
775 | #endif // !defined(PRODUCT) |
776 | |
777 | ISOLATE_UNIT_TEST_CASE(ArrayTruncationRaces) { |
778 | // Alternate between allocating new lists and truncating. |
779 | // For each list, the life cycle is |
780 | // 1) the list is allocated and filled with some elements |
781 | // 2) kNumLists other lists are allocated |
782 | // 3) the list's backing store is truncated; the list becomes unreachable |
783 | // 4) kNumLists other lists are allocated |
784 | // 5) the backing store becomes unreachable |
785 | // The goal is to cause truncation *during* concurrent mark or sweep, by |
786 | // truncating an array that had been alive for a while and will be visited by |
787 | // a GC triggering by the allocations in step 2. |
788 | |
789 | intptr_t kMaxListLength = 100; |
790 | intptr_t kNumLists = 1000; |
791 | Array& lists = Array::Handle(Array::New(kNumLists)); |
792 | Array& arrays = Array::Handle(Array::New(kNumLists)); |
793 | |
794 | GrowableObjectArray& list = GrowableObjectArray::Handle(); |
795 | Array& array = Array::Handle(); |
796 | Object& element = Object::Handle(); |
797 | |
798 | for (intptr_t i = 0; i < kNumLists; i++) { |
799 | list = GrowableObjectArray::New(Heap::kNew); |
800 | intptr_t length = i % kMaxListLength; |
801 | for (intptr_t j = 0; j < length; j++) { |
802 | list.Add(element, Heap::kNew); |
803 | } |
804 | lists.SetAt(i, list); |
805 | } |
806 | |
807 | intptr_t kTruncations = 100000; |
808 | for (intptr_t i = 0; i < kTruncations; i++) { |
809 | list ^= lists.At(i % kNumLists); |
810 | array = Array::MakeFixedLength(list); |
811 | arrays.SetAt(i % kNumLists, array); |
812 | |
813 | list = GrowableObjectArray::New(Heap::kOld); |
814 | intptr_t length = i % kMaxListLength; |
815 | for (intptr_t j = 0; j < length; j++) { |
816 | list.Add(element, Heap::kOld); |
817 | } |
818 | lists.SetAt(i % kNumLists, list); |
819 | } |
820 | } |
821 | |
822 | } // namespace dart |
823 | |