[vm, gc] Make incremental write-barrier elimination safe.
If generated code allocates an old object during concurrent marking, add this object to the deferred marking stack to be (re)scanned when marking is finalized to catch stores missed by the barrier elimination. Bug: https://github.com/dart-lang/sdk/issues/36341 Change-Id: Ifc744fdf720446f14b68268383e1fe5c92d9b5a5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97861 Reviewed-by: Siva Annamalai <asiva@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
d07e33e11f
commit
ae62f7fefe
@@ -1747,6 +1747,11 @@ void Assembler::StoreIntoObjectNoBarrier(Register object,
|
||||
#if defined(DEBUG)
|
||||
Label done;
|
||||
StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate);
|
||||
|
||||
ldrb(TMP, FieldAddress(object, target::Object::tags_offset()));
|
||||
tst(TMP, Operand(1 << target::RawObject::kOldAndNotRememberedBit));
|
||||
b(&done, ZERO);
|
||||
|
||||
Stop("Store buffer update is required");
|
||||
Bind(&done);
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
@@ -1093,6 +1093,11 @@ void Assembler::StoreIntoObjectNoBarrier(Register object,
|
||||
#if defined(DEBUG)
|
||||
Label done;
|
||||
StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate);
|
||||
|
||||
ldr(TMP, FieldAddress(object, target::Object::tags_offset()), kUnsignedByte);
|
||||
tsti(TMP, Immediate(1 << target::RawObject::kOldAndNotRememberedBit));
|
||||
b(&done, ZERO);
|
||||
|
||||
Stop("Store buffer update is required");
|
||||
Bind(&done);
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
@@ -1910,6 +1910,11 @@ void Assembler::StoreIntoObjectNoBarrier(Register object,
|
||||
Label done;
|
||||
pushl(value);
|
||||
StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate);
|
||||
|
||||
testb(FieldAddress(object, target::Object::tags_offset()),
|
||||
Immediate(1 << target::RawObject::kOldAndNotRememberedBit));
|
||||
j(ZERO, &done, Assembler::kNearJump);
|
||||
|
||||
Stop("Store buffer update is required");
|
||||
Bind(&done);
|
||||
popl(value);
|
||||
|
||||
@@ -1371,6 +1371,11 @@ void Assembler::StoreIntoObjectNoBarrier(Register object,
|
||||
Label done;
|
||||
pushq(value);
|
||||
StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate);
|
||||
|
||||
testb(FieldAddress(object, target::Object::tags_offset()),
|
||||
Immediate(1 << target::RawObject::kOldAndNotRememberedBit));
|
||||
j(ZERO, &done, Assembler::kNearJump);
|
||||
|
||||
Stop("Store buffer update is required");
|
||||
Bind(&done);
|
||||
popq(value);
|
||||
|
||||
@@ -316,17 +316,30 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
|
||||
return raw_weak->VisitPointersNonvirtual(this);
|
||||
}
|
||||
|
||||
void FinalizeInstructions() {
|
||||
void ProcessDeferredMarking() {
|
||||
RawObject* raw_obj;
|
||||
while ((raw_obj = deferred_work_list_.Pop()) != NULL) {
|
||||
ASSERT(raw_obj->IsInstructions());
|
||||
RawInstructions* instr = static_cast<RawInstructions*>(raw_obj);
|
||||
if (TryAcquireMarkBit(instr)) {
|
||||
intptr_t size = instr->HeapSize();
|
||||
ASSERT(raw_obj->IsHeapObject() && raw_obj->IsOldObject());
|
||||
// N.B. We are scanning the object even if it is already marked.
|
||||
const intptr_t class_id = raw_obj->GetClassId();
|
||||
intptr_t size;
|
||||
if (class_id != kWeakPropertyCid) {
|
||||
size = raw_obj->VisitPointersNonvirtual(this);
|
||||
} else {
|
||||
RawWeakProperty* raw_weak = static_cast<RawWeakProperty*>(raw_obj);
|
||||
size = ProcessWeakProperty(raw_weak);
|
||||
}
|
||||
// Add the size only if we win the marking race to prevent
|
||||
// double-counting.
|
||||
if (TryAcquireMarkBit(raw_obj)) {
|
||||
marked_bytes_ += size;
|
||||
NOT_IN_PRODUCT(UpdateLiveOld(kInstructionsCid, size));
|
||||
NOT_IN_PRODUCT(UpdateLiveOld(class_id, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FinalizeDeferredMarking() {
|
||||
ProcessDeferredMarking();
|
||||
deferred_work_list_.Finalize();
|
||||
}
|
||||
|
||||
@@ -654,6 +667,8 @@ class ParallelMarkTask : public ThreadPool::Task {
|
||||
// Phase 1: Iterate over roots and drain marking stack in tasks.
|
||||
marker_->IterateRoots(visitor_);
|
||||
|
||||
visitor_->ProcessDeferredMarking();
|
||||
|
||||
bool more_to_mark = false;
|
||||
do {
|
||||
do {
|
||||
@@ -707,7 +722,7 @@ class ParallelMarkTask : public ThreadPool::Task {
|
||||
barrier_->Sync();
|
||||
} while (more_to_mark);
|
||||
|
||||
visitor_->FinalizeInstructions();
|
||||
visitor_->FinalizeDeferredMarking();
|
||||
|
||||
// Phase 2: Weak processing and follow-up marking on main thread.
|
||||
barrier_->Sync();
|
||||
@@ -925,8 +940,9 @@ void GCMarker::MarkObjects(PageSpace* page_space, bool collect_code) {
|
||||
skipped_code_functions);
|
||||
ResetRootSlices();
|
||||
IterateRoots(&mark);
|
||||
mark.ProcessDeferredMarking();
|
||||
mark.DrainMarkingStack();
|
||||
mark.FinalizeInstructions();
|
||||
mark.FinalizeDeferredMarking();
|
||||
{
|
||||
TIMELINE_FUNCTION_GC_DURATION(thread, "ProcessWeakHandles");
|
||||
MarkingWeakVisitor mark_weak(thread);
|
||||
|
||||
@@ -220,9 +220,9 @@ DEFINE_RUNTIME_ENTRY(IntegerDivisionByZeroException, 0) {
|
||||
}
|
||||
|
||||
static void EnsureNewOrRemembered(Thread* thread, const Object& result) {
|
||||
// For write barrier elimination, we need to ensure that the allocation ends
|
||||
// up in the new space if Heap::IsGuaranteedNewSpaceAllocation is true for
|
||||
// this size or else the object needs to go into the store buffer.
|
||||
// For generational write barrier elimination, we need to ensure that the
|
||||
// allocation ends up in the new space if Heap::IsGuaranteedNewSpaceAllocation
|
||||
// is true for this size or else the object needs to go into the store buffer.
|
||||
NoSafepointScope no_safepoint_scope;
|
||||
|
||||
RawObject* object = result.raw();
|
||||
@@ -231,6 +231,18 @@ static void EnsureNewOrRemembered(Thread* thread, const Object& result) {
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsureNewOrDeferredMarking(Thread* thread, const Object& result) {
|
||||
// For incremental write barrier elimination, we need to ensure that the
|
||||
// allocation ends up in the new space or else the object needs to added
|
||||
// to deferred marking stack so it will be [re]scanned.
|
||||
NoSafepointScope no_safepoint_scope;
|
||||
|
||||
RawObject* object = result.raw();
|
||||
if (object->IsOldObject() && thread->is_marking()) {
|
||||
thread->DeferredMarkingStackAddObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocation of a fixed length array of given element type.
|
||||
// This runtime entry is never called for allocating a List of a generic type,
|
||||
// because a prior run time call instantiates the element type if necessary.
|
||||
@@ -266,6 +278,8 @@ DEFINE_RUNTIME_ENTRY(AllocateArray, 2) {
|
||||
if (!array.raw()->IsCardRemembered()) {
|
||||
EnsureNewOrRemembered(thread, array);
|
||||
}
|
||||
EnsureNewOrDeferredMarking(thread, array);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -314,6 +328,7 @@ DEFINE_RUNTIME_ENTRY(AllocateObject, 2) {
|
||||
|
||||
if (AllocateObjectInstr::WillAllocateNewOrRemembered(cls)) {
|
||||
EnsureNewOrRemembered(thread, instance);
|
||||
EnsureNewOrDeferredMarking(thread, instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +440,7 @@ DEFINE_RUNTIME_ENTRY(AllocateContext, 1) {
|
||||
AllocateUninitializedContextInstr::WillAllocateNewOrRemembered(
|
||||
num_context_variables)) {
|
||||
EnsureNewOrRemembered(thread, context);
|
||||
EnsureNewOrDeferredMarking(thread, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user