Line data Source code
1 : // Copyright 2016 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 : #include "src/api.h"
6 : #include "src/heap/array-buffer-tracker.h"
7 : #include "src/heap/spaces.h"
8 : #include "src/isolate.h"
9 : #include "src/objects-inl.h"
10 : #include "test/cctest/cctest.h"
11 : #include "test/cctest/heap/heap-utils.h"
12 :
13 : namespace {
14 :
15 : typedef i::LocalArrayBufferTracker LocalTracker;
16 :
17 : bool IsTracked(i::JSArrayBuffer* buf) {
18 204 : return i::ArrayBufferTracker::IsTracked(buf);
19 : }
20 :
21 : } // namespace
22 :
23 : namespace v8 {
24 : namespace internal {
25 : namespace heap {
26 :
27 : // The following tests make sure that JSArrayBuffer tracking works expected when
28 : // moving the objects through various spaces during GC phases.
29 :
30 23724 : TEST(ArrayBuffer_OnlyMC) {
31 : ManualGCScope manual_gc_scope;
32 6 : CcTest::InitializeVM();
33 12 : LocalContext env;
34 6 : v8::Isolate* isolate = env->GetIsolate();
35 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
36 :
37 : JSArrayBuffer* raw_ab = nullptr;
38 : {
39 6 : v8::HandleScope handle_scope(isolate);
40 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
41 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
42 6 : CHECK(IsTracked(*buf));
43 6 : heap::GcAndSweep(heap, OLD_SPACE);
44 6 : CHECK(IsTracked(*buf));
45 6 : heap::GcAndSweep(heap, OLD_SPACE);
46 6 : CHECK(IsTracked(*buf));
47 : raw_ab = *buf;
48 : // Prohibit page from being released.
49 12 : Page::FromAddress(buf->address())->MarkNeverEvacuate();
50 : }
51 : // 2 GCs are needed because we promote to old space as live, meaning that
52 : // we will survive one GC.
53 6 : heap::GcAndSweep(heap, OLD_SPACE);
54 6 : heap::GcAndSweep(heap, OLD_SPACE);
55 6 : CHECK(!IsTracked(raw_ab));
56 6 : }
57 :
58 23724 : TEST(ArrayBuffer_OnlyScavenge) {
59 : ManualGCScope manual_gc_scope;
60 6 : CcTest::InitializeVM();
61 12 : LocalContext env;
62 6 : v8::Isolate* isolate = env->GetIsolate();
63 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
64 :
65 : JSArrayBuffer* raw_ab = nullptr;
66 : {
67 6 : v8::HandleScope handle_scope(isolate);
68 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
69 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
70 6 : CHECK(IsTracked(*buf));
71 6 : heap::GcAndSweep(heap, NEW_SPACE);
72 6 : CHECK(IsTracked(*buf));
73 6 : heap::GcAndSweep(heap, NEW_SPACE);
74 6 : CHECK(IsTracked(*buf));
75 6 : heap::GcAndSweep(heap, NEW_SPACE);
76 6 : CHECK(IsTracked(*buf));
77 : raw_ab = *buf;
78 : // Prohibit page from being released.
79 12 : Page::FromAddress(buf->address())->MarkNeverEvacuate();
80 : }
81 : // 2 GCs are needed because we promote to old space as live, meaning that
82 : // we will survive one GC.
83 6 : heap::GcAndSweep(heap, OLD_SPACE);
84 6 : heap::GcAndSweep(heap, OLD_SPACE);
85 6 : CHECK(!IsTracked(raw_ab));
86 6 : }
87 :
88 23724 : TEST(ArrayBuffer_ScavengeAndMC) {
89 : ManualGCScope manual_gc_scope;
90 6 : CcTest::InitializeVM();
91 12 : LocalContext env;
92 6 : v8::Isolate* isolate = env->GetIsolate();
93 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
94 :
95 : JSArrayBuffer* raw_ab = nullptr;
96 : {
97 6 : v8::HandleScope handle_scope(isolate);
98 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
99 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
100 6 : CHECK(IsTracked(*buf));
101 6 : heap::GcAndSweep(heap, NEW_SPACE);
102 6 : CHECK(IsTracked(*buf));
103 6 : heap::GcAndSweep(heap, NEW_SPACE);
104 6 : CHECK(IsTracked(*buf));
105 6 : heap::GcAndSweep(heap, OLD_SPACE);
106 6 : CHECK(IsTracked(*buf));
107 6 : heap::GcAndSweep(heap, NEW_SPACE);
108 6 : CHECK(IsTracked(*buf));
109 : raw_ab = *buf;
110 : // Prohibit page from being released.
111 12 : Page::FromAddress(buf->address())->MarkNeverEvacuate();
112 : }
113 : // 2 GCs are needed because we promote to old space as live, meaning that
114 : // we will survive one GC.
115 6 : heap::GcAndSweep(heap, OLD_SPACE);
116 6 : heap::GcAndSweep(heap, OLD_SPACE);
117 6 : CHECK(!IsTracked(raw_ab));
118 6 : }
119 :
120 23724 : TEST(ArrayBuffer_Compaction) {
121 6 : if (FLAG_never_compact) return;
122 : ManualGCScope manual_gc_scope;
123 6 : FLAG_manual_evacuation_candidates_selection = true;
124 6 : CcTest::InitializeVM();
125 12 : LocalContext env;
126 6 : v8::Isolate* isolate = env->GetIsolate();
127 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
128 6 : heap::AbandonCurrentlyFreeMemory(heap->old_space());
129 :
130 12 : v8::HandleScope handle_scope(isolate);
131 6 : Local<v8::ArrayBuffer> ab1 = v8::ArrayBuffer::New(isolate, 100);
132 : Handle<JSArrayBuffer> buf1 = v8::Utils::OpenHandle(*ab1);
133 6 : CHECK(IsTracked(*buf1));
134 6 : heap::GcAndSweep(heap, NEW_SPACE);
135 6 : heap::GcAndSweep(heap, NEW_SPACE);
136 :
137 6 : Page* page_before_gc = Page::FromAddress(buf1->address());
138 6 : heap::ForceEvacuationCandidate(page_before_gc);
139 6 : CHECK(IsTracked(*buf1));
140 :
141 6 : CcTest::CollectAllGarbage();
142 :
143 6 : Page* page_after_gc = Page::FromAddress(buf1->address());
144 6 : CHECK(IsTracked(*buf1));
145 :
146 6 : CHECK_NE(page_before_gc, page_after_gc);
147 : }
148 :
149 23724 : TEST(ArrayBuffer_UnregisterDuringSweep) {
150 : // Regular pages in old space (without compaction) are processed concurrently
151 : // in the sweeper. If we happen to unregister a buffer (either explicitly, or
152 : // implicitly through e.g. |Externalize|) we need to sync with the sweeper
153 : // task.
154 : //
155 : // Note: This test will will only fail on TSAN configurations.
156 :
157 : // Disable verify-heap since it forces sweeping to be completed in the
158 : // epilogue of the GC.
159 : #ifdef VERIFY_HEAP
160 : i::FLAG_verify_heap = false;
161 : #endif // VERIFY_HEAP
162 : ManualGCScope manual_gc_scope;
163 :
164 6 : CcTest::InitializeVM();
165 12 : LocalContext env;
166 6 : v8::Isolate* isolate = env->GetIsolate();
167 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
168 : {
169 6 : v8::HandleScope handle_scope(isolate);
170 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
171 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
172 :
173 : {
174 6 : v8::HandleScope handle_scope(isolate);
175 : // Allocate another buffer on the same page to force processing a
176 : // non-empty set of buffers in the last GC.
177 6 : Local<v8::ArrayBuffer> ab2 = v8::ArrayBuffer::New(isolate, 100);
178 : Handle<JSArrayBuffer> buf2 = v8::Utils::OpenHandle(*ab2);
179 6 : CHECK(IsTracked(*buf));
180 6 : CHECK(IsTracked(*buf));
181 6 : heap::GcAndSweep(heap, NEW_SPACE);
182 6 : CHECK(IsTracked(*buf));
183 6 : CHECK(IsTracked(*buf));
184 6 : heap::GcAndSweep(heap, NEW_SPACE);
185 6 : CHECK(IsTracked(*buf));
186 6 : CHECK(IsTracked(*buf2));
187 : }
188 :
189 6 : CcTest::CollectGarbage(OLD_SPACE);
190 : // |Externalize| will cause the buffer to be |Unregister|ed. Without
191 : // barriers and proper synchronization this will trigger a data race on
192 : // TSAN.
193 6 : v8::ArrayBuffer::Contents contents = ab->Externalize();
194 6 : heap->isolate()->array_buffer_allocator()->Free(contents.Data(),
195 6 : contents.ByteLength());
196 : }
197 6 : }
198 :
199 23724 : TEST(ArrayBuffer_NonLivePromotion) {
200 6 : if (!FLAG_incremental_marking) return;
201 : ManualGCScope manual_gc_scope;
202 : // The test verifies that the marking state is preserved when promoting
203 : // a buffer to old space.
204 6 : CcTest::InitializeVM();
205 12 : LocalContext env;
206 6 : v8::Isolate* isolate = env->GetIsolate();
207 12 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
208 :
209 : JSArrayBuffer* raw_ab = nullptr;
210 : {
211 6 : v8::HandleScope handle_scope(isolate);
212 : Handle<FixedArray> root =
213 6 : heap->isolate()->factory()->NewFixedArray(1, TENURED);
214 : {
215 6 : v8::HandleScope handle_scope(isolate);
216 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
217 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
218 6 : root->set(0, *buf); // Buffer that should not be promoted as live.
219 : }
220 6 : heap::SimulateIncrementalMarking(heap, false);
221 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
222 6 : heap::GcAndSweep(heap, NEW_SPACE);
223 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
224 6 : heap::GcAndSweep(heap, NEW_SPACE);
225 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
226 : raw_ab = JSArrayBuffer::cast(root->get(0));
227 6 : root->set(0, heap->undefined_value());
228 6 : heap::SimulateIncrementalMarking(heap, true);
229 : // Prohibit page from being released.
230 6 : Page::FromAddress(raw_ab->address())->MarkNeverEvacuate();
231 6 : heap::GcAndSweep(heap, OLD_SPACE);
232 6 : CHECK(!IsTracked(raw_ab));
233 : }
234 : }
235 :
236 23724 : TEST(ArrayBuffer_LivePromotion) {
237 6 : if (!FLAG_incremental_marking) return;
238 : ManualGCScope manual_gc_scope;
239 : // The test verifies that the marking state is preserved when promoting
240 : // a buffer to old space.
241 6 : CcTest::InitializeVM();
242 12 : LocalContext env;
243 6 : v8::Isolate* isolate = env->GetIsolate();
244 12 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
245 :
246 : JSArrayBuffer* raw_ab = nullptr;
247 : {
248 6 : v8::HandleScope handle_scope(isolate);
249 : Handle<FixedArray> root =
250 6 : heap->isolate()->factory()->NewFixedArray(1, TENURED);
251 : {
252 6 : v8::HandleScope handle_scope(isolate);
253 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
254 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
255 6 : root->set(0, *buf); // Buffer that should be promoted as live.
256 : }
257 6 : heap::SimulateIncrementalMarking(heap, true);
258 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
259 6 : heap::GcAndSweep(heap, NEW_SPACE);
260 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
261 6 : heap::GcAndSweep(heap, NEW_SPACE);
262 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
263 : raw_ab = JSArrayBuffer::cast(root->get(0));
264 6 : root->set(0, heap->undefined_value());
265 : // Prohibit page from being released.
266 6 : Page::FromAddress(raw_ab->address())->MarkNeverEvacuate();
267 6 : heap::GcAndSweep(heap, OLD_SPACE);
268 6 : CHECK(IsTracked(raw_ab));
269 : }
270 : }
271 :
272 23724 : TEST(ArrayBuffer_SemiSpaceCopyThenPagePromotion) {
273 6 : if (!i::FLAG_incremental_marking) return;
274 : ManualGCScope manual_gc_scope;
275 : // The test verifies that the marking state is preserved across semispace
276 : // copy.
277 6 : CcTest::InitializeVM();
278 12 : LocalContext env;
279 6 : v8::Isolate* isolate = env->GetIsolate();
280 12 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
281 :
282 6 : heap::SealCurrentObjects(heap);
283 : {
284 6 : v8::HandleScope handle_scope(isolate);
285 : Handle<FixedArray> root =
286 6 : heap->isolate()->factory()->NewFixedArray(1, TENURED);
287 : {
288 6 : v8::HandleScope handle_scope(isolate);
289 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
290 : Handle<JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
291 6 : root->set(0, *buf); // Buffer that should be promoted as live.
292 12 : Page::FromAddress(buf->address())->MarkNeverEvacuate();
293 : }
294 : std::vector<Handle<FixedArray>> handles;
295 : // Make the whole page transition from new->old, getting the buffers
296 : // processed in the sweeper (relying on marking information) instead of
297 : // processing during newspace evacuation.
298 6 : heap::FillCurrentPage(heap->new_space(), &handles);
299 6 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
300 6 : heap::GcAndSweep(heap, NEW_SPACE);
301 6 : heap::SimulateIncrementalMarking(heap, true);
302 6 : heap::GcAndSweep(heap, OLD_SPACE);
303 12 : CHECK(IsTracked(JSArrayBuffer::cast(root->get(0))));
304 : }
305 : }
306 :
307 23724 : UNINITIALIZED_TEST(ArrayBuffer_SemiSpaceCopyMultipleTasks) {
308 6 : if (FLAG_optimize_for_size) return;
309 : // Test allocates JSArrayBuffer on different pages before triggering a
310 : // full GC that performs the semispace copy. If parallelized, this test
311 : // ensures proper synchronization in TSAN configurations.
312 6 : FLAG_min_semi_space_size = 2 * Page::kPageSize / MB;
313 : v8::Isolate::CreateParams create_params;
314 6 : create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
315 6 : v8::Isolate* isolate = v8::Isolate::New(create_params);
316 : i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
317 : {
318 : v8::Isolate::Scope isolate_scope(isolate);
319 12 : v8::HandleScope handle_scope(isolate);
320 12 : v8::Context::New(isolate)->Enter();
321 12 : Heap* heap = i_isolate->heap();
322 :
323 : // Ensure heap is in a clean state.
324 : heap->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask,
325 6 : GarbageCollectionReason::kTesting);
326 : heap->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask,
327 6 : GarbageCollectionReason::kTesting);
328 :
329 6 : Local<v8::ArrayBuffer> ab1 = v8::ArrayBuffer::New(isolate, 100);
330 : Handle<JSArrayBuffer> buf1 = v8::Utils::OpenHandle(*ab1);
331 6 : heap::FillCurrentPage(heap->new_space());
332 6 : Local<v8::ArrayBuffer> ab2 = v8::ArrayBuffer::New(isolate, 100);
333 : Handle<JSArrayBuffer> buf2 = v8::Utils::OpenHandle(*ab2);
334 18 : CHECK_NE(Page::FromAddress(buf1->address()),
335 : Page::FromAddress(buf2->address()));
336 6 : heap::GcAndSweep(heap, OLD_SPACE);
337 : }
338 6 : isolate->Dispose();
339 : }
340 :
341 23724 : TEST(ArrayBuffer_RetainedSizeIncreases) {
342 6 : CcTest::InitializeVM();
343 6 : LocalContext env;
344 6 : v8::Isolate* isolate = env->GetIsolate();
345 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
346 :
347 6 : const size_t retained_before = ArrayBufferTracker::RetainedInNewSpace(heap);
348 : {
349 : const size_t kArraybufferSize = 117;
350 6 : v8::HandleScope handle_scope(isolate);
351 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, kArraybufferSize);
352 : USE(ab);
353 6 : const size_t retained_after = ArrayBufferTracker::RetainedInNewSpace(heap);
354 6 : CHECK_EQ(kArraybufferSize, retained_after - retained_before);
355 6 : }
356 6 : }
357 :
358 23724 : TEST(ArrayBuffer_RetainedSizeDecreases) {
359 6 : CcTest::InitializeVM();
360 6 : LocalContext env;
361 6 : v8::Isolate* isolate = env->GetIsolate();
362 6 : Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
363 :
364 6 : const size_t retained_before = ArrayBufferTracker::RetainedInNewSpace(heap);
365 : {
366 : const size_t kArraybufferSize = 117;
367 6 : v8::HandleScope handle_scope(isolate);
368 6 : Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, kArraybufferSize);
369 6 : USE(ab);
370 : }
371 6 : heap::GcAndSweep(heap, OLD_SPACE);
372 6 : const size_t retained_after = ArrayBufferTracker::RetainedInNewSpace(heap);
373 6 : CHECK_EQ(0, retained_after - retained_before);
374 6 : }
375 :
376 : } // namespace heap
377 : } // namespace internal
378 71154 : } // namespace v8
|