Serializer: cache recent back references for shorter encoding.
And some refactorings. R=mvstanton@chromium.org Review URL: https://codereview.chromium.org/766893002 Cr-Commit-Position: refs/heads/master@{#25629}
This commit is contained in:
parent
02cb54e3cc
commit
07584119ca
@ -488,7 +488,7 @@ DEFINE_BOOL(trace_stub_failures, false,
|
|||||||
|
|
||||||
DEFINE_BOOL(serialize_toplevel, true, "enable caching of toplevel scripts")
|
DEFINE_BOOL(serialize_toplevel, true, "enable caching of toplevel scripts")
|
||||||
DEFINE_BOOL(serialize_inner, false, "enable caching of inner functions")
|
DEFINE_BOOL(serialize_inner, false, "enable caching of inner functions")
|
||||||
DEFINE_BOOL(trace_code_serializer, false, "print code serializer trace")
|
DEFINE_BOOL(trace_serializer, false, "print code serializer trace")
|
||||||
|
|
||||||
// compiler.cc
|
// compiler.cc
|
||||||
DEFINE_INT(min_preparse_length, 1024,
|
DEFINE_INT(min_preparse_length, 1024,
|
||||||
|
245
src/serialize.cc
245
src/serialize.cc
@ -794,10 +794,24 @@ HeapObject* Deserializer::ProcessNewObjectFromSerializedCode(HeapObject* obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Object* Deserializer::ProcessBackRefInSerializedCode(Object* obj) {
|
HeapObject* Deserializer::GetBackReferencedObject(int space) {
|
||||||
if (obj->IsInternalizedString()) {
|
HeapObject* obj;
|
||||||
return String::cast(obj)->GetForwardedInternalizedString();
|
if (space == LO_SPACE) {
|
||||||
|
uint32_t index = source_->GetInt();
|
||||||
|
obj = deserialized_large_objects_[index];
|
||||||
|
} else {
|
||||||
|
BackReference back_reference(source_->GetInt());
|
||||||
|
DCHECK(space < kNumberOfPreallocatedSpaces);
|
||||||
|
uint32_t chunk_index = back_reference.chunk_index();
|
||||||
|
DCHECK_LE(chunk_index, current_chunk_[space]);
|
||||||
|
uint32_t chunk_offset = back_reference.chunk_offset();
|
||||||
|
obj = HeapObject::FromAddress(reservations_[space][chunk_index].start +
|
||||||
|
chunk_offset);
|
||||||
}
|
}
|
||||||
|
if (deserializing_user_code() && obj->IsInternalizedString()) {
|
||||||
|
obj = String::cast(obj)->GetForwardedInternalizedString();
|
||||||
|
}
|
||||||
|
hot_objects_.Add(obj);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,7 +918,7 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
source_space != CODE_SPACE &&
|
source_space != CODE_SPACE &&
|
||||||
source_space != OLD_DATA_SPACE);
|
source_space != OLD_DATA_SPACE);
|
||||||
while (current < limit) {
|
while (current < limit) {
|
||||||
int data = source_->Get();
|
byte data = source_->Get();
|
||||||
switch (data) {
|
switch (data) {
|
||||||
#define CASE_STATEMENT(where, how, within, space_number) \
|
#define CASE_STATEMENT(where, how, within, space_number) \
|
||||||
case where + how + within + space_number: \
|
case where + how + within + space_number: \
|
||||||
@ -945,9 +959,6 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
} else if (where == kBackref) { \
|
} else if (where == kBackref) { \
|
||||||
emit_write_barrier = (space_number == NEW_SPACE); \
|
emit_write_barrier = (space_number == NEW_SPACE); \
|
||||||
new_object = GetBackReferencedObject(data & kSpaceMask); \
|
new_object = GetBackReferencedObject(data & kSpaceMask); \
|
||||||
if (deserializing_user_code()) { \
|
|
||||||
new_object = ProcessBackRefInSerializedCode(new_object); \
|
|
||||||
} \
|
|
||||||
} else if (where == kBuiltin) { \
|
} else if (where == kBuiltin) { \
|
||||||
DCHECK(deserializing_user_code()); \
|
DCHECK(deserializing_user_code()); \
|
||||||
int builtin_id = source_->GetInt(); \
|
int builtin_id = source_->GetInt(); \
|
||||||
@ -968,9 +979,6 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
reinterpret_cast<Address>(current) + skip); \
|
reinterpret_cast<Address>(current) + skip); \
|
||||||
emit_write_barrier = (space_number == NEW_SPACE); \
|
emit_write_barrier = (space_number == NEW_SPACE); \
|
||||||
new_object = GetBackReferencedObject(data & kSpaceMask); \
|
new_object = GetBackReferencedObject(data & kSpaceMask); \
|
||||||
if (deserializing_user_code()) { \
|
|
||||||
new_object = ProcessBackRefInSerializedCode(new_object); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
if (within == kInnerPointer) { \
|
if (within == kInnerPointer) { \
|
||||||
if (space_number != CODE_SPACE || new_object->IsCode()) { \
|
if (space_number != CODE_SPACE || new_object->IsCode()) { \
|
||||||
@ -1111,7 +1119,7 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case kRepeat: {
|
case kVariableRepeat: {
|
||||||
int repeats = source_->GetInt();
|
int repeats = source_->GetInt();
|
||||||
Object* object = current[-1];
|
Object* object = current[-1];
|
||||||
DCHECK(!isolate->heap()->InNewSpace(object));
|
DCHECK(!isolate->heap()->InNewSpace(object));
|
||||||
@ -1122,11 +1130,13 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
|
|
||||||
STATIC_ASSERT(kRootArrayNumberOfConstantEncodings ==
|
STATIC_ASSERT(kRootArrayNumberOfConstantEncodings ==
|
||||||
Heap::kOldSpaceRoots);
|
Heap::kOldSpaceRoots);
|
||||||
STATIC_ASSERT(kMaxRepeats == 13);
|
STATIC_ASSERT(kMaxFixedRepeats == 15);
|
||||||
case kConstantRepeat:
|
FOUR_CASES(kFixedRepeat)
|
||||||
FOUR_CASES(kConstantRepeat + 1)
|
FOUR_CASES(kFixedRepeat + 4)
|
||||||
FOUR_CASES(kConstantRepeat + 5)
|
FOUR_CASES(kFixedRepeat + 8)
|
||||||
FOUR_CASES(kConstantRepeat + 9) {
|
case kFixedRepeat + 12:
|
||||||
|
case kFixedRepeat + 13:
|
||||||
|
case kFixedRepeat + 14: {
|
||||||
int repeats = RepeatsForCode(data);
|
int repeats = RepeatsForCode(data);
|
||||||
Object* object = current[-1];
|
Object* object = current[-1];
|
||||||
DCHECK(!isolate->heap()->InNewSpace(object));
|
DCHECK(!isolate->heap()->InNewSpace(object));
|
||||||
@ -1261,6 +1271,27 @@ void Deserializer::ReadData(Object** current, Object** limit, int source_space,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FOUR_CASES(kHotObjectWithSkip)
|
||||||
|
FOUR_CASES(kHotObjectWithSkip + 4) {
|
||||||
|
int skip = source_->GetInt();
|
||||||
|
current = reinterpret_cast<Object**>(
|
||||||
|
reinterpret_cast<Address>(current) + skip);
|
||||||
|
// Fall through.
|
||||||
|
}
|
||||||
|
FOUR_CASES(kHotObject)
|
||||||
|
FOUR_CASES(kHotObject + 4) {
|
||||||
|
int index = data & kHotObjectIndexMask;
|
||||||
|
*current = hot_objects_.Get(index);
|
||||||
|
if (write_barrier_needed && isolate->heap()->InNewSpace(*current)) {
|
||||||
|
Address current_address = reinterpret_cast<Address>(current);
|
||||||
|
isolate->heap()->RecordWrite(
|
||||||
|
current_object_address,
|
||||||
|
static_cast<int>(current_address - current_object_address));
|
||||||
|
}
|
||||||
|
current++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case kSynchronize: {
|
case kSynchronize: {
|
||||||
// If we get here then that indicates that you have a mismatch between
|
// If we get here then that indicates that you have a mismatch between
|
||||||
// the number of GC roots when serializing and deserializing.
|
// the number of GC roots when serializing and deserializing.
|
||||||
@ -1324,7 +1355,7 @@ void StartupSerializer::VisitPointers(Object** start, Object** end) {
|
|||||||
sink_->Put(kSkip, "Skip");
|
sink_->Put(kSkip, "Skip");
|
||||||
sink_->PutInt(kPointerSize, "SkipOneWord");
|
sink_->PutInt(kPointerSize, "SkipOneWord");
|
||||||
} else if ((*current)->IsSmi()) {
|
} else if ((*current)->IsSmi()) {
|
||||||
sink_->Put(kRawData + 1, "Smi");
|
sink_->Put(kOnePointerRawData, "Smi");
|
||||||
for (int i = 0; i < kPointerSize; i++) {
|
for (int i = 0; i < kPointerSize; i++) {
|
||||||
sink_->Put(reinterpret_cast<byte*>(current)[i], "Byte");
|
sink_->Put(reinterpret_cast<byte*>(current)[i], "Byte");
|
||||||
}
|
}
|
||||||
@ -1352,7 +1383,7 @@ bool Serializer::ShouldBeSkipped(Object** current) {
|
|||||||
void Serializer::VisitPointers(Object** start, Object** end) {
|
void Serializer::VisitPointers(Object** start, Object** end) {
|
||||||
for (Object** current = start; current < end; current++) {
|
for (Object** current = start; current < end; current++) {
|
||||||
if ((*current)->IsSmi()) {
|
if ((*current)->IsSmi()) {
|
||||||
sink_->Put(kRawData + 1, "Smi");
|
sink_->Put(kOnePointerRawData, "Smi");
|
||||||
for (int i = 0; i < kPointerSize; i++) {
|
for (int i = 0; i < kPointerSize; i++) {
|
||||||
sink_->Put(reinterpret_cast<byte*>(current)[i], "Byte");
|
sink_->Put(reinterpret_cast<byte*>(current)[i], "Byte");
|
||||||
}
|
}
|
||||||
@ -1426,24 +1457,61 @@ int PartialSerializer::PartialSnapshotCacheIndex(HeapObject* heap_object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Encode the location of an already deserialized object in order to write its
|
bool Serializer::SerializeKnownObject(HeapObject* obj, HowToCode how_to_code,
|
||||||
// location into a later object. We can encode the location as an offset from
|
WhereToPoint where_to_point, int skip) {
|
||||||
// the start of the deserialized objects or as an offset backwards from the
|
if (how_to_code == kPlain && where_to_point == kStartOfObject) {
|
||||||
// current allocation pointer.
|
// Encode a reference to a hot object by its index in the working set.
|
||||||
void Serializer::SerializeBackReference(BackReference back_reference,
|
int index = hot_objects_.Find(obj);
|
||||||
HowToCode how_to_code,
|
if (index != HotObjectsList::kNotFound) {
|
||||||
WhereToPoint where_to_point, int skip) {
|
DCHECK(index >= 0 && index <= kMaxHotObjectIndex);
|
||||||
AllocationSpace space = back_reference.space();
|
if (FLAG_trace_serializer) {
|
||||||
if (skip == 0) {
|
PrintF(" Encoding hot object %d:", index);
|
||||||
sink_->Put(kBackref + how_to_code + where_to_point + space, "BackRefSer");
|
obj->ShortPrint();
|
||||||
} else {
|
PrintF("\n");
|
||||||
sink_->Put(kBackrefWithSkip + how_to_code + where_to_point + space,
|
}
|
||||||
"BackRefSerWithSkip");
|
if (skip != 0) {
|
||||||
sink_->PutInt(skip, "BackRefSkipDistance");
|
sink_->Put(kHotObjectWithSkip + index, "HotObjectWithSkip");
|
||||||
|
sink_->PutInt(skip, "HotObjectSkipDistance");
|
||||||
|
} else {
|
||||||
|
sink_->Put(kHotObject + index, "HotObject");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BackReference back_reference = back_reference_map_.Lookup(obj);
|
||||||
|
if (back_reference.is_valid()) {
|
||||||
|
// Encode the location of an already deserialized object in order to write
|
||||||
|
// its location into a later object. We can encode the location as an
|
||||||
|
// offset fromthe start of the deserialized objects or as an offset
|
||||||
|
// backwards from thecurrent allocation pointer.
|
||||||
|
if (back_reference.is_source()) {
|
||||||
|
FlushSkip(skip);
|
||||||
|
if (FLAG_trace_serializer) PrintF(" Encoding source object\n");
|
||||||
|
DCHECK(how_to_code == kPlain && where_to_point == kStartOfObject);
|
||||||
|
sink_->Put(kAttachedReference + how_to_code + where_to_point, "Source");
|
||||||
|
sink_->PutInt(kSourceObjectReference, "kSourceObjectIndex");
|
||||||
|
} else {
|
||||||
|
if (FLAG_trace_serializer) {
|
||||||
|
PrintF(" Encoding back reference to: ");
|
||||||
|
obj->ShortPrint();
|
||||||
|
PrintF("\n");
|
||||||
|
}
|
||||||
|
|
||||||
sink_->PutInt(back_reference.reference(),
|
AllocationSpace space = back_reference.space();
|
||||||
(space == LO_SPACE) ? "large object index" : "allocation");
|
if (skip == 0) {
|
||||||
|
sink_->Put(kBackref + how_to_code + where_to_point + space, "BackRef");
|
||||||
|
} else {
|
||||||
|
sink_->Put(kBackrefWithSkip + how_to_code + where_to_point + space,
|
||||||
|
"BackRefWithSkip");
|
||||||
|
sink_->PutInt(skip, "BackRefSkipDistance");
|
||||||
|
}
|
||||||
|
sink_->PutInt(back_reference.reference(), "BackRefValue");
|
||||||
|
|
||||||
|
hot_objects_.Add(obj);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1460,16 +1528,9 @@ void StartupSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackReference back_reference = back_reference_map_.Lookup(obj);
|
if (SerializeKnownObject(obj, how_to_code, where_to_point, skip)) return;
|
||||||
if (back_reference.is_valid()) {
|
|
||||||
SerializeBackReference(back_reference, how_to_code, where_to_point, skip);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip != 0) {
|
FlushSkip(skip);
|
||||||
sink_->Put(kSkip, "FlushPendingSkip");
|
|
||||||
sink_->PutInt(skip, "SkipDistance");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object has not yet been serialized. Serialize it here.
|
// Object has not yet been serialized. Serialize it here.
|
||||||
ObjectSerializer object_serializer(this, obj, sink_, how_to_code,
|
ObjectSerializer object_serializer(this, obj, sink_, how_to_code,
|
||||||
@ -1479,7 +1540,7 @@ void StartupSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
|
|
||||||
|
|
||||||
void StartupSerializer::SerializeWeakReferences() {
|
void StartupSerializer::SerializeWeakReferences() {
|
||||||
// This phase comes right after the partial serialization (of the snapshot).
|
// This phase comes right after the serialization (of the snapshot).
|
||||||
// After we have done the partial serialization the partial snapshot cache
|
// After we have done the partial serialization the partial snapshot cache
|
||||||
// will contain some references needed to decode the partial snapshot. We
|
// will contain some references needed to decode the partial snapshot. We
|
||||||
// add one entry with 'undefined' which is the sentinel that the deserializer
|
// add one entry with 'undefined' which is the sentinel that the deserializer
|
||||||
@ -1496,6 +1557,12 @@ void Serializer::PutRoot(int root_index,
|
|||||||
SerializerDeserializer::HowToCode how_to_code,
|
SerializerDeserializer::HowToCode how_to_code,
|
||||||
SerializerDeserializer::WhereToPoint where_to_point,
|
SerializerDeserializer::WhereToPoint where_to_point,
|
||||||
int skip) {
|
int skip) {
|
||||||
|
if (FLAG_trace_serializer) {
|
||||||
|
PrintF(" Encoding root %d:", root_index);
|
||||||
|
object->ShortPrint();
|
||||||
|
PrintF("\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (how_to_code == kPlain &&
|
if (how_to_code == kPlain &&
|
||||||
where_to_point == kStartOfObject &&
|
where_to_point == kStartOfObject &&
|
||||||
root_index < kRootArrayNumberOfConstantEncodings &&
|
root_index < kRootArrayNumberOfConstantEncodings &&
|
||||||
@ -1509,10 +1576,7 @@ void Serializer::PutRoot(int root_index,
|
|||||||
sink_->PutInt(skip, "SkipInPutRoot");
|
sink_->PutInt(skip, "SkipInPutRoot");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (skip != 0) {
|
FlushSkip(skip);
|
||||||
sink_->Put(kSkip, "SkipFromPutRoot");
|
|
||||||
sink_->PutInt(skip, "SkipFromPutRootDistance");
|
|
||||||
}
|
|
||||||
sink_->Put(kRootArray + how_to_code + where_to_point, "RootSerialization");
|
sink_->Put(kRootArray + how_to_code + where_to_point, "RootSerialization");
|
||||||
sink_->PutInt(root_index, "root_index");
|
sink_->PutInt(root_index, "root_index");
|
||||||
}
|
}
|
||||||
@ -1534,10 +1598,7 @@ void PartialSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldBeInThePartialSnapshotCache(obj)) {
|
if (ShouldBeInThePartialSnapshotCache(obj)) {
|
||||||
if (skip != 0) {
|
FlushSkip(skip);
|
||||||
sink_->Put(kSkip, "SkipFromSerializeObject");
|
|
||||||
sink_->PutInt(skip, "SkipDistanceFromSerializeObject");
|
|
||||||
}
|
|
||||||
|
|
||||||
int cache_index = PartialSnapshotCacheIndex(obj);
|
int cache_index = PartialSnapshotCacheIndex(obj);
|
||||||
sink_->Put(kPartialSnapshotCache + how_to_code + where_to_point,
|
sink_->Put(kPartialSnapshotCache + how_to_code + where_to_point,
|
||||||
@ -1554,16 +1615,10 @@ void PartialSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
// either in the root table or in the partial snapshot cache.
|
// either in the root table or in the partial snapshot cache.
|
||||||
DCHECK(!obj->IsInternalizedString());
|
DCHECK(!obj->IsInternalizedString());
|
||||||
|
|
||||||
BackReference back_reference = back_reference_map_.Lookup(obj);
|
if (SerializeKnownObject(obj, how_to_code, where_to_point, skip)) return;
|
||||||
if (back_reference.is_valid()) {
|
|
||||||
SerializeBackReference(back_reference, how_to_code, where_to_point, skip);
|
FlushSkip(skip);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip != 0) {
|
|
||||||
sink_->Put(kSkip, "SkipFromSerializeObject");
|
|
||||||
sink_->PutInt(skip, "SkipDistanceFromSerializeObject");
|
|
||||||
}
|
|
||||||
// Object has not yet been serialized. Serialize it here.
|
// Object has not yet been serialized. Serialize it here.
|
||||||
ObjectSerializer serializer(this, obj, sink_, how_to_code, where_to_point);
|
ObjectSerializer serializer(this, obj, sink_, how_to_code, where_to_point);
|
||||||
serializer.Serialize();
|
serializer.Serialize();
|
||||||
@ -1584,8 +1639,8 @@ void Serializer::ObjectSerializer::SerializePrologue(AllocationSpace space,
|
|||||||
BackReference back_reference;
|
BackReference back_reference;
|
||||||
if (space == LO_SPACE) {
|
if (space == LO_SPACE) {
|
||||||
sink_->Put(kNewObject + reference_representation_ + space,
|
sink_->Put(kNewObject + reference_representation_ + space,
|
||||||
"new large object");
|
"NewLargeObject");
|
||||||
sink_->PutInt(size >> kObjectAlignmentBits, "Size in words");
|
sink_->PutInt(size >> kObjectAlignmentBits, "ObjectSizeInWords");
|
||||||
if (object_->IsCode()) {
|
if (object_->IsCode()) {
|
||||||
sink_->Put(EXECUTABLE, "executable large object");
|
sink_->Put(EXECUTABLE, "executable large object");
|
||||||
} else {
|
} else {
|
||||||
@ -1596,14 +1651,14 @@ void Serializer::ObjectSerializer::SerializePrologue(AllocationSpace space,
|
|||||||
if (object_->NeedsToEnsureDoubleAlignment()) {
|
if (object_->NeedsToEnsureDoubleAlignment()) {
|
||||||
// Add wriggle room for double alignment padding.
|
// Add wriggle room for double alignment padding.
|
||||||
back_reference = serializer_->Allocate(space, size + kPointerSize);
|
back_reference = serializer_->Allocate(space, size + kPointerSize);
|
||||||
sink_->PutInt(kDoubleAlignmentSentinel, "double align");
|
sink_->PutInt(kDoubleAlignmentSentinel, "DoubleAlignSentinel");
|
||||||
} else {
|
} else {
|
||||||
back_reference = serializer_->Allocate(space, size);
|
back_reference = serializer_->Allocate(space, size);
|
||||||
}
|
}
|
||||||
sink_->Put(kNewObject + reference_representation_ + space, "new object");
|
sink_->Put(kNewObject + reference_representation_ + space, "NewObject");
|
||||||
int encoded_size = size >> kObjectAlignmentBits;
|
int encoded_size = size >> kObjectAlignmentBits;
|
||||||
DCHECK_NE(kDoubleAlignmentSentinel, encoded_size);
|
DCHECK_NE(kDoubleAlignmentSentinel, encoded_size);
|
||||||
sink_->PutInt(encoded_size, "Size in words");
|
sink_->PutInt(encoded_size, "ObjectSizeInWords");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this object as already serialized.
|
// Mark this object as already serialized.
|
||||||
@ -1677,6 +1732,12 @@ void Serializer::ObjectSerializer::SerializeExternalString() {
|
|||||||
|
|
||||||
|
|
||||||
void Serializer::ObjectSerializer::Serialize() {
|
void Serializer::ObjectSerializer::Serialize() {
|
||||||
|
if (FLAG_trace_serializer) {
|
||||||
|
PrintF(" Encoding heap object: ");
|
||||||
|
object_->ShortPrint();
|
||||||
|
PrintF("\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (object_->IsExternalString()) {
|
if (object_->IsExternalString()) {
|
||||||
Heap* heap = serializer_->isolate()->heap();
|
Heap* heap = serializer_->isolate()->heap();
|
||||||
if (object_->map() != heap->native_source_string_map()) {
|
if (object_->map() != heap->native_source_string_map()) {
|
||||||
@ -1727,8 +1788,8 @@ void Serializer::ObjectSerializer::VisitPointers(Object** start,
|
|||||||
}
|
}
|
||||||
current += repeat_count;
|
current += repeat_count;
|
||||||
bytes_processed_so_far_ += repeat_count * kPointerSize;
|
bytes_processed_so_far_ += repeat_count * kPointerSize;
|
||||||
if (repeat_count > kMaxRepeats) {
|
if (repeat_count > kMaxFixedRepeats) {
|
||||||
sink_->Put(kRepeat, "SerializeRepeats");
|
sink_->Put(kVariableRepeat, "SerializeRepeats");
|
||||||
sink_->PutInt(repeat_count, "SerializeRepeats");
|
sink_->PutInt(repeat_count, "SerializeRepeats");
|
||||||
} else {
|
} else {
|
||||||
sink_->Put(CodeForRepeats(repeat_count), "SerializeRepeats");
|
sink_->Put(CodeForRepeats(repeat_count), "SerializeRepeats");
|
||||||
@ -1957,8 +2018,8 @@ BackReference Serializer::Allocate(AllocationSpace space, int size) {
|
|||||||
if (new_chunk_size > max_chunk_size(space)) {
|
if (new_chunk_size > max_chunk_size(space)) {
|
||||||
// The new chunk size would not fit onto a single page. Complete the
|
// The new chunk size would not fit onto a single page. Complete the
|
||||||
// current chunk and start a new one.
|
// current chunk and start a new one.
|
||||||
sink_->Put(kNextChunk, "move to next chunk");
|
sink_->Put(kNextChunk, "NextChunk");
|
||||||
sink_->Put(space, "space of next chunk");
|
sink_->Put(space, "NextChunkSpace");
|
||||||
completed_chunks_[space].Add(pending_chunk_[space]);
|
completed_chunks_[space].Add(pending_chunk_[space]);
|
||||||
pending_chunk_[space] = 0;
|
pending_chunk_[space] = 0;
|
||||||
new_chunk_size = size;
|
new_chunk_size = size;
|
||||||
@ -1990,7 +2051,7 @@ ScriptData* CodeSerializer::Serialize(Isolate* isolate,
|
|||||||
Handle<String> source) {
|
Handle<String> source) {
|
||||||
base::ElapsedTimer timer;
|
base::ElapsedTimer timer;
|
||||||
if (FLAG_profile_deserialization) timer.Start();
|
if (FLAG_profile_deserialization) timer.Start();
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF("[Serializing from");
|
PrintF("[Serializing from");
|
||||||
Object* script = info->script();
|
Object* script = info->script();
|
||||||
if (script->IsScript()) Script::cast(script)->name()->ShortPrint();
|
if (script->IsScript()) Script::cast(script)->name()->ShortPrint();
|
||||||
@ -2030,33 +2091,13 @@ void CodeSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
WhereToPoint where_to_point, int skip) {
|
WhereToPoint where_to_point, int skip) {
|
||||||
int root_index = root_index_map_.Lookup(obj);
|
int root_index = root_index_map_.Lookup(obj);
|
||||||
if (root_index != RootIndexMap::kInvalidRootIndex) {
|
if (root_index != RootIndexMap::kInvalidRootIndex) {
|
||||||
if (FLAG_trace_code_serializer) {
|
|
||||||
PrintF(" Encoding root: %d\n", root_index);
|
|
||||||
}
|
|
||||||
PutRoot(root_index, obj, how_to_code, where_to_point, skip);
|
PutRoot(root_index, obj, how_to_code, where_to_point, skip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackReference back_reference = back_reference_map_.Lookup(obj);
|
if (SerializeKnownObject(obj, how_to_code, where_to_point, skip)) return;
|
||||||
if (back_reference.is_valid()) {
|
|
||||||
if (back_reference.is_source()) {
|
|
||||||
DCHECK_EQ(source_, obj);
|
|
||||||
SerializeSourceObject(how_to_code, where_to_point);
|
|
||||||
} else {
|
|
||||||
if (FLAG_trace_code_serializer) {
|
|
||||||
PrintF(" Encoding back reference to: ");
|
|
||||||
obj->ShortPrint();
|
|
||||||
PrintF("\n");
|
|
||||||
}
|
|
||||||
SerializeBackReference(back_reference, how_to_code, where_to_point, skip);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip != 0) {
|
FlushSkip(skip);
|
||||||
sink_->Put(kSkip, "SkipFromSerializeObject");
|
|
||||||
sink_->PutInt(skip, "SkipDistanceFromSerializeObject");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj->IsCode()) {
|
if (obj->IsCode()) {
|
||||||
Code* code_object = Code::cast(obj);
|
Code* code_object = Code::cast(obj);
|
||||||
@ -2109,12 +2150,6 @@ void CodeSerializer::SerializeObject(HeapObject* obj, HowToCode how_to_code,
|
|||||||
void CodeSerializer::SerializeGeneric(HeapObject* heap_object,
|
void CodeSerializer::SerializeGeneric(HeapObject* heap_object,
|
||||||
HowToCode how_to_code,
|
HowToCode how_to_code,
|
||||||
WhereToPoint where_to_point) {
|
WhereToPoint where_to_point) {
|
||||||
if (FLAG_trace_code_serializer) {
|
|
||||||
PrintF(" Encoding heap object: ");
|
|
||||||
heap_object->ShortPrint();
|
|
||||||
PrintF("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (heap_object->IsInternalizedString()) num_internalized_strings_++;
|
if (heap_object->IsInternalizedString()) num_internalized_strings_++;
|
||||||
|
|
||||||
// Object has not yet been serialized. Serialize it here.
|
// Object has not yet been serialized. Serialize it here.
|
||||||
@ -2132,7 +2167,7 @@ void CodeSerializer::SerializeBuiltin(int builtin_index, HowToCode how_to_code,
|
|||||||
DCHECK_LT(builtin_index, Builtins::builtin_count);
|
DCHECK_LT(builtin_index, Builtins::builtin_count);
|
||||||
DCHECK_LE(0, builtin_index);
|
DCHECK_LE(0, builtin_index);
|
||||||
|
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF(" Encoding builtin: %s\n",
|
PrintF(" Encoding builtin: %s\n",
|
||||||
isolate()->builtins()->name(builtin_index));
|
isolate()->builtins()->name(builtin_index));
|
||||||
}
|
}
|
||||||
@ -2152,7 +2187,7 @@ void CodeSerializer::SerializeCodeStub(uint32_t stub_key, HowToCode how_to_code,
|
|||||||
|
|
||||||
int index = AddCodeStubKey(stub_key) + kCodeStubsBaseIndex;
|
int index = AddCodeStubKey(stub_key) + kCodeStubsBaseIndex;
|
||||||
|
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF(" Encoding code stub %s as %d\n",
|
PrintF(" Encoding code stub %s as %d\n",
|
||||||
CodeStub::MajorName(CodeStub::MajorKeyFromKey(stub_key), false),
|
CodeStub::MajorName(CodeStub::MajorKeyFromKey(stub_key), false),
|
||||||
index);
|
index);
|
||||||
@ -2168,7 +2203,7 @@ void CodeSerializer::SerializeIC(Code* ic, HowToCode how_to_code,
|
|||||||
// The IC may be implemented as a stub.
|
// The IC may be implemented as a stub.
|
||||||
uint32_t stub_key = ic->stub_key();
|
uint32_t stub_key = ic->stub_key();
|
||||||
if (stub_key != CodeStub::NoCacheKey()) {
|
if (stub_key != CodeStub::NoCacheKey()) {
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF(" %s is a code stub\n", Code::Kind2String(ic->kind()));
|
PrintF(" %s is a code stub\n", Code::Kind2String(ic->kind()));
|
||||||
}
|
}
|
||||||
SerializeCodeStub(stub_key, how_to_code, where_to_point);
|
SerializeCodeStub(stub_key, how_to_code, where_to_point);
|
||||||
@ -2182,7 +2217,7 @@ void CodeSerializer::SerializeIC(Code* ic, HowToCode how_to_code,
|
|||||||
Builtins::Name name = static_cast<Builtins::Name>(builtin_index);
|
Builtins::Name name = static_cast<Builtins::Name>(builtin_index);
|
||||||
Code* builtin = isolate()->builtins()->builtin(name);
|
Code* builtin = isolate()->builtins()->builtin(name);
|
||||||
if (builtin == ic) {
|
if (builtin == ic) {
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF(" %s is a builtin\n", Code::Kind2String(ic->kind()));
|
PrintF(" %s is a builtin\n", Code::Kind2String(ic->kind()));
|
||||||
}
|
}
|
||||||
DCHECK(ic->kind() == Code::KEYED_LOAD_IC ||
|
DCHECK(ic->kind() == Code::KEYED_LOAD_IC ||
|
||||||
@ -2193,7 +2228,7 @@ void CodeSerializer::SerializeIC(Code* ic, HowToCode how_to_code,
|
|||||||
}
|
}
|
||||||
// The IC may also just be a piece of code kept in the non_monomorphic_cache.
|
// The IC may also just be a piece of code kept in the non_monomorphic_cache.
|
||||||
// In that case, just serialize as a normal code object.
|
// In that case, just serialize as a normal code object.
|
||||||
if (FLAG_trace_code_serializer) {
|
if (FLAG_trace_serializer) {
|
||||||
PrintF(" %s has no special handling\n", Code::Kind2String(ic->kind()));
|
PrintF(" %s has no special handling\n", Code::Kind2String(ic->kind()));
|
||||||
}
|
}
|
||||||
DCHECK(ic->kind() == Code::LOAD_IC || ic->kind() == Code::STORE_IC);
|
DCHECK(ic->kind() == Code::LOAD_IC || ic->kind() == Code::STORE_IC);
|
||||||
@ -2215,7 +2250,7 @@ int CodeSerializer::AddCodeStubKey(uint32_t stub_key) {
|
|||||||
|
|
||||||
void CodeSerializer::SerializeSourceObject(HowToCode how_to_code,
|
void CodeSerializer::SerializeSourceObject(HowToCode how_to_code,
|
||||||
WhereToPoint where_to_point) {
|
WhereToPoint where_to_point) {
|
||||||
if (FLAG_trace_code_serializer) PrintF(" Encoding source object\n");
|
if (FLAG_trace_serializer) PrintF(" Encoding source object\n");
|
||||||
|
|
||||||
DCHECK(how_to_code == kPlain && where_to_point == kStartOfObject);
|
DCHECK(how_to_code == kPlain && where_to_point == kStartOfObject);
|
||||||
sink_->Put(kAttachedReference + how_to_code + where_to_point, "Source");
|
sink_->Put(kAttachedReference + how_to_code + where_to_point, "Source");
|
||||||
|
137
src/serialize.h
137
src/serialize.h
@ -285,6 +285,43 @@ class BackReferenceMap : public AddressMapBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class HotObjectsList {
|
||||||
|
public:
|
||||||
|
HotObjectsList() : index_(0) {
|
||||||
|
for (int i = 0; i < kSize; i++) circular_queue_[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(HeapObject* object) {
|
||||||
|
circular_queue_[index_] = object;
|
||||||
|
index_ = (index_ + 1) & kSizeMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapObject* Get(int index) {
|
||||||
|
DCHECK_NE(NULL, circular_queue_[index]);
|
||||||
|
return circular_queue_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int kNotFound = -1;
|
||||||
|
|
||||||
|
int Find(HeapObject* object) {
|
||||||
|
for (int i = 0; i < kSize; i++) {
|
||||||
|
if (circular_queue_[i] == object) return i;
|
||||||
|
}
|
||||||
|
return kNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int kSize = 8;
|
||||||
|
|
||||||
|
private:
|
||||||
|
STATIC_ASSERT(IS_POWER_OF_TWO(kSize));
|
||||||
|
static const int kSizeMask = kSize - 1;
|
||||||
|
HeapObject* circular_queue_[kSize];
|
||||||
|
int index_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(HotObjectsList);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// The Serializer/Deserializer class is a common superclass for Serializer and
|
// The Serializer/Deserializer class is a common superclass for Serializer and
|
||||||
// Deserializer which is used to store common constants and methods used by
|
// Deserializer which is used to store common constants and methods used by
|
||||||
// both.
|
// both.
|
||||||
@ -303,6 +340,7 @@ class SerializerDeserializer: public ObjectVisitor {
|
|||||||
enum Where {
|
enum Where {
|
||||||
kNewObject = 0, // Object is next in snapshot.
|
kNewObject = 0, // Object is next in snapshot.
|
||||||
// 1-7 One per space.
|
// 1-7 One per space.
|
||||||
|
// 0x8 Unused.
|
||||||
kRootArray = 0x9, // Object is found in root array.
|
kRootArray = 0x9, // Object is found in root array.
|
||||||
kPartialSnapshotCache = 0xa, // Object is in the cache.
|
kPartialSnapshotCache = 0xa, // Object is in the cache.
|
||||||
kExternalReference = 0xb, // Pointer to an external reference.
|
kExternalReference = 0xb, // Pointer to an external reference.
|
||||||
@ -346,38 +384,58 @@ class SerializerDeserializer: public ObjectVisitor {
|
|||||||
// entire code in one memcpy, then fix up stuff with kSkip and other byte
|
// entire code in one memcpy, then fix up stuff with kSkip and other byte
|
||||||
// codes that overwrite data.
|
// codes that overwrite data.
|
||||||
static const int kRawData = 0x20;
|
static const int kRawData = 0x20;
|
||||||
// Some common raw lengths: 0x21-0x3f. These autoadvance the current pointer.
|
// Some common raw lengths: 0x21-0x3f.
|
||||||
// A tag emitted at strategic points in the snapshot to delineate sections.
|
// These autoadvance the current pointer.
|
||||||
// If the deserializer does not find these at the expected moments then it
|
static const int kOnePointerRawData = 0x21;
|
||||||
// is an indication that the snapshot and the VM do not fit together.
|
|
||||||
// Examine the build process for architecture, version or configuration
|
static const int kVariableRepeat = 0x60;
|
||||||
// mismatches.
|
// 0x61-0x6f Repeat last word
|
||||||
static const int kSynchronize = 0x70;
|
static const int kFixedRepeat = 0x61;
|
||||||
// Used for the source code of the natives, which is in the executable, but
|
static const int kFixedRepeatBase = kFixedRepeat - 1;
|
||||||
// is referred to from external strings in the snapshot.
|
static const int kLastFixedRepeat = 0x6f;
|
||||||
static const int kNativesStringResource = 0x71;
|
static const int kMaxFixedRepeats = kLastFixedRepeat - kFixedRepeatBase;
|
||||||
static const int kRepeat = 0x72;
|
|
||||||
static const int kConstantRepeat = 0x73;
|
|
||||||
// 0x73-0x7f Repeat last word (subtract 0x72 to get the count).
|
|
||||||
static const int kMaxRepeats = 0x7f - 0x72;
|
|
||||||
static int CodeForRepeats(int repeats) {
|
static int CodeForRepeats(int repeats) {
|
||||||
DCHECK(repeats >= 1 && repeats <= kMaxRepeats);
|
DCHECK(repeats >= 1 && repeats <= kMaxFixedRepeats);
|
||||||
return 0x72 + repeats;
|
return kFixedRepeatBase + repeats;
|
||||||
}
|
}
|
||||||
static int RepeatsForCode(int byte_code) {
|
static int RepeatsForCode(int byte_code) {
|
||||||
DCHECK(byte_code >= kConstantRepeat && byte_code <= 0x7f);
|
DCHECK(byte_code > kFixedRepeatBase && byte_code <= kLastFixedRepeat);
|
||||||
return byte_code - 0x72;
|
return byte_code - kFixedRepeatBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hot objects are a small set of recently seen or back-referenced objects.
|
||||||
|
// They are represented by a single opcode to save space.
|
||||||
|
// We use 0x70..0x77 for 8 hot objects, and 0x78..0x7f to add skip.
|
||||||
|
static const int kHotObject = 0x70;
|
||||||
|
static const int kMaxHotObjectIndex = 0x77 - kHotObject;
|
||||||
|
static const int kHotObjectWithSkip = 0x78;
|
||||||
|
STATIC_ASSERT(HotObjectsList::kSize == kMaxHotObjectIndex + 1);
|
||||||
|
STATIC_ASSERT(0x7f - kHotObjectWithSkip == kMaxHotObjectIndex);
|
||||||
|
static const int kHotObjectIndexMask = 0x7;
|
||||||
|
|
||||||
static const int kRootArrayConstants = 0xa0;
|
static const int kRootArrayConstants = 0xa0;
|
||||||
// 0xa0-0xbf Things from the first 32 elements of the root array.
|
// 0xa0-0xbf Things from the first 32 elements of the root array.
|
||||||
static const int kRootArrayNumberOfConstantEncodings = 0x20;
|
static const int kRootArrayNumberOfConstantEncodings = 0x20;
|
||||||
static int RootArrayConstantFromByteCode(int byte_code) {
|
static int RootArrayConstantFromByteCode(int byte_code) {
|
||||||
return byte_code & 0x1f;
|
return byte_code & 0x1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int kNop = 0xf; // Do nothing, used for padding.
|
// Do nothing, used for padding.
|
||||||
|
static const int kNop = 0xf;
|
||||||
|
|
||||||
static const int kNextChunk = 0x4f; // Move to next reserved chunk.
|
// Move to next reserved chunk.
|
||||||
|
static const int kNextChunk = 0x4f;
|
||||||
|
|
||||||
|
// A tag emitted at strategic points in the snapshot to delineate sections.
|
||||||
|
// If the deserializer does not find these at the expected moments then it
|
||||||
|
// is an indication that the snapshot and the VM do not fit together.
|
||||||
|
// Examine the build process for architecture, version or configuration
|
||||||
|
// mismatches.
|
||||||
|
static const int kSynchronize = 0x8f;
|
||||||
|
|
||||||
|
// Used for the source code of the natives, which is in the executable, but
|
||||||
|
// is referred to from external strings in the snapshot.
|
||||||
|
static const int kNativesStringResource = 0xcf;
|
||||||
|
|
||||||
static const int kAnyOldSpace = -1;
|
static const int kAnyOldSpace = -1;
|
||||||
|
|
||||||
@ -387,6 +445,11 @@ class SerializerDeserializer: public ObjectVisitor {
|
|||||||
|
|
||||||
// Sentinel after a new object to indicate that double alignment is needed.
|
// Sentinel after a new object to indicate that double alignment is needed.
|
||||||
static const int kDoubleAlignmentSentinel = 0;
|
static const int kDoubleAlignmentSentinel = 0;
|
||||||
|
|
||||||
|
// Used as index for the attached reference representing the source object.
|
||||||
|
static const int kSourceObjectReference = 0;
|
||||||
|
|
||||||
|
HotObjectsList hot_objects_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -449,24 +512,10 @@ class Deserializer: public SerializerDeserializer {
|
|||||||
|
|
||||||
// Special handling for serialized code like hooking up internalized strings.
|
// Special handling for serialized code like hooking up internalized strings.
|
||||||
HeapObject* ProcessNewObjectFromSerializedCode(HeapObject* obj);
|
HeapObject* ProcessNewObjectFromSerializedCode(HeapObject* obj);
|
||||||
Object* ProcessBackRefInSerializedCode(Object* obj);
|
|
||||||
|
|
||||||
// This returns the address of an object that has been described in the
|
// This returns the address of an object that has been described in the
|
||||||
// snapshot by chunk index and offset.
|
// snapshot by chunk index and offset.
|
||||||
HeapObject* GetBackReferencedObject(int space) {
|
HeapObject* GetBackReferencedObject(int space);
|
||||||
if (space == LO_SPACE) {
|
|
||||||
uint32_t index = source_->GetInt();
|
|
||||||
return deserialized_large_objects_[index];
|
|
||||||
} else {
|
|
||||||
BackReference back_reference(source_->GetInt());
|
|
||||||
DCHECK(space < kNumberOfPreallocatedSpaces);
|
|
||||||
uint32_t chunk_index = back_reference.chunk_index();
|
|
||||||
DCHECK_LE(chunk_index, current_chunk_[space]);
|
|
||||||
uint32_t chunk_offset = back_reference.chunk_offset();
|
|
||||||
return HeapObject::FromAddress(reservations_[space][chunk_index].start +
|
|
||||||
chunk_offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached current isolate.
|
// Cached current isolate.
|
||||||
Isolate* isolate_;
|
Isolate* isolate_;
|
||||||
@ -576,9 +625,17 @@ class Serializer : public SerializerDeserializer {
|
|||||||
void PutRoot(int index, HeapObject* object, HowToCode how, WhereToPoint where,
|
void PutRoot(int index, HeapObject* object, HowToCode how, WhereToPoint where,
|
||||||
int skip);
|
int skip);
|
||||||
|
|
||||||
void SerializeBackReference(BackReference back_reference,
|
// Returns true if the object was successfully serialized.
|
||||||
HowToCode how_to_code,
|
bool SerializeKnownObject(HeapObject* obj, HowToCode how_to_code,
|
||||||
WhereToPoint where_to_point, int skip);
|
WhereToPoint where_to_point, int skip);
|
||||||
|
|
||||||
|
inline void FlushSkip(int skip) {
|
||||||
|
if (skip != 0) {
|
||||||
|
sink_->Put(kSkip, "SkipFromSerializeObject");
|
||||||
|
sink_->PutInt(skip, "SkipDistanceFromSerializeObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InitializeAllocators();
|
void InitializeAllocators();
|
||||||
// This will return the space for an object.
|
// This will return the space for an object.
|
||||||
static AllocationSpace SpaceOfObject(HeapObject* object);
|
static AllocationSpace SpaceOfObject(HeapObject* object);
|
||||||
@ -716,6 +773,8 @@ class CodeSerializer : public Serializer {
|
|||||||
Isolate* isolate, ScriptData* cached_data, Handle<String> source);
|
Isolate* isolate, ScriptData* cached_data, Handle<String> source);
|
||||||
|
|
||||||
static const int kSourceObjectIndex = 0;
|
static const int kSourceObjectIndex = 0;
|
||||||
|
STATIC_ASSERT(kSourceObjectReference == kSourceObjectIndex);
|
||||||
|
|
||||||
static const int kCodeStubsBaseIndex = 1;
|
static const int kCodeStubsBaseIndex = 1;
|
||||||
|
|
||||||
String* source() const {
|
String* source() const {
|
||||||
|
@ -24,7 +24,7 @@ class SnapshotByteSource FINAL {
|
|||||||
|
|
||||||
bool HasMore() { return position_ < length_; }
|
bool HasMore() { return position_ < length_; }
|
||||||
|
|
||||||
int Get() {
|
byte Get() {
|
||||||
DCHECK(position_ < length_);
|
DCHECK(position_ < length_);
|
||||||
return data_[position_++];
|
return data_[position_++];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user