| OLD | NEW |
| 1 // Copyright 2014 the V8 project authors. All rights reserved. | 1 // Copyright 2014 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <stdlib.h> | 5 #include <stdlib.h> |
| 6 #include <utility> | 6 #include <utility> |
| 7 | 7 |
| 8 #include "src/v8.h" | 8 #include "src/v8.h" |
| 9 | 9 |
| 10 #include "src/compilation-cache.h" | 10 #include "src/compilation-cache.h" |
| (...skipping 30 matching lines...) Expand all Loading... |
| 41 } | 41 } |
| 42 | 42 |
| 43 | 43 |
| 44 static Handle<String> MakeName(const char* str, int suffix) { | 44 static Handle<String> MakeName(const char* str, int suffix) { |
| 45 EmbeddedVector<char, 128> buffer; | 45 EmbeddedVector<char, 128> buffer; |
| 46 SNPrintF(buffer, "%s%d", str, suffix); | 46 SNPrintF(buffer, "%s%d", str, suffix); |
| 47 return MakeString(buffer.start()); | 47 return MakeString(buffer.start()); |
| 48 } | 48 } |
| 49 | 49 |
| 50 | 50 |
| 51 Handle<JSObject> GetObject(const char* name) { | |
| 52 return v8::Utils::OpenHandle( | |
| 53 *v8::Handle<v8::Object>::Cast(CcTest::global()->Get(v8_str(name)))); | |
| 54 } | |
| 55 | |
| 56 | |
| 57 static double GetDoubleFieldValue(JSObject* obj, FieldIndex field_index) { | 51 static double GetDoubleFieldValue(JSObject* obj, FieldIndex field_index) { |
| 58 if (obj->IsUnboxedDoubleField(field_index)) { | 52 if (obj->IsUnboxedDoubleField(field_index)) { |
| 59 return obj->RawFastDoublePropertyAt(field_index); | 53 return obj->RawFastDoublePropertyAt(field_index); |
| 60 } else { | 54 } else { |
| 61 Object* value = obj->RawFastPropertyAt(field_index); | 55 Object* value = obj->RawFastPropertyAt(field_index); |
| 62 DCHECK(value->IsMutableHeapNumber()); | 56 DCHECK(value->IsMutableHeapNumber()); |
| 63 return HeapNumber::cast(value)->value(); | 57 return HeapNumber::cast(value)->value(); |
| 64 } | 58 } |
| 65 } | 59 } |
| 66 | 60 |
| (...skipping 1237 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1304 Object* clone_obj = heap->CopyJSObject(jsobject).ToObjectChecked(); | 1298 Object* clone_obj = heap->CopyJSObject(jsobject).ToObjectChecked(); |
| 1305 Handle<JSObject> clone(JSObject::cast(clone_obj)); | 1299 Handle<JSObject> clone(JSObject::cast(clone_obj)); |
| 1306 CHECK(heap->old_pointer_space()->Contains(clone->address())); | 1300 CHECK(heap->old_pointer_space()->Contains(clone->address())); |
| 1307 | 1301 |
| 1308 CcTest::heap()->CollectGarbage(NEW_SPACE, "boom"); | 1302 CcTest::heap()->CollectGarbage(NEW_SPACE, "boom"); |
| 1309 | 1303 |
| 1310 // The value in cloned object should not be corrupted by GC. | 1304 // The value in cloned object should not be corrupted by GC. |
| 1311 CHECK_EQ(boom_value, clone->RawFastDoublePropertyAt(index)); | 1305 CHECK_EQ(boom_value, clone->RawFastDoublePropertyAt(index)); |
| 1312 } | 1306 } |
| 1313 | 1307 |
| 1314 | |
| 1315 static void TestWriteBarrier(Handle<Map> map, Handle<Map> new_map, | |
| 1316 int tagged_descriptor, int double_descriptor, | |
| 1317 bool check_tagged_value = true) { | |
| 1318 FLAG_stress_compaction = true; | |
| 1319 FLAG_manual_evacuation_candidates_selection = true; | |
| 1320 Isolate* isolate = CcTest::i_isolate(); | |
| 1321 Factory* factory = isolate->factory(); | |
| 1322 Heap* heap = CcTest::heap(); | |
| 1323 PagedSpace* old_pointer_space = heap->old_pointer_space(); | |
| 1324 | |
| 1325 // The plan: create |obj| by |map| in old space, create |obj_value| in | |
| 1326 // new space and ensure that write barrier is triggered when |obj_value| is | |
| 1327 // written to property |tagged_descriptor| of |obj|. | |
| 1328 // Then migrate object to |new_map| and set proper value for property | |
| 1329 // |double_descriptor|. Call GC and ensure that it did not crash during | |
| 1330 // store buffer entries updating. | |
| 1331 | |
| 1332 Handle<JSObject> obj; | |
| 1333 Handle<HeapObject> obj_value; | |
| 1334 { | |
| 1335 AlwaysAllocateScope always_allocate(isolate); | |
| 1336 obj = factory->NewJSObjectFromMap(map, TENURED, false); | |
| 1337 CHECK(old_pointer_space->Contains(*obj)); | |
| 1338 | |
| 1339 obj_value = factory->NewJSArray(32 * KB, FAST_HOLEY_ELEMENTS); | |
| 1340 } | |
| 1341 | |
| 1342 CHECK(heap->InNewSpace(*obj_value)); | |
| 1343 | |
| 1344 StoreBuffer* store_buffer = heap->store_buffer(); | |
| 1345 USE(store_buffer); | |
| 1346 Address slot; | |
| 1347 { | |
| 1348 FieldIndex index = FieldIndex::ForDescriptor(*map, tagged_descriptor); | |
| 1349 int offset = index.offset(); | |
| 1350 slot = reinterpret_cast<Address>(HeapObject::RawField(*obj, offset)); | |
| 1351 USE(slot); | |
| 1352 DCHECK(!store_buffer->CellIsInStoreBuffer(slot)); | |
| 1353 | |
| 1354 const int n = 153; | |
| 1355 for (int i = 0; i < n; i++) { | |
| 1356 obj->FastPropertyAtPut(index, *obj_value); | |
| 1357 } | |
| 1358 // Ensure that the slot was actually added to the store buffer. | |
| 1359 DCHECK(store_buffer->CellIsInStoreBuffer(slot)); | |
| 1360 } | |
| 1361 | |
| 1362 // Migrate |obj| to |new_map| which should shift fields and put the | |
| 1363 // |boom_value| to the slot that was earlier recorded by write barrier. | |
| 1364 JSObject::MigrateToMap(obj, new_map); | |
| 1365 | |
| 1366 // Ensure that invalid entries were removed from the store buffer. | |
| 1367 DCHECK(!store_buffer->CellIsInStoreBuffer(slot)); | |
| 1368 | |
| 1369 Address fake_object = reinterpret_cast<Address>(*obj_value) + kPointerSize; | |
| 1370 double boom_value = bit_cast<double>(fake_object); | |
| 1371 | |
| 1372 FieldIndex double_field_index = | |
| 1373 FieldIndex::ForDescriptor(*new_map, double_descriptor); | |
| 1374 CHECK(obj->IsUnboxedDoubleField(double_field_index)); | |
| 1375 obj->RawFastDoublePropertyAtPut(double_field_index, boom_value); | |
| 1376 | |
| 1377 // Trigger GC to evacuate all candidates. | |
| 1378 CcTest::heap()->CollectGarbage(NEW_SPACE, "boom"); | |
| 1379 | |
| 1380 if (check_tagged_value) { | |
| 1381 FieldIndex tagged_field_index = | |
| 1382 FieldIndex::ForDescriptor(*new_map, tagged_descriptor); | |
| 1383 CHECK_EQ(*obj_value, obj->RawFastPropertyAt(tagged_field_index)); | |
| 1384 } | |
| 1385 CHECK_EQ(boom_value, obj->RawFastDoublePropertyAt(double_field_index)); | |
| 1386 } | |
| 1387 | |
| 1388 | |
| 1389 static void TestIncrementalWriteBarrier(Handle<Map> map, Handle<Map> new_map, | |
| 1390 int tagged_descriptor, | |
| 1391 int double_descriptor, | |
| 1392 bool check_tagged_value = true) { | |
| 1393 if (FLAG_never_compact || !FLAG_incremental_marking) return; | |
| 1394 FLAG_stress_compaction = true; | |
| 1395 FLAG_manual_evacuation_candidates_selection = true; | |
| 1396 Isolate* isolate = CcTest::i_isolate(); | |
| 1397 Factory* factory = isolate->factory(); | |
| 1398 Heap* heap = CcTest::heap(); | |
| 1399 PagedSpace* old_pointer_space = heap->old_pointer_space(); | |
| 1400 | |
| 1401 // The plan: create |obj| by |map| in old space, create |obj_value| in | |
| 1402 // old space and ensure it end up in evacuation candidate page. Start | |
| 1403 // incremental marking and ensure that incremental write barrier is triggered | |
| 1404 // when |obj_value| is written to property |tagged_descriptor| of |obj|. | |
| 1405 // Then migrate object to |new_map| and set proper value for property | |
| 1406 // |double_descriptor|. Call GC and ensure that it did not crash during | |
| 1407 // slots buffer entries updating. | |
| 1408 | |
| 1409 Handle<JSObject> obj; | |
| 1410 Handle<HeapObject> obj_value; | |
| 1411 Page* ec_page; | |
| 1412 { | |
| 1413 AlwaysAllocateScope always_allocate(isolate); | |
| 1414 obj = factory->NewJSObjectFromMap(map, TENURED, false); | |
| 1415 CHECK(old_pointer_space->Contains(*obj)); | |
| 1416 | |
| 1417 // Make sure |obj_value| is placed on an old-space evacuation candidate. | |
| 1418 SimulateFullSpace(old_pointer_space); | |
| 1419 obj_value = factory->NewJSArray(32 * KB, FAST_HOLEY_ELEMENTS, TENURED); | |
| 1420 ec_page = Page::FromAddress(obj_value->address()); | |
| 1421 CHECK_NE(ec_page, Page::FromAddress(obj->address())); | |
| 1422 } | |
| 1423 | |
| 1424 // Heap is ready, force |ec_page| to become an evacuation candidate and | |
| 1425 // simulate incremental marking. | |
| 1426 ec_page->SetFlag(MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING); | |
| 1427 SimulateIncrementalMarking(heap); | |
| 1428 | |
| 1429 // Check that everything is ready for triggering incremental write barrier | |
| 1430 // (i.e. that both |obj| and |obj_value| are black and the marking phase is | |
| 1431 // still active and |obj_value|'s page is indeed an evacuation candidate). | |
| 1432 IncrementalMarking* marking = heap->incremental_marking(); | |
| 1433 CHECK(marking->IsMarking()); | |
| 1434 CHECK(Marking::IsBlack(Marking::MarkBitFrom(*obj))); | |
| 1435 CHECK(Marking::IsBlack(Marking::MarkBitFrom(*obj_value))); | |
| 1436 CHECK(MarkCompactCollector::IsOnEvacuationCandidate(*obj_value)); | |
| 1437 | |
| 1438 // Trigger incremental write barrier, which should add a slot to |ec_page|'s | |
| 1439 // slots buffer. | |
| 1440 { | |
| 1441 int slots_buffer_len = SlotsBuffer::SizeOfChain(ec_page->slots_buffer()); | |
| 1442 FieldIndex index = FieldIndex::ForDescriptor(*map, tagged_descriptor); | |
| 1443 const int n = SlotsBuffer::kNumberOfElements + 10; | |
| 1444 for (int i = 0; i < n; i++) { | |
| 1445 obj->FastPropertyAtPut(index, *obj_value); | |
| 1446 } | |
| 1447 // Ensure that the slot was actually added to the |ec_page|'s slots buffer. | |
| 1448 CHECK_EQ(slots_buffer_len + n, | |
| 1449 SlotsBuffer::SizeOfChain(ec_page->slots_buffer())); | |
| 1450 } | |
| 1451 | |
| 1452 // Migrate |obj| to |new_map| which should shift fields and put the | |
| 1453 // |boom_value| to the slot that was earlier recorded by incremental write | |
| 1454 // barrier. | |
| 1455 JSObject::MigrateToMap(obj, new_map); | |
| 1456 | |
| 1457 double boom_value = bit_cast<double>(UINT64_C(0xbaad0176a37c28e1)); | |
| 1458 | |
| 1459 FieldIndex double_field_index = | |
| 1460 FieldIndex::ForDescriptor(*new_map, double_descriptor); | |
| 1461 CHECK(obj->IsUnboxedDoubleField(double_field_index)); | |
| 1462 obj->RawFastDoublePropertyAtPut(double_field_index, boom_value); | |
| 1463 | |
| 1464 // Trigger GC to evacuate all candidates. | |
| 1465 CcTest::heap()->CollectGarbage(OLD_POINTER_SPACE, "boom"); | |
| 1466 | |
| 1467 // Ensure that the values are still there and correct. | |
| 1468 CHECK(!MarkCompactCollector::IsOnEvacuationCandidate(*obj_value)); | |
| 1469 | |
| 1470 if (check_tagged_value) { | |
| 1471 FieldIndex tagged_field_index = | |
| 1472 FieldIndex::ForDescriptor(*new_map, tagged_descriptor); | |
| 1473 CHECK_EQ(*obj_value, obj->RawFastPropertyAt(tagged_field_index)); | |
| 1474 } | |
| 1475 CHECK_EQ(boom_value, obj->RawFastDoublePropertyAt(double_field_index)); | |
| 1476 } | |
| 1477 | |
| 1478 | |
| 1479 enum WriteBarrierKind { OLD_TO_OLD_WRITE_BARRIER, OLD_TO_NEW_WRITE_BARRIER }; | |
| 1480 static void TestWriteBarrierObjectShiftFieldsRight( | |
| 1481 WriteBarrierKind write_barrier_kind) { | |
| 1482 CcTest::InitializeVM(); | |
| 1483 Isolate* isolate = CcTest::i_isolate(); | |
| 1484 v8::HandleScope scope(CcTest::isolate()); | |
| 1485 | |
| 1486 Handle<HeapType> any_type = HeapType::Any(isolate); | |
| 1487 | |
| 1488 CompileRun("function func() { return 1; }"); | |
| 1489 | |
| 1490 Handle<JSObject> func = GetObject("func"); | |
| 1491 | |
| 1492 Handle<Map> map = Map::Create(isolate, 10); | |
| 1493 map = Map::CopyWithConstant(map, MakeName("prop", 0), func, NONE, | |
| 1494 INSERT_TRANSITION).ToHandleChecked(); | |
| 1495 map = Map::CopyWithField(map, MakeName("prop", 1), any_type, NONE, | |
| 1496 Representation::Double(), | |
| 1497 INSERT_TRANSITION).ToHandleChecked(); | |
| 1498 map = Map::CopyWithField(map, MakeName("prop", 2), any_type, NONE, | |
| 1499 Representation::Tagged(), | |
| 1500 INSERT_TRANSITION).ToHandleChecked(); | |
| 1501 | |
| 1502 // Shift fields right by turning constant property to a field. | |
| 1503 Handle<Map> new_map = Map::ReconfigureProperty( | |
| 1504 map, 0, kData, NONE, Representation::Tagged(), any_type, FORCE_FIELD); | |
| 1505 | |
| 1506 if (write_barrier_kind == OLD_TO_NEW_WRITE_BARRIER) { | |
| 1507 TestWriteBarrier(map, new_map, 2, 1); | |
| 1508 } else { | |
| 1509 CHECK_EQ(OLD_TO_OLD_WRITE_BARRIER, write_barrier_kind); | |
| 1510 TestIncrementalWriteBarrier(map, new_map, 2, 1); | |
| 1511 } | |
| 1512 } | |
| 1513 | |
| 1514 | |
| 1515 TEST(WriteBarrierObjectShiftFieldsRight) { | |
| 1516 TestWriteBarrierObjectShiftFieldsRight(OLD_TO_NEW_WRITE_BARRIER); | |
| 1517 } | |
| 1518 | |
| 1519 | |
| 1520 TEST(IncrementalWriteBarrierObjectShiftFieldsRight) { | |
| 1521 TestWriteBarrierObjectShiftFieldsRight(OLD_TO_OLD_WRITE_BARRIER); | |
| 1522 } | |
| 1523 | |
| 1524 | |
| 1525 // TODO(ishell): add respective tests for property kind reconfiguring from | |
| 1526 // accessor field to double, once accessor fields are supported by | |
| 1527 // Map::ReconfigureProperty(). | |
| 1528 | |
| 1529 | |
| 1530 // TODO(ishell): add respective tests for fast property removal case once | |
| 1531 // Map::ReconfigureProperty() supports that. | |
| 1532 | |
| 1533 #endif | 1308 #endif |
| OLD | NEW |