Line data Source code
1 : // Copyright 2012 the V8 project authors. All rights reserved.
2 : // Redistribution and use in source and binary forms, with or without
3 : // modification, are permitted provided that the following conditions are
4 : // met:
5 : //
6 : // * Redistributions of source code must retain the above copyright
7 : // notice, this list of conditions and the following disclaimer.
8 : // * Redistributions in binary form must reproduce the above
9 : // copyright notice, this list of conditions and the following
10 : // disclaimer in the documentation and/or other materials provided
11 : // with the distribution.
12 : // * Neither the name of Google Inc. nor the names of its
13 : // contributors may be used to endorse or promote products derived
14 : // from this software without specific prior written permission.
15 : //
16 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 : // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 : // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 : // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 : // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 : // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 : // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 : // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 : // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 : // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 :
28 : #include <stdlib.h>
29 :
30 : #ifdef __linux__
31 : #include <errno.h>
32 : #include <fcntl.h>
33 : #include <sys/stat.h>
34 : #include <sys/types.h>
35 : #include <unistd.h>
36 : #endif
37 :
38 : #include <utility>
39 :
40 : #include "src/v8.h"
41 :
42 : #include "src/global-handles.h"
43 : #include "src/heap/mark-compact-inl.h"
44 : #include "src/heap/mark-compact.h"
45 : #include "src/objects-inl.h"
46 : #include "test/cctest/cctest.h"
47 : #include "test/cctest/heap/heap-tester.h"
48 : #include "test/cctest/heap/heap-utils.h"
49 :
50 : namespace v8 {
51 : namespace internal {
52 : namespace heap {
53 :
54 25880 : TEST(Promotion) {
55 5 : CcTest::InitializeVM();
56 : Isolate* isolate = CcTest::i_isolate();
57 : {
58 5 : v8::HandleScope sc(CcTest::isolate());
59 5 : Heap* heap = isolate->heap();
60 :
61 5 : heap::SealCurrentObjects(heap);
62 :
63 5 : int array_length = heap::FixedArrayLenFromSize(kMaxRegularHeapObjectSize);
64 5 : Handle<FixedArray> array = isolate->factory()->NewFixedArray(array_length);
65 :
66 : // Array should be in the new space.
67 5 : CHECK(heap->InSpace(*array, NEW_SPACE));
68 5 : CcTest::CollectAllGarbage();
69 5 : CcTest::CollectAllGarbage();
70 5 : CHECK(heap->InSpace(*array, OLD_SPACE));
71 : }
72 5 : }
73 :
74 25880 : HEAP_TEST(NoPromotion) {
75 : // Page promotion allows pages to be moved to old space even in the case of
76 : // OOM scenarios.
77 5 : FLAG_page_promotion = false;
78 :
79 5 : CcTest::InitializeVM();
80 : Isolate* isolate = CcTest::i_isolate();
81 : {
82 5 : v8::HandleScope sc(CcTest::isolate());
83 5 : Heap* heap = isolate->heap();
84 :
85 5 : heap::SealCurrentObjects(heap);
86 :
87 5 : int array_length = heap::FixedArrayLenFromSize(kMaxRegularHeapObjectSize);
88 5 : Handle<FixedArray> array = isolate->factory()->NewFixedArray(array_length);
89 :
90 : heap->set_force_oom(true);
91 : // Array should be in the new space.
92 5 : CHECK(heap->InSpace(*array, NEW_SPACE));
93 5 : CcTest::CollectAllGarbage();
94 5 : CcTest::CollectAllGarbage();
95 5 : CHECK(heap->InSpace(*array, NEW_SPACE));
96 : }
97 5 : }
98 :
99 : // This is the same as Factory::NewMap, except it doesn't retry on
100 : // allocation failure.
101 620737 : AllocationResult HeapTester::AllocateMapForTest(Isolate* isolate) {
102 620737 : Heap* heap = isolate->heap();
103 620737 : HeapObject obj;
104 620737 : AllocationResult alloc = heap->AllocateRaw(Map::kSize, MAP_SPACE);
105 620737 : if (!alloc.To(&obj)) return alloc;
106 : obj->set_map_after_allocation(ReadOnlyRoots(heap).meta_map(),
107 620732 : SKIP_WRITE_BARRIER);
108 : return isolate->factory()->InitializeMap(Map::cast(obj), JS_OBJECT_TYPE,
109 : JSObject::kHeaderSize,
110 620732 : TERMINAL_FAST_ELEMENTS_KIND, 0);
111 : }
112 :
113 : // This is the same as Factory::NewFixedArray, except it doesn't retry
114 : // on allocation failure.
115 6435 : AllocationResult HeapTester::AllocateFixedArrayForTest(
116 : Heap* heap, int length, PretenureFlag pretenure) {
117 : DCHECK(length >= 0 && length <= FixedArray::kMaxLength);
118 : int size = FixedArray::SizeFor(length);
119 6435 : AllocationSpace space = heap->SelectSpace(pretenure);
120 6435 : HeapObject obj;
121 : {
122 6435 : AllocationResult result = heap->AllocateRaw(size, space);
123 6435 : if (!result.To(&obj)) return result;
124 : }
125 : obj->set_map_after_allocation(ReadOnlyRoots(heap).fixed_array_map(),
126 6420 : SKIP_WRITE_BARRIER);
127 : FixedArray array = FixedArray::cast(obj);
128 : array->set_length(length);
129 : MemsetTagged(array->data_start(), ReadOnlyRoots(heap).undefined_value(),
130 : length);
131 6420 : return array;
132 : }
133 :
134 25880 : HEAP_TEST(MarkCompactCollector) {
135 5 : FLAG_incremental_marking = false;
136 5 : FLAG_retain_maps_for_n_gc = 0;
137 5 : CcTest::InitializeVM();
138 : Isolate* isolate = CcTest::i_isolate();
139 5 : Heap* heap = CcTest::heap();
140 : Factory* factory = isolate->factory();
141 :
142 5 : v8::HandleScope sc(CcTest::isolate());
143 10 : Handle<JSGlobalObject> global(isolate->context()->global_object(), isolate);
144 :
145 : // call mark-compact when heap is empty
146 5 : CcTest::CollectGarbage(OLD_SPACE);
147 :
148 : // keep allocating garbage in new space until it fails
149 : const int arraysize = 100;
150 : AllocationResult allocation;
151 6370 : do {
152 6370 : allocation = AllocateFixedArrayForTest(heap, arraysize, NOT_TENURED);
153 : } while (!allocation.IsRetry());
154 5 : CcTest::CollectGarbage(NEW_SPACE);
155 5 : AllocateFixedArrayForTest(heap, arraysize, NOT_TENURED).ToObjectChecked();
156 :
157 : // keep allocating maps until it fails
158 620732 : do {
159 620732 : allocation = AllocateMapForTest(isolate);
160 : } while (!allocation.IsRetry());
161 5 : CcTest::CollectGarbage(MAP_SPACE);
162 5 : AllocateMapForTest(isolate).ToObjectChecked();
163 :
164 : { HandleScope scope(isolate);
165 : // allocate a garbage
166 5 : Handle<String> func_name = factory->InternalizeUtf8String("theFunction");
167 5 : Handle<JSFunction> function = factory->NewFunctionForTest(func_name);
168 10 : Object::SetProperty(isolate, global, func_name, function).Check();
169 :
170 5 : factory->NewJSObject(function);
171 : }
172 :
173 5 : CcTest::CollectGarbage(OLD_SPACE);
174 :
175 : { HandleScope scope(isolate);
176 5 : Handle<String> func_name = factory->InternalizeUtf8String("theFunction");
177 10 : CHECK(Just(true) == JSReceiver::HasOwnProperty(global, func_name));
178 : Handle<Object> func_value =
179 10 : Object::GetProperty(isolate, global, func_name).ToHandleChecked();
180 10 : CHECK(func_value->IsJSFunction());
181 5 : Handle<JSFunction> function = Handle<JSFunction>::cast(func_value);
182 5 : Handle<JSObject> obj = factory->NewJSObject(function);
183 :
184 5 : Handle<String> obj_name = factory->InternalizeUtf8String("theObject");
185 10 : Object::SetProperty(isolate, global, obj_name, obj).Check();
186 5 : Handle<String> prop_name = factory->InternalizeUtf8String("theSlot");
187 : Handle<Smi> twenty_three(Smi::FromInt(23), isolate);
188 10 : Object::SetProperty(isolate, obj, prop_name, twenty_three).Check();
189 : }
190 :
191 5 : CcTest::CollectGarbage(OLD_SPACE);
192 :
193 : { HandleScope scope(isolate);
194 5 : Handle<String> obj_name = factory->InternalizeUtf8String("theObject");
195 10 : CHECK(Just(true) == JSReceiver::HasOwnProperty(global, obj_name));
196 : Handle<Object> object =
197 10 : Object::GetProperty(isolate, global, obj_name).ToHandleChecked();
198 10 : CHECK(object->IsJSObject());
199 5 : Handle<String> prop_name = factory->InternalizeUtf8String("theSlot");
200 20 : CHECK_EQ(*Object::GetProperty(isolate, object, prop_name).ToHandleChecked(),
201 : Smi::FromInt(23));
202 5 : }
203 5 : }
204 :
205 :
206 : // TODO(1600): compaction of map space is temporary removed from GC.
207 : #if 0
208 : static Handle<Map> CreateMap(Isolate* isolate) {
209 : return isolate->factory()->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
210 : }
211 :
212 :
213 : TEST(MapCompact) {
214 : FLAG_max_map_space_pages = 16;
215 : CcTest::InitializeVM();
216 : Isolate* isolate = CcTest::i_isolate();
217 : Factory* factory = isolate->factory();
218 :
219 : {
220 : v8::HandleScope sc;
221 : // keep allocating maps while pointers are still encodable and thus
222 : // mark compact is permitted.
223 : Handle<JSObject> root = factory->NewJSObjectFromMap(CreateMap());
224 : do {
225 : Handle<Map> map = CreateMap();
226 : map->set_prototype(*root);
227 : root = factory->NewJSObjectFromMap(map);
228 : } while (CcTest::heap()->map_space()->MapPointersEncodable());
229 : }
230 : // Now, as we don't have any handles to just allocated maps, we should
231 : // be able to trigger map compaction.
232 : // To give an additional chance to fail, try to force compaction which
233 : // should be impossible right now.
234 : CcTest::CollectAllGarbage(Heap::kForceCompactionMask);
235 : // And now map pointers should be encodable again.
236 : CHECK(CcTest::heap()->map_space()->MapPointersEncodable());
237 : }
238 : #endif
239 :
240 : #if defined(__has_feature)
241 : #if __has_feature(address_sanitizer)
242 : #define V8_WITH_ASAN 1
243 : #endif
244 : #endif
245 :
246 : // Here is a memory use test that uses /proc, and is therefore Linux-only. We
247 : // do not care how much memory the simulator uses, since it is only there for
248 : // debugging purposes. Testing with ASAN doesn't make sense, either.
249 : #if defined(__linux__) && !defined(USE_SIMULATOR) && !defined(V8_WITH_ASAN)
250 :
251 :
252 17844 : static uintptr_t ReadLong(char* buffer, intptr_t* position, int base) {
253 17844 : char* end_address = buffer + *position;
254 17844 : uintptr_t result = strtoul(buffer + *position, &end_address, base);
255 17844 : CHECK(result != ULONG_MAX || errno != ERANGE);
256 17844 : CHECK(end_address > buffer + *position);
257 17844 : *position = end_address - buffer;
258 17844 : return result;
259 : }
260 :
261 :
262 : // The memory use computed this way is not entirely accurate and depends on
263 : // the way malloc allocates memory. That's why the memory use may seem to
264 : // increase even though the sum of the allocated object sizes decreases. It
265 : // also means that the memory use depends on the kernel and stdlib.
266 55 : static intptr_t MemoryInUse() {
267 : intptr_t memory_use = 0;
268 :
269 : int fd = open("/proc/self/maps", O_RDONLY);
270 55 : if (fd < 0) return -1;
271 :
272 : const int kBufSize = 20000;
273 : char buffer[kBufSize];
274 : ssize_t length = read(fd, buffer, kBufSize);
275 : intptr_t line_start = 0;
276 55 : CHECK_LT(length, kBufSize); // Make the buffer bigger.
277 55 : CHECK_GT(length, 0); // We have to find some data in the file.
278 6003 : while (line_start < length) {
279 5948 : if (buffer[line_start] == '\n') {
280 2974 : line_start++;
281 2974 : continue;
282 : }
283 2974 : intptr_t position = line_start;
284 2974 : uintptr_t start = ReadLong(buffer, &position, 16);
285 2974 : CHECK_EQ(buffer[position++], '-');
286 2974 : uintptr_t end = ReadLong(buffer, &position, 16);
287 2974 : CHECK_EQ(buffer[position++], ' ');
288 2974 : CHECK(buffer[position] == '-' || buffer[position] == 'r');
289 2974 : bool read_permission = (buffer[position++] == 'r');
290 2974 : CHECK(buffer[position] == '-' || buffer[position] == 'w');
291 2974 : bool write_permission = (buffer[position++] == 'w');
292 2974 : CHECK(buffer[position] == '-' || buffer[position] == 'x');
293 2974 : bool execute_permission = (buffer[position++] == 'x');
294 2974 : CHECK(buffer[position] == 's' || buffer[position] == 'p');
295 2974 : bool private_mapping = (buffer[position++] == 'p');
296 2974 : CHECK_EQ(buffer[position++], ' ');
297 2974 : uintptr_t offset = ReadLong(buffer, &position, 16);
298 : USE(offset);
299 2974 : CHECK_EQ(buffer[position++], ' ');
300 2974 : uintptr_t major = ReadLong(buffer, &position, 16);
301 : USE(major);
302 2974 : CHECK_EQ(buffer[position++], ':');
303 2974 : uintptr_t minor = ReadLong(buffer, &position, 16);
304 : USE(minor);
305 2974 : CHECK_EQ(buffer[position++], ' ');
306 2974 : uintptr_t inode = ReadLong(buffer, &position, 10);
307 2974 : while (position < length && buffer[position] != '\n') position++;
308 2974 : if ((read_permission || write_permission || execute_permission) &&
309 2045 : private_mapping && inode == 0) {
310 1099 : memory_use += (end - start);
311 : }
312 :
313 : line_start = position;
314 : }
315 55 : close(fd);
316 55 : return memory_use;
317 : }
318 :
319 :
320 55 : intptr_t ShortLivingIsolate() {
321 : v8::Isolate::CreateParams create_params;
322 55 : create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
323 55 : v8::Isolate* isolate = v8::Isolate::New(create_params);
324 : { v8::Isolate::Scope isolate_scope(isolate);
325 55 : v8::Locker lock(isolate);
326 110 : v8::HandleScope handle_scope(isolate);
327 55 : v8::Local<v8::Context> context = v8::Context::New(isolate);
328 55 : CHECK(!context.IsEmpty());
329 : }
330 55 : isolate->Dispose();
331 55 : return MemoryInUse();
332 : }
333 :
334 :
335 25880 : TEST(RegressJoinThreadsOnIsolateDeinit) {
336 5 : intptr_t size_limit = ShortLivingIsolate() * 2;
337 55 : for (int i = 0; i < 10; i++) {
338 50 : CHECK_GT(size_limit, ShortLivingIsolate());
339 : }
340 5 : }
341 :
342 25880 : TEST(Regress5829) {
343 5 : CcTest::InitializeVM();
344 : Isolate* isolate = CcTest::i_isolate();
345 5 : v8::HandleScope sc(CcTest::isolate());
346 10 : Heap* heap = isolate->heap();
347 5 : heap::SealCurrentObjects(heap);
348 5 : i::MarkCompactCollector* collector = heap->mark_compact_collector();
349 : i::IncrementalMarking* marking = heap->incremental_marking();
350 5 : if (collector->sweeping_in_progress()) {
351 0 : collector->EnsureSweepingCompleted();
352 : }
353 5 : CHECK(marking->IsMarking() || marking->IsStopped());
354 5 : if (marking->IsStopped()) {
355 : heap->StartIncrementalMarking(i::Heap::kNoGCFlags,
356 5 : i::GarbageCollectionReason::kTesting);
357 : }
358 5 : CHECK(marking->IsMarking());
359 : marking->StartBlackAllocationForTesting();
360 5 : Handle<FixedArray> array = isolate->factory()->NewFixedArray(10, TENURED);
361 5 : Address old_end = array->address() + array->Size();
362 : // Right trim the array without clearing the mark bits.
363 : array->set_length(9);
364 : heap->CreateFillerObjectAt(old_end - kTaggedSize, kTaggedSize,
365 5 : ClearRecordedSlots::kNo);
366 5 : heap->old_space()->FreeLinearAllocationArea();
367 5 : Page* page = Page::FromAddress(array->address());
368 : IncrementalMarking::MarkingState* marking_state = marking->marking_state();
369 5 : for (auto object_and_size :
370 5 : LiveObjectRange<kGreyObjects>(page, marking_state->bitmap(page))) {
371 0 : CHECK(!object_and_size.first->IsFiller());
372 5 : }
373 5 : }
374 :
375 : #endif // __linux__ and !USE_SIMULATOR
376 :
377 : } // namespace heap
378 : } // namespace internal
379 77625 : } // namespace v8
|