Index: vm/snapshot.cc |
=================================================================== |
--- vm/snapshot.cc (revision 8515) |
+++ vm/snapshot.cc (working copy) |
@@ -89,12 +89,14 @@ |
RawObject* SnapshotReader::ReadObject() { |
- int64_t value = Read<int64_t>(); |
- if ((value & kSmiTagMask) == 0) { |
- return Integer::New((value >> kSmiTagShift)); |
+ Object& obj = Object::Handle(ReadObjectImpl()); |
+ for (intptr_t i = 0; i < backward_references_.length(); i++) { |
+ if (!backward_references_[i]->is_deserialized()) { |
+ ReadObjectImpl(); |
+ backward_references_[i]->set_state(kIsDeserialized); |
+ } |
} |
- ASSERT((value <= kIntptrMax) && (value >= kIntptrMin)); |
- return ReadObjectImpl(value); |
+ return obj.raw(); |
} |
@@ -105,13 +107,13 @@ |
ASSERT((class_header & kSmiTagMask) != 0); |
Class& cls = Class::ZoneHandle(isolate(), Class::null()); |
cls ^= LookupInternalClass(class_header); |
- AddBackwardReference(object_id, &cls); |
+ AddBackRef(object_id, &cls, kIsDeserialized); |
if (cls.IsNull()) { |
// Read the library/class information and lookup the class. |
str_ ^= ReadObjectImpl(class_header); |
library_ = Library::LookupLibrary(str_); |
ASSERT(!library_.IsNull()); |
- str_ ^= ReadObject(); |
+ str_ ^= ReadObjectImpl(); |
cls ^= library_.LookupClass(str_); |
} |
ASSERT(!cls.IsNull()); |
@@ -119,12 +121,135 @@ |
} |
-void SnapshotReader::AddBackwardReference(intptr_t id, Object* obj) { |
- ASSERT((id - kMaxPredefinedObjectIds) == backward_references_.length()); |
- backward_references_.Add(obj); |
+RawObject* SnapshotReader::ReadObjectImpl() { |
+ int64_t value = Read<int64_t>(); |
+ if ((value & kSmiTagMask) == 0) { |
+ return Integer::New((value >> kSmiTagShift)); |
+ } |
+ return ReadObjectImpl(value); |
} |
+RawObject* SnapshotReader::ReadObjectImpl(intptr_t value) { |
+ ASSERT((value <= kIntptrMax) && (value >= kIntptrMin)); |
+ SerializedHeaderType header_type = SerializedHeaderTag::decode(value); |
+ intptr_t header_value = SerializedHeaderData::decode(value); |
+ |
+ if (header_type == kObjectId) { |
+ return ReadIndexedObject(header_value); |
+ } |
+ ASSERT(header_type == kInlined); |
+ return ReadInlinedObject(header_value); |
+} |
+ |
+ |
+RawObject* SnapshotReader::ReadObjectRef() { |
+ int64_t value = Read<int64_t>(); |
+ if ((value & kSmiTagMask) == 0) { |
+ return Integer::New((value >> kSmiTagShift)); |
+ } |
+ ASSERT((value <= kIntptrMax) && (value >= kIntptrMin)); |
+ SerializedHeaderType header_type = SerializedHeaderTag::decode(value); |
+ intptr_t header_value = SerializedHeaderData::decode(value); |
+ |
+ if (header_type == kObjectId) { |
+ return ReadIndexedObject(header_value); |
+ } |
+ ASSERT(header_type == kInlined); |
+ intptr_t object_id = header_value; |
+ ASSERT(GetBackRef(object_id) == NULL); |
+ |
+ // Read the class header information and lookup the class. |
+ intptr_t class_header = ReadIntptrValue(); |
+ |
+ // Since we are only reading an object reference, If it is an instance kind |
+ // then we only need to figure out the class of the object and allocate an |
+ // instance of it. The individual fields will be read later. |
+ if (SerializedHeaderData::decode(class_header) == kInstanceId) { |
+ Instance& result = Instance::ZoneHandle(isolate(), Instance::null()); |
+ AddBackRef(object_id, &result, kIsNotDeserialized); |
+ |
+ cls_ ^= ReadObjectImpl(); // Read class information. |
+ ASSERT(!cls_.IsNull()); |
+ intptr_t instance_size = cls_.instance_size(); |
+ ASSERT(instance_size > 0); |
+ if (kind_ == Snapshot::kFull) { |
+ result ^= AllocateUninitialized(cls_, instance_size); |
+ } else { |
+ result ^= Object::Allocate(cls_, instance_size, Heap::kNew); |
+ } |
+ return result.raw(); |
+ } else { |
+ ASSERT((class_header & kSmiTagMask) != 0); |
+ cls_ ^= LookupInternalClass(class_header); |
+ ASSERT(!cls_.IsNull()); |
+ } |
+ |
+ // Similarly Array and ImmutableArray objects are also similarly only |
+ // allocated here, the individual array elements are read later. |
+ ObjectKind obj_kind = cls_.instance_kind(); |
+ if (obj_kind == Array::kInstanceKind) { |
+ // Read the length and allocate an object based on the len. |
+ intptr_t len = ReadSmiValue(); |
+ Array& array = Array::ZoneHandle( |
+ isolate(), |
+ (kind_ == Snapshot::kFull) ? NewArray(len) : Array::New(len)); |
+ AddBackRef(object_id, &array, kIsNotDeserialized); |
+ |
+ return array.raw(); |
+ } |
+ if (obj_kind == ImmutableArray::kInstanceKind) { |
+ // Read the length and allocate an object based on the len. |
+ intptr_t len = ReadSmiValue(); |
+ ImmutableArray& array = ImmutableArray::ZoneHandle( |
+ isolate(), |
+ (kind_ == Snapshot::kFull) ? |
+ NewImmutableArray(len) : ImmutableArray::New(len)); |
+ AddBackRef(object_id, &array, kIsNotDeserialized); |
+ |
+ return array.raw(); |
+ } |
+ |
+ // For all other internal VM classes we read the object inline. |
+ intptr_t tags = ReadIntptrValue(); |
+ switch (obj_kind) { |
+#define SNAPSHOT_READ(clazz) \ |
+ case clazz::kInstanceKind: { \ |
+ obj_ = clazz::ReadFrom(this, object_id, tags, kind_); \ |
+ break; \ |
+ } |
+ CLASS_LIST_NO_OBJECT(SNAPSHOT_READ) |
+#undef SNAPSHOT_READ |
+ default: UNREACHABLE(); break; |
+ } |
+ if (kind_ == Snapshot::kFull) { |
+ obj_.SetCreatedFromSnapshot(); |
+ } |
+ return obj_.raw(); |
+} |
+ |
+ |
+void SnapshotReader::AddBackRef(intptr_t id, |
+ Object* obj, |
+ DeserializeState state) { |
+ intptr_t index = (id - kMaxPredefinedObjectIds); |
+ ASSERT(index == backward_references_.length()); |
+ BackRefNode* node = new BackRefNode(obj, state); |
+ ASSERT(node != NULL); |
+ backward_references_.Add(node); |
+} |
+ |
+ |
+Object* SnapshotReader::GetBackRef(intptr_t id) { |
+ ASSERT(id >= kMaxPredefinedObjectIds); |
+ intptr_t index = (id - kMaxPredefinedObjectIds); |
+ if (index < backward_references_.length()) { |
+ return backward_references_[index]->reference(); |
+ } |
+ return NULL; |
+} |
+ |
+ |
void SnapshotReader::ReadFullSnapshot() { |
ASSERT(kind_ == Snapshot::kFull); |
Isolate* isolate = Isolate::Current(); |
@@ -139,8 +264,14 @@ |
// Read in all the objects stored in the object store. |
intptr_t num_flds = (object_store->to() - object_store->from()); |
for (intptr_t i = 0; i <= num_flds; i++) { |
- *(object_store->from() + i) = ReadObject(); |
+ *(object_store->from() + i) = ReadObjectImpl(); |
} |
+ for (intptr_t i = 0; i < backward_references_.length(); i++) { |
+ if (!backward_references_[i]->is_deserialized()) { |
+ ReadObjectImpl(); |
+ backward_references_[i]->set_state(kIsDeserialized); |
+ } |
+ } |
// Setup native resolver for bootstrap impl. |
Bootstrap::SetupNativeResolver(); |
@@ -378,18 +509,6 @@ |
} |
-RawObject* SnapshotReader::ReadObjectImpl(intptr_t header) { |
- SerializedHeaderType header_type = SerializedHeaderTag::decode(header); |
- intptr_t header_value = SerializedHeaderData::decode(header); |
- |
- if (header_type == kObjectId) { |
- return ReadIndexedObject(header_value); |
- } |
- ASSERT(header_type == kInlined); |
- return ReadInlinedObject(header_value); |
-} |
- |
- |
RawObject* SnapshotReader::ReadIndexedObject(intptr_t object_id) { |
if (object_id == Object::kNullObject) { |
// This is a singleton null object, return it. |
@@ -412,10 +531,8 @@ |
} |
} |
- ASSERT(object_id >= kMaxPredefinedObjectIds); |
- intptr_t index = object_id - kMaxPredefinedObjectIds; |
- ASSERT(index < backward_references_.length()); |
- return backward_references_[index]->raw(); |
+ Object* object = GetBackRef(object_id); |
+ return object->raw(); |
} |
@@ -425,31 +542,38 @@ |
intptr_t tags = ReadIntptrValue(); |
if (SerializedHeaderData::decode(class_header) == kInstanceId) { |
// Object is regular dart instance. |
- Instance& result = Instance::ZoneHandle(isolate(), Instance::null()); |
- AddBackwardReference(object_id, &result); |
- |
- cls_ ^= ReadObject(); |
- ASSERT(!cls_.IsNull()); |
- intptr_t instance_size = cls_.instance_size(); |
- ASSERT(instance_size > 0); |
- // Allocate the instance and read in all the fields for the object. |
- if (kind_ == Snapshot::kFull) { |
- result ^= AllocateUninitialized(cls_, instance_size); |
+ Instance* result = reinterpret_cast<Instance*>(GetBackRef(object_id)); |
+ intptr_t instance_size = 0; |
+ if (result == NULL) { |
+ result = &(Instance::ZoneHandle(isolate(), Instance::null())); |
+ AddBackRef(object_id, result, kIsDeserialized); |
+ cls_ ^= ReadObjectImpl(); |
+ ASSERT(!cls_.IsNull()); |
+ instance_size = cls_.instance_size(); |
+ ASSERT(instance_size > 0); |
+ // Allocate the instance and read in all the fields for the object. |
+ if (kind_ == Snapshot::kFull) { |
+ *result ^= AllocateUninitialized(cls_, instance_size); |
+ } else { |
+ *result ^= Object::Allocate(cls_, instance_size, Heap::kNew); |
+ } |
} else { |
- result ^= Object::Allocate(cls_, instance_size, Heap::kNew); |
+ cls_ ^= ReadObjectImpl(); |
+ ASSERT(!cls_.IsNull()); |
+ instance_size = cls_.instance_size(); |
} |
intptr_t offset = Object::InstanceSize(); |
while (offset < instance_size) { |
- obj_ = ReadObject(); |
- result.SetFieldAtOffset(offset, obj_); |
+ obj_ = ReadObjectRef(); |
+ result->SetFieldAtOffset(offset, obj_); |
offset += kWordSize; |
} |
if (kind_ == Snapshot::kFull) { |
- result.SetCreatedFromSnapshot(); |
- } else if (result.IsCanonical()) { |
- result = result.Canonicalize(); |
+ result->SetCreatedFromSnapshot(); |
+ } else if (result->IsCanonical()) { |
+ *result = result->Canonicalize(); |
} |
- return result.raw(); |
+ return result->raw(); |
} else { |
ASSERT((class_header & kSmiTagMask) != 0); |
cls_ ^= LookupInternalClass(class_header); |
@@ -472,26 +596,123 @@ |
} |
+void SnapshotReader::ArrayReadFrom(const Array& result, |
+ intptr_t len, |
+ intptr_t tags) { |
+ // Set the object tags. |
+ result.set_tags(tags); |
+ |
+ // Setup the object fields. |
+ *TypeArgumentsHandle() ^= ReadObjectImpl(); |
+ result.SetTypeArguments(*TypeArgumentsHandle()); |
+ |
+ for (intptr_t i = 0; i < len; i++) { |
+ *ObjectHandle() = ReadObjectRef(); |
+ result.SetAt(i, *ObjectHandle()); |
+ } |
+} |
+ |
+ |
void SnapshotWriter::WriteObject(RawObject* rawobj) { |
+ WriteObjectImpl(rawobj); |
+ WriteForwardedObjects(); |
+} |
+ |
+ |
+void SnapshotWriter::WriteObjectRef(RawObject* raw) { |
// First check if object can be written as a simple predefined type. |
- if (CheckAndWritePredefinedObject(rawobj)) { |
+ if (CheckAndWritePredefinedObject(raw)) { |
return; |
} |
- // Now write the object out inline in the stream as follows: |
- // - Object is seen for the first time (inlined as follows): |
- // (object size in multiples of kObjectAlignment | 0x1) |
- // serialized fields of the object |
- // ...... |
- WriteInlinedObject(rawobj); |
-} |
+ // Check if object has already been serialized, in that |
+ // case just write the object id out. |
+ uword tags = raw->ptr()->tags_; |
+ if (SerializedHeaderTag::decode(tags) == kObjectId) { |
+ intptr_t id = SerializedHeaderData::decode(tags); |
+ WriteIndexedObject(id); |
+ return; |
+ } |
+ NoGCScope no_gc; |
+ RawClass* cls = class_table_->At(RawObject::ClassIdTag::decode(tags)); |
-void SnapshotWriter::UnmarkAll() { |
- NoGCScope no_gc; |
- for (intptr_t i = 0; i < forward_list_.length(); i++) { |
- RawObject* raw = forward_list_[i]->raw(); |
- raw->ptr()->tags_ = forward_list_[i]->tags(); // Restore original tags. |
+ ObjectKind obj_kind = cls->ptr()->instance_kind_; |
+ if (obj_kind == Instance::kInstanceKind) { |
+ // Object is being referenced, add it to the forward ref list and mark |
+ // it so that future references to this object in the snapshot will use |
+ // this object id. Mark it as not having been serialized yet so that we |
+ // will serialize the object when we go through the forward list. |
+ intptr_t object_id = MarkObject(raw, kIsNotSerialized); |
+ |
+ // Write out the serialization header value for this object. |
+ WriteSerializationMarker(kInlined, object_id); |
+ |
+ // Indicate this is an instance object. |
+ WriteIntptrValue(SerializedHeaderData::encode(kInstanceId)); |
+ |
+ // Write out the class information for this object. |
+ WriteObjectImpl(cls); |
+ |
+ return; |
} |
+ if (obj_kind == Array::kInstanceKind) { |
+ // Object is being referenced, add it to the forward ref list and mark |
+ // it so that future references to this object in the snapshot will use |
+ // this object id. Mark it as not having been serialized yet so that we |
+ // will serialize the object when we go through the forward list. |
+ intptr_t object_id = MarkObject(raw, kIsNotSerialized); |
+ |
+ RawArray* rawarray = reinterpret_cast<RawArray*>(raw); |
+ |
+ // Write out the serialization header value for this object. |
+ WriteSerializationMarker(kInlined, object_id); |
+ |
+ // Write out the class information. |
+ WriteIndexedObject(ObjectStore::kArrayClass); |
+ |
+ // Write out the length field. |
+ Write<RawObject*>(rawarray->ptr()->length_); |
+ |
+ return; |
+ } |
+ if (obj_kind == ImmutableArray::kInstanceKind) { |
+ // Object is being referenced, add it to the forward ref list and mark |
+ // it so that future references to this object in the snapshot will use |
+ // this object id. Mark it as not having been serialized yet so that we |
+ // will serialize the object when we go through the forward list. |
+ intptr_t object_id = MarkObject(raw, kIsNotSerialized); |
+ |
+ RawArray* rawarray = reinterpret_cast<RawArray*>(raw); |
+ |
+ // Write out the serialization header value for this object. |
+ WriteSerializationMarker(kInlined, object_id); |
+ |
+ // Write out the class information. |
+ WriteIndexedObject(ObjectStore::kImmutableArrayClass); |
+ |
+ // Write out the length field. |
+ Write<RawObject*>(rawarray->ptr()->length_); |
+ |
+ return; |
+ } |
+ // Object is being referenced, add it to the forward ref list and mark |
+ // it so that future references to this object in the snapshot will use |
+ // this object id. Mark it as not having been serialized yet so that we |
+ // will serialize the object when we go through the forward list. |
+ intptr_t object_id = MarkObject(raw, kIsSerialized); |
+ switch (obj_kind) { |
+#define SNAPSHOT_WRITE(clazz) \ |
+ case clazz::kInstanceKind: { \ |
+ Raw##clazz* raw_obj = reinterpret_cast<Raw##clazz*>(raw); \ |
+ raw_obj->WriteTo(this, object_id, kind_); \ |
+ return; \ |
+ } \ |
+ |
+ CLASS_LIST_NO_OBJECT(SNAPSHOT_WRITE) |
+#undef SNAPSHOT_WRITE |
+ default: break; |
+ } |
+ UNREACHABLE(); |
} |
@@ -504,9 +725,12 @@ |
// Write out all the objects in the object store of the isolate which |
// is the root set for all dart allocated objects at this point. |
- SnapshotWriterVisitor visitor(this); |
+ SnapshotWriterVisitor visitor(this, false); |
object_store->VisitObjectPointers(&visitor); |
+ // Write out all forwarded objects. |
+ WriteForwardedObjects(); |
+ |
// Finalize the snapshot buffer. |
FinalizeBuffer(); |
} |
@@ -523,7 +747,7 @@ |
} |
-intptr_t SnapshotWriter::MarkObject(RawObject* raw) { |
+intptr_t SnapshotWriter::MarkObject(RawObject* raw, SerializeState state) { |
NoGCScope no_gc; |
intptr_t object_id = forward_list_.length() + kMaxPredefinedObjectIds; |
ASSERT(object_id <= kMaxObjectId); |
@@ -532,13 +756,22 @@ |
value = SerializedHeaderData::update(object_id, value); |
uword tags = raw->ptr()->tags_; |
raw->ptr()->tags_ = value; |
- ForwardObjectNode* node = new ForwardObjectNode(raw, tags); |
+ ForwardObjectNode* node = new ForwardObjectNode(raw, tags, state); |
ASSERT(node != NULL); |
forward_list_.Add(node); |
return object_id; |
} |
+void SnapshotWriter::UnmarkAll() { |
+ NoGCScope no_gc; |
+ for (intptr_t i = 0; i < forward_list_.length(); i++) { |
+ RawObject* raw = forward_list_[i]->raw(); |
+ raw->ptr()->tags_ = forward_list_[i]->tags(); // Restore original tags. |
+ } |
+} |
+ |
+ |
bool SnapshotWriter::CheckAndWritePredefinedObject(RawObject* rawobj) { |
// Check if object can be written in one of the following ways: |
// - Smi: the Smi value is written as is (last bit is not tagged). |
@@ -603,30 +836,51 @@ |
} |
} |
+ return false; |
+} |
+ |
+ |
+void SnapshotWriter::WriteObjectImpl(RawObject* raw) { |
+ // First check if object can be written as a simple predefined type. |
+ if (CheckAndWritePredefinedObject(raw)) { |
+ return; |
+ } |
+ |
// Check if object has already been serialized, in that |
// case just write the object id out. |
- uword tags = rawobj->ptr()->tags_; |
+ uword tags = raw->ptr()->tags_; |
if (SerializedHeaderTag::decode(tags) == kObjectId) { |
intptr_t id = SerializedHeaderData::decode(tags); |
WriteIndexedObject(id); |
- return true; |
+ return; |
} |
- return false; |
+ // Object is being serialized, add it to the forward ref list and mark |
+ // it so that future references to this object in the snapshot will use |
+ // an object id, instead of trying to serialize it again. |
+ MarkObject(raw, kIsSerialized); |
+ |
+ WriteInlinedObject(raw); |
} |
void SnapshotWriter::WriteInlinedObject(RawObject* raw) { |
+ // Assert object is not a simple predefined type. |
+ ASSERT(!CheckAndWritePredefinedObject(raw)); |
+ |
+ // Now write the object out inline in the stream as follows: |
+ // - Object is seen for the first time (inlined as follows): |
+ // (object size in multiples of kObjectAlignment | 0x1) |
+ // serialized fields of the object |
+ // ...... |
NoGCScope no_gc; |
uword tags = raw->ptr()->tags_; |
+ ASSERT(SerializedHeaderTag::decode(tags) == kObjectId); |
+ intptr_t object_id = SerializedHeaderData::decode(tags); |
+ tags = forward_list_[object_id - kMaxPredefinedObjectIds]->tags(); |
RawClass* cls = class_table_->At(RawObject::ClassIdTag::decode(tags)); |
+ ObjectKind kind = cls->ptr()->instance_kind_; |
- // Object is being serialized, add it to the forward ref list and mark |
- // it so that future references to this object in the snapshot will use |
- // an object id, instead of trying to serialize it again. |
- intptr_t object_id = MarkObject(raw); |
- |
- ObjectKind kind = cls->ptr()->instance_kind_; |
if (kind == Instance::kInstanceKind) { |
// Object is regular dart instance. |
// TODO(5411462): figure out what we need to do if an object with native |
@@ -645,12 +899,12 @@ |
WriteIntptrValue(tags); |
// Write out the class information for this object. |
- WriteObject(cls); |
+ WriteObjectImpl(cls); |
// Write out all the fields for the object. |
intptr_t offset = Object::InstanceSize(); |
while (offset < instance_size) { |
- WriteObject(*reinterpret_cast<RawObject**>( |
+ WriteObjectRef(*reinterpret_cast<RawObject**>( |
reinterpret_cast<uword>(raw->ptr()) + offset)); |
offset += kWordSize; |
} |
@@ -672,6 +926,25 @@ |
} |
+void SnapshotWriter::WriteForwardedObjects() { |
+ // Write out all objects that were added to the forward list and have |
+ // not been serialized yet. These would typically be fields of instance |
+ // objects, arrays or immutable arrays (this is done in order to avoid |
+ // deep recursive calls to WriteObjectImpl). |
+ // NOTE: The forward list might grow as we process the list. |
+ for (intptr_t i = 0; i < forward_list_.length(); i++) { |
+ if (!forward_list_[i]->is_serialized()) { |
+ // Write the object out in the stream. |
+ RawObject* raw = forward_list_[i]->raw(); |
+ WriteInlinedObject(raw); |
+ |
+ // Mark object as serialized. |
+ forward_list_[i]->set_state(kIsSerialized); |
+ } |
+ } |
+} |
+ |
+ |
void SnapshotWriter::WriteClassId(RawClass* cls) { |
ASSERT(kind_ != Snapshot::kFull); |
int id = object_store()->GetClassIndex(cls); |
@@ -681,17 +954,44 @@ |
// TODO(5411462): Should restrict this to only core-lib classes in this |
// case. |
// Write out the class and tags information. |
- WriteObjectHeader(Object::kClassClass, cls->ptr()->tags_); |
+ WriteObjectHeader(Object::kClassClass, GetObjectTags(cls)); |
// Write out the library url and class name. |
RawLibrary* library = cls->ptr()->library_; |
ASSERT(library != Library::null()); |
- WriteObject(library->ptr()->url_); |
- WriteObject(cls->ptr()->name_); |
+ WriteObjectImpl(library->ptr()->url_); |
+ WriteObjectImpl(cls->ptr()->name_); |
} |
} |
+void SnapshotWriter::ArrayWriteTo(intptr_t object_id, |
+ intptr_t array_kind, |
+ intptr_t tags, |
+ RawSmi* length, |
+ RawAbstractTypeArguments* type_arguments, |
+ RawObject* data[]) { |
+ intptr_t len = Smi::Value(length); |
+ |
+ // Write out the serialization header value for this object. |
+ WriteSerializationMarker(kInlined, object_id); |
+ |
+ // Write out the class and tags information. |
+ WriteObjectHeader(array_kind, tags); |
+ |
+ // Write out the length field. |
+ Write<RawObject*>(length); |
+ |
+ // Write out the type arguments. |
+ WriteObjectImpl(type_arguments); |
+ |
+ // Write out the individual object ids. |
+ for (intptr_t i = 0; i < len; i++) { |
+ WriteObjectRef(data[i]); |
+ } |
+} |
+ |
+ |
void ScriptSnapshotWriter::WriteScriptSnapshot(const Library& lib) { |
ASSERT(kind() == Snapshot::kScript); |
@@ -706,7 +1006,11 @@ |
void SnapshotWriterVisitor::VisitPointers(RawObject** first, RawObject** last) { |
for (RawObject** current = first; current <= last; current++) { |
RawObject* raw_obj = *current; |
- writer_->WriteObject(raw_obj); |
+ if (as_references_) { |
+ writer_->WriteObjectRef(raw_obj); |
+ } else { |
+ writer_->WriteObjectImpl(raw_obj); |
+ } |
} |
} |