[vm, gc] Reapply the generational barrier to unforwarded slots during become.

The remembered bit may be incorrectly false when become is the result of aborting a scavenge in the middle of visiting the remembered set.

Bug: https://github.com/dart-lang/sdk/issues/43642
Bug: https://github.com/dart-lang/sdk/issues/43862
Change-Id: I32b6ca3d4627fceef3fbee21997f326e1271a48e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170641
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak 2020-11-09 17:46:54 +00:00 committed by commit-bot@chromium.org
parent 783d83950b
commit 711b625bca
4 changed files with 596 additions and 10 deletions

View file

@ -0,0 +1,288 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:io";
import "package:expect/expect.dart";
// The sizes of these classes are co-prime multiples of the allocation unit to
// increase the likelihood that scavenging fails from fragmentation.
// header + 13 fields = 7 allocation units
class A {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13;
}
}
// header + 17 fields = 9 allocation units
class B {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
dynamic field14;
dynamic field15;
dynamic field16;
dynamic field17;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13 +
field14 +
field15 +
field16 +
field17;
}
}
// header + 19 fields = 10 allocation units
class C {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
dynamic field14;
dynamic field15;
dynamic field16;
dynamic field17;
dynamic field18;
dynamic field19;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13 +
field14 +
field15 +
field16 +
field17 +
field18 +
field19;
}
}
class Old {
dynamic next;
dynamic new1;
dynamic new2;
dynamic new3;
dynamic new4;
dynamic new5;
dynamic new6;
dynamic new7;
dynamic new8;
dynamic new9;
dynamic new10;
dynamic new11;
dynamic new12;
dynamic new13;
dynamic new14;
dynamic new15;
dynamic new16;
dynamic new17;
dynamic new18;
dynamic new19;
dynamic new20;
dynamic new21;
dynamic new22;
dynamic new23;
dynamic new24;
dynamic new25;
dynamic new26;
dynamic new27;
dynamic new28;
dynamic new29;
dynamic new30;
dynamic new31;
// Prevent fields from being optimized away as write-only.
String toString() {
return new1 +
new2 +
new3 +
new4 +
new5 +
new6 +
new7 +
new8 +
new9 +
new10 +
new11 +
new12 +
new13 +
new14 +
new15 +
new16 +
new17 +
new18 +
new19 +
new20 +
new21 +
new22 +
new23 +
new24 +
new25 +
new26 +
new27 +
new28 +
new29 +
new30 +
new31;
}
}
fill(old) {
// Note the allocation order is different from the field order. The objects
// will be scavenged in field order, causing the objects to be rearranged to
// produce new-space fragmentation.
old.new1 = new C();
old.new4 = new C();
old.new7 = new C();
old.new10 = new C();
old.new13 = new C();
old.new16 = new C();
old.new2 = new B();
old.new5 = new B();
old.new8 = new B();
old.new11 = new B();
old.new14 = new B();
old.new17 = new B();
old.new3 = new A();
old.new6 = new A();
old.new9 = new A();
old.new12 = new A();
old.new15 = new A();
old.new18 = new A();
}
makeOld() {
// 2/4 MB worth of Old.
print("PHASE1");
var head;
for (var i = 0; i < 16384; i++) {
var old = new Old();
old.next = head;
head = old;
}
// 32/64 MB worth of new objects, all directly reachable from the
// remembered set.
print("PHASE2");
for (var old = head; old != null; old = old.next) {
fill(old);
}
print("PHASE3");
return head;
}
main(List<String> argsIn) async {
if (argsIn.contains("--testee")) {
// Trigger OOM.
// Must read the fields to prevent the writes from being optimized away. If
// the writes are optimized away, most of the tree is collectible right away
// and we timeout instead of triggering OOM.
print(makeOld());
return;
}
var exec = Platform.executable;
var args = Platform.executableArguments +
[
"--new_gen_semi_max_size=4" /*MB*/,
"--old_gen_heap_size=15" /*MB*/,
"--verbose_gc",
"--verify_store_buffer",
Platform.script.toFilePath(),
"--testee"
];
print("+ $exec ${args.join(' ')}");
var result = await Process.run(exec, args);
print("Command stdout:");
print(result.stdout);
print("Command stderr:");
print(result.stderr);
Expect.equals(255, result.exitCode,
"Should see runtime exception error code, not SEGV");
Expect.isTrue(
result.stderr.contains("Unhandled exception:\nOut of Memory") ||
result.stderr.contains("Unhandled exception:\r\nOut of Memory"),
"Should see the Dart OutOfMemoryError");
Expect.isFalse(result.stderr.contains("error: Out of memory"),
"Should not see the C++ OUT_OF_MEMORY()");
}

View file

@ -0,0 +1,288 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:io";
import "package:expect/expect.dart";
// The sizes of these classes are co-prime multiples of the allocation unit to
// increase the likelihood that scavenging fails from fragmentation.
// header + 13 fields = 7 allocation units
class A {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13;
}
}
// header + 17 fields = 9 allocation units
class B {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
dynamic field14;
dynamic field15;
dynamic field16;
dynamic field17;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13 +
field14 +
field15 +
field16 +
field17;
}
}
// header + 19 fields = 10 allocation units
class C {
dynamic field1;
dynamic field2;
dynamic field3;
dynamic field4;
dynamic field5;
dynamic field6;
dynamic field7;
dynamic field8;
dynamic field9;
dynamic field10;
dynamic field11;
dynamic field12;
dynamic field13;
dynamic field14;
dynamic field15;
dynamic field16;
dynamic field17;
dynamic field18;
dynamic field19;
// Prevent fields from being optimized away as write-only.
String toString() {
return field1 +
field2 +
field3 +
field4 +
field5 +
field6 +
field7 +
field8 +
field9 +
field10 +
field11 +
field12 +
field13 +
field14 +
field15 +
field16 +
field17 +
field18 +
field19;
}
}
class Old {
dynamic next;
dynamic new1;
dynamic new2;
dynamic new3;
dynamic new4;
dynamic new5;
dynamic new6;
dynamic new7;
dynamic new8;
dynamic new9;
dynamic new10;
dynamic new11;
dynamic new12;
dynamic new13;
dynamic new14;
dynamic new15;
dynamic new16;
dynamic new17;
dynamic new18;
dynamic new19;
dynamic new20;
dynamic new21;
dynamic new22;
dynamic new23;
dynamic new24;
dynamic new25;
dynamic new26;
dynamic new27;
dynamic new28;
dynamic new29;
dynamic new30;
dynamic new31;
// Prevent fields from being optimized away as write-only.
String toString() {
return new1 +
new2 +
new3 +
new4 +
new5 +
new6 +
new7 +
new8 +
new9 +
new10 +
new11 +
new12 +
new13 +
new14 +
new15 +
new16 +
new17 +
new18 +
new19 +
new20 +
new21 +
new22 +
new23 +
new24 +
new25 +
new26 +
new27 +
new28 +
new29 +
new30 +
new31;
}
}
fill(old) {
// Note the allocation order is different from the field order. The objects
// will be scavenged in field order, causing the objects to be rearranged to
// produce new-space fragmentation.
old.new1 = new C();
old.new4 = new C();
old.new7 = new C();
old.new10 = new C();
old.new13 = new C();
old.new16 = new C();
old.new2 = new B();
old.new5 = new B();
old.new8 = new B();
old.new11 = new B();
old.new14 = new B();
old.new17 = new B();
old.new3 = new A();
old.new6 = new A();
old.new9 = new A();
old.new12 = new A();
old.new15 = new A();
old.new18 = new A();
}
makeOld() {
// 2/4 MB worth of Old.
print("PHASE1");
var head;
for (var i = 0; i < 16384; i++) {
var old = new Old();
old.next = head;
head = old;
}
// 32/64 MB worth of new objects, all directly reachable from the
// remembered set.
print("PHASE2");
for (var old = head; old != null; old = old.next) {
fill(old);
}
print("PHASE3");
return head;
}
main(List<String> argsIn) async {
if (argsIn.contains("--testee")) {
// Trigger OOM.
// Must read the fields to prevent the writes from being optimized away. If
// the writes are optimized away, most of the tree is collectible right away
// and we timeout instead of triggering OOM.
print(makeOld());
return;
}
var exec = Platform.executable;
var args = Platform.executableArguments +
[
"--new_gen_semi_max_size=4" /*MB*/,
"--old_gen_heap_size=15" /*MB*/,
"--verbose_gc",
"--verify_store_buffer",
Platform.script.toFilePath(),
"--testee"
];
print("+ $exec ${args.join(' ')}");
var result = await Process.run(exec, args);
print("Command stdout:");
print(result.stdout);
print("Command stderr:");
print(result.stderr);
Expect.equals(255, result.exitCode,
"Should see runtime exception error code, not SEGV");
Expect.isTrue(
result.stderr.contains("Unhandled exception:\nOut of Memory") ||
result.stderr.contains("Unhandled exception:\r\nOut of Memory"),
"Should see the Dart OutOfMemoryError");
Expect.isFalse(result.stderr.contains("error: Out of memory"),
"Should not see the C++ OUT_OF_MEMORY()");
}

View file

@ -86,25 +86,35 @@ class ForwardPointersVisitor : public ObjectPointerVisitor {
virtual void VisitPointers(ObjectPtr* first, ObjectPtr* last) {
for (ObjectPtr* p = first; p <= last; p++) {
ObjectPtr old_target = *p;
ObjectPtr new_target;
if (IsForwardingObject(old_target)) {
ObjectPtr new_target = GetForwardedObject(old_target);
if (visiting_object_ == nullptr) {
*p = new_target;
} else if (visiting_object_->ptr()->IsCardRemembered()) {
visiting_object_->ptr()->StoreArrayPointer(p, new_target, thread_);
} else {
visiting_object_->ptr()->StorePointer(p, new_target, thread_);
}
new_target = GetForwardedObject(old_target);
} else {
// Though we do not need to update the slot's value when it is not
// forwarded, we do need to recheck the generational barrier. In
// particular, the remembered bit may be incorrectly false if this
// become was the result of aborting a scavenge while visiting the
// remembered set.
new_target = old_target;
}
if (visiting_object_ == nullptr) {
*p = new_target;
} else if (visiting_object_->ptr()->IsCardRemembered()) {
visiting_object_->ptr()->StoreArrayPointer(p, new_target, thread_);
} else {
visiting_object_->ptr()->StorePointer(p, new_target, thread_);
}
}
}
void VisitingObject(ObjectPtr obj) {
visiting_object_ = obj;
// The incoming remembered bit may be unreliable. Clear it so we can
// consistently reapply the barrier to all slots.
if ((obj != nullptr) && obj->IsOldObject() && obj->ptr()->IsRemembered()) {
ASSERT(!obj->IsForwardingCorpse());
ASSERT(!obj->IsFreeListElement());
thread_->StoreBufferAddObjectGC(obj);
obj->ptr()->ClearRememberedBit();
}
}

View file

@ -830,7 +830,7 @@ class CheckStoreBufferVisitor : public ObjectVisitor,
if (raw_obj->IsHeapObject() && raw_obj->IsNewObject()) {
if (!is_remembered_) {
FATAL3(
"Old object %#" Px "references new object %#" Px
"Old object %#" Px " references new object %#" Px
", but it is not"
" in any store buffer. Consider using rr to watch the slot %p and"
" reverse-continue to find the store with a missing barrier.\n",