dart-sdk/runtime/vm/field_table.h

132 lines
3.9 KiB
C
Raw Normal View History

// 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.
#ifndef RUNTIME_VM_FIELD_TABLE_H_
#define RUNTIME_VM_FIELD_TABLE_H_
#include "platform/assert.h"
#include "platform/atomic.h"
#include "vm/bitfield.h"
#include "vm/class_id.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/tagged_pointer.h"
namespace dart {
class Isolate;
class Field;
[vm/concurrency] Final support for hot-reload of multi-isolate groups This is the initial implementation of hot reload with multi-isolate groups. Implementation: As before, when a service API call triggers a reload it will be routed as an OOB message to a specific isolate (**). As opposed to before, that isolate has now to coordinate with all other isolates, ensuring that it "owns" the reload and all other isolates are waiting in a state that allows reload. This is implemented as a [ReloadOperationScope] which first participates in other reloads (if there are any) and then owns the reload. It will send a new kind of service message to all other registered isolates. All of them have to check in before reload can proceed. If a new isolate is about to join the group, it will participate when registering the isolate. If an old isolate wants to die, it will participate when unregistering the isolate. This means that in addition to the existing StackOverFlow checks that can process OOB messages and therefore reload, we'll have isolate registration and unregistration as well as a new Isolate::kCheckForReload OOB message handler where an isolate can participate in a reload. We consider the isolate group to be reloadable if the main isolate has loaded the program and set the root library. Helper isolates don't need to load any more kernel code and only initialize core libraries, so it's fine to reload them during this time. (**) The reason we continue to send reload service API calls to any isolate in an isolate group is that re-loading might involve calling out to the embedder's tag handler. Doing so currently requires an active isolate. If we allowed a subset of dart_api.h (the subset needed by the tag handler) to be used only with an active IsolateGroup instead of an active Isolate we could remove this requirement. Edge cases: There's various edge cases to consider: The main edge case is, we currently maintain an upper limit to the number of isolates executing in parallel (to ensure each can have big enough chunk of new space, i.e. TLAB). If there are more isolates with active work they are waiting until one of the exiting ones "yields". To ensure progress, if any such actively running isolate gets a request to participate in a reload, it will mark its own thread as "blocked" and therefore "yields", so another isolate can make progress until all isolates are participating and the reload can start. Marking an isolate as "blocked" happens by exiting that isolate. It will free up it's TLAB, decrease active mutator count and (if running on VM's thread pool) also temporarily increase the thread pool size. The side-effect of this is that it will use one pthread per isolate during reload. In the future we can extend this first implementation, by specially handling isolates that don't have a message handler running. Doing so would require careful consideration to avoid races. Testing: In order to test this we use a small helper framework for reload tests. The helper framework will, similar to real world reload e.g. in flutter, will spawn a subprocess. It will use the service API to trigger reloads in this subproces. To synchronize between the reload driver and the application being reloaded it allows watching for events to be printed to stdout/stderr. The reload test itself can be written - similar to multitests - with annotations such as `// @include-in-relload-0` in them. The testing framework will then generate multiple application versions that all get compiled to kernel. For simplicity we generate the kernel using the standalone VM with `--snapshot-kind=kernel` and avoid using the incremental compiler. There are 4 different tests exercising different aspects of multi-isolate reload: vm/dart_2/isolates/reload_active_stack_test: Performs a reload while a fixed number of isolates have an active stack, thereby ensuring e.g. that all frames of all isolate mutator stacks get deoptimized, ... vm/dart_2/isolates/reload_no_active_stack_test: Similar to the test above, but instead of having an active stack the isolates can yield to the event loop, possibly be even descheduled vm/dart_2/isolates/reload_many_isolates_test: Similar to the test above, but this test uses many more isolates. vm/dart_2/isolates/reload_many_isolates_live_and_die_test: Performs a reload where isolates get spawned and die all the time. There are always P isolates alive at any given point in time, each of them spawns children when their parent has died. Performing a reload catches isolates as various stages of their lifecycle and can therefore cover a lot of corner cases. TEST=vm/dart_2/isolates/reload_*_test.dart Issue https://github.com/dart-lang/sdk/issues/36097 Change-Id: I97039b4084de040b7f2e22f5832a40d57ba398d5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/187461 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
2021-03-02 18:57:02 +00:00
class FieldInvalidator;
class FieldTable {
public:
explicit FieldTable(Isolate* isolate)
: top_(0),
capacity_(0),
free_head_(-1),
table_(nullptr),
old_tables_(new MallocGrowableArray<ObjectPtr*>()),
isolate_(isolate),
is_ready_to_use_(isolate == nullptr) {}
~FieldTable();
bool IsReadyToUse() const;
void MarkReadyToUse();
intptr_t NumFieldIds() const { return top_; }
intptr_t Capacity() const { return capacity_; }
ObjectPtr* table() { return table_; }
void FreeOldTables();
// Used by the generated code.
static intptr_t FieldOffsetFor(intptr_t field_id);
bool IsValidIndex(intptr_t index) const { return index >= 0 && index < top_; }
// Returns whether registering this field caused a growth in the backing
// store.
bool Register(const Field& field, intptr_t expected_field_id = -1);
void AllocateIndex(intptr_t index);
// Static field elements are being freed only during isolate reload
// when initially created static field have to get remapped to point
// to an existing static field value.
void Free(intptr_t index);
ObjectPtr At(intptr_t index, bool concurrent_use = false) const {
ASSERT(IsValidIndex(index));
if (concurrent_use) {
ObjectPtr* table =
reinterpret_cast<const AcqRelAtomic<ObjectPtr*>*>(&table_)->load();
return reinterpret_cast<AcqRelAtomic<ObjectPtr>*>(&table[index])->load();
} else {
// There is no concurrent access expected for this field, so we avoid
// using atomics. This will allow us to detect via TSAN if there are
// racy uses.
return table_[index];
}
}
void SetAt(intptr_t index,
ObjectPtr raw_instance,
bool concurrent_use = false) {
ASSERT(index < capacity_);
ObjectPtr* slot = &table_[index];
if (concurrent_use) {
reinterpret_cast<AcqRelAtomic<ObjectPtr>*>(slot)->store(raw_instance);
} else {
// There is no concurrent access expected for this field, so we avoid
// using atomics. This will allow us to detect via TSAN if there are
// racy uses.
*slot = raw_instance;
}
}
FieldTable* Clone(Isolate* for_isolate);
void VisitObjectPointers(ObjectPointerVisitor* visitor);
static const int kInitialCapacity = 512;
static const int kCapacityIncrement = 256;
private:
friend class GCMarker;
friend class MarkingWeakVisitor;
friend class Scavenger;
friend class ScavengerWeakVisitor;
void Grow(intptr_t new_capacity);
intptr_t top_;
intptr_t capacity_;
// -1 if free list is empty, otherwise index of first empty element. Empty
// elements are organized into linked list - they contain index of next
// element, last element contains -1.
intptr_t free_head_;
ObjectPtr* table_;
// When table_ grows and have to reallocated, keep the old one here
// so it will get freed when its are no longer in use.
MallocGrowableArray<ObjectPtr*>* old_tables_;
// If non-NULL, it will specify the isolate this field table belongs to.
// Growing the field table will keep the cached field table on the isolate's
// mutator thread up-to-date.
Isolate* isolate_;
// Whether this field table is ready to use by e.g. registering new static
// fields.
bool is_ready_to_use_ = false;
DISALLOW_COPY_AND_ASSIGN(FieldTable);
};
} // namespace dart
#endif // RUNTIME_VM_FIELD_TABLE_H_