|  | // Copyright (c) 2012, 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. | 
|  |  | 
|  | #include "vm/raw_object.h" | 
|  |  | 
|  | #include "vm/class_table.h" | 
|  | #include "vm/dart.h" | 
|  | #include "vm/heap/become.h" | 
|  | #include "vm/heap/freelist.h" | 
|  | #include "vm/isolate.h" | 
|  | #include "vm/isolate_reload.h" | 
|  | #include "vm/object.h" | 
|  | #include "vm/runtime_entry.h" | 
|  | #include "vm/stack_frame.h" | 
|  | #include "vm/visitor.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | bool UntaggedObject::InVMIsolateHeap() const { | 
|  | // All "vm-isolate" objects are pre-marked and in old space | 
|  | // (see [Object::FinalizeVMIsolate]). | 
|  | if (!IsOldObject() || !IsMarked()) return false; | 
|  |  | 
|  | auto heap = Dart::vm_isolate_group()->heap(); | 
|  | ASSERT(heap->UsedInWords(Heap::kNew) == 0); | 
|  | return heap->old_space()->ContainsUnsafe(ToAddr(this)); | 
|  | } | 
|  |  | 
|  | void ObjectPtr::Validate(IsolateGroup* isolate_group) const { | 
|  | // All Smi values are valid. | 
|  | if (!IsHeapObject()) { | 
|  | return; | 
|  | } | 
|  | // Slightly more readable than a segfault. | 
|  | if (tagged_pointer_ == kHeapObjectTag) { | 
|  | FATAL("RAW_NULL encountered"); | 
|  | } | 
|  | untag()->Validate(isolate_group); | 
|  | } | 
|  |  | 
|  | void UntaggedObject::Validate(IsolateGroup* isolate_group) const { | 
|  | if (static_cast<uword>(Object::void_class_) == kHeapObjectTag) { | 
|  | // Validation relies on properly initialized class classes. Skip if the | 
|  | // VM is still being initialized. | 
|  | return; | 
|  | } | 
|  | // Validate that the tags_ field is sensible. | 
|  | uword tags = tags_; | 
|  | if (IsNewObject()) { | 
|  | if (!NewOrEvacuationCandidateBit::decode(tags)) { | 
|  | FATAL("New object missing kNewBit: %" Px "\n", tags); | 
|  | } | 
|  | if (OldAndNotRememberedBit::decode(tags)) { | 
|  | FATAL("New object has kOldAndNotRememberedBit: %" Px "\n", tags); | 
|  | } | 
|  | } | 
|  | const intptr_t class_id = ClassIdTag::decode(tags); | 
|  | if (!isolate_group->class_table()->IsValidIndex(class_id)) { | 
|  | FATAL("Invalid class id encountered %" Pd "\n", class_id); | 
|  | } | 
|  | if (class_id == kNullCid && | 
|  | isolate_group->class_table()->HasValidClassAt(class_id)) { | 
|  | // Null class not yet initialized; skip. | 
|  | return; | 
|  | } | 
|  | intptr_t size_from_tags = SizeTag::decode(tags); | 
|  | intptr_t size_from_class = HeapSizeFromClass(tags); | 
|  | if ((size_from_tags != 0) && (size_from_tags != size_from_class)) { | 
|  | FATAL( | 
|  | "Inconsistent size encountered " | 
|  | "cid: %" Pd ", size_from_tags: %" Pd ", size_from_class: %" Pd "\n", | 
|  | class_id, size_from_tags, size_from_class); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Can't look at the class object because it can be called during | 
|  | // compaction when the class objects are moving. Can use the class | 
|  | // id in the header and the sizes in the Class Table. | 
|  | // Cannot deference ptr()->tags_. May dereference other parts of the object. | 
|  | intptr_t UntaggedObject::HeapSizeFromClass(uword tags) const { | 
|  | intptr_t class_id = ClassIdTag::decode(tags); | 
|  | intptr_t instance_size = 0; | 
|  | switch (class_id) { | 
|  | case kCodeCid: { | 
|  | const CodePtr raw_code = static_cast<const CodePtr>(this); | 
|  | intptr_t pointer_offsets_length = | 
|  | Code::PtrOffBits::decode(raw_code->untag()->state_bits_); | 
|  | instance_size = Code::InstanceSize(pointer_offsets_length); | 
|  | break; | 
|  | } | 
|  | case kInstructionsCid: { | 
|  | const InstructionsPtr raw_instructions = | 
|  | static_cast<const InstructionsPtr>(this); | 
|  | intptr_t instructions_size = Instructions::Size(raw_instructions); | 
|  | instance_size = Instructions::InstanceSize(instructions_size); | 
|  | break; | 
|  | } | 
|  | case kInstructionsSectionCid: { | 
|  | const InstructionsSectionPtr raw_section = | 
|  | static_cast<const InstructionsSectionPtr>(this); | 
|  | intptr_t section_size = InstructionsSection::Size(raw_section); | 
|  | instance_size = InstructionsSection::InstanceSize(section_size); | 
|  | break; | 
|  | } | 
|  | case kContextCid: { | 
|  | const ContextPtr raw_context = static_cast<const ContextPtr>(this); | 
|  | intptr_t num_variables = raw_context->untag()->num_variables_; | 
|  | instance_size = Context::InstanceSize(num_variables); | 
|  | break; | 
|  | } | 
|  | case kContextScopeCid: { | 
|  | const ContextScopePtr raw_context_scope = | 
|  | static_cast<const ContextScopePtr>(this); | 
|  | intptr_t num_variables = raw_context_scope->untag()->num_variables_; | 
|  | instance_size = ContextScope::InstanceSize(num_variables); | 
|  | break; | 
|  | } | 
|  | case kOneByteStringCid: { | 
|  | const OneByteStringPtr raw_string = | 
|  | static_cast<const OneByteStringPtr>(this); | 
|  | intptr_t string_length = Smi::Value(raw_string->untag()->length()); | 
|  | instance_size = OneByteString::InstanceSize(string_length); | 
|  | break; | 
|  | } | 
|  | case kTwoByteStringCid: { | 
|  | const TwoByteStringPtr raw_string = | 
|  | static_cast<const TwoByteStringPtr>(this); | 
|  | intptr_t string_length = Smi::Value(raw_string->untag()->length()); | 
|  | instance_size = TwoByteString::InstanceSize(string_length); | 
|  | break; | 
|  | } | 
|  | case kArrayCid: | 
|  | case kImmutableArrayCid: { | 
|  | const ArrayPtr raw_array = static_cast<const ArrayPtr>(this); | 
|  | intptr_t array_length = | 
|  | Smi::Value(raw_array->untag()->length<std::memory_order_acquire>()); | 
|  | instance_size = Array::InstanceSize(array_length); | 
|  | break; | 
|  | } | 
|  | case kWeakArrayCid: { | 
|  | const WeakArrayPtr raw_array = static_cast<const WeakArrayPtr>(this); | 
|  | intptr_t array_length = Smi::Value(raw_array->untag()->length()); | 
|  | instance_size = WeakArray::InstanceSize(array_length); | 
|  | break; | 
|  | } | 
|  | case kObjectPoolCid: { | 
|  | const ObjectPoolPtr raw_object_pool = | 
|  | static_cast<const ObjectPoolPtr>(this); | 
|  | intptr_t len = raw_object_pool->untag()->length_; | 
|  | instance_size = ObjectPool::InstanceSize(len); | 
|  | break; | 
|  | } | 
|  | case kRecordCid: { | 
|  | const RecordPtr raw_record = static_cast<const RecordPtr>(this); | 
|  | intptr_t num_fields = | 
|  | RecordShape(raw_record->untag()->shape()).num_fields(); | 
|  | instance_size = Record::InstanceSize(num_fields); | 
|  | break; | 
|  | } | 
|  | #define SIZE_FROM_CLASS(clazz) case kTypedData##clazz##Cid: | 
|  | CLASS_LIST_TYPED_DATA(SIZE_FROM_CLASS) { | 
|  | const TypedDataPtr raw_obj = static_cast<const TypedDataPtr>(this); | 
|  | intptr_t array_len = Smi::Value(raw_obj->untag()->length()); | 
|  | intptr_t lengthInBytes = | 
|  | array_len * TypedData::ElementSizeInBytes(class_id); | 
|  | instance_size = TypedData::InstanceSize(lengthInBytes); | 
|  | break; | 
|  | } | 
|  | #undef SIZE_FROM_CLASS | 
|  | case kPointerCid: | 
|  | instance_size = Pointer::InstanceSize(); | 
|  | break; | 
|  | case kSuspendStateCid: { | 
|  | const SuspendStatePtr raw_suspend_state = | 
|  | static_cast<const SuspendStatePtr>(this); | 
|  | intptr_t frame_capacity = raw_suspend_state->untag()->frame_capacity(); | 
|  | instance_size = SuspendState::InstanceSize(frame_capacity); | 
|  | break; | 
|  | } | 
|  | case kTypeArgumentsCid: { | 
|  | const TypeArgumentsPtr raw_array = | 
|  | static_cast<const TypeArgumentsPtr>(this); | 
|  | intptr_t array_length = Smi::Value(raw_array->untag()->length()); | 
|  | instance_size = TypeArguments::InstanceSize(array_length); | 
|  | break; | 
|  | } | 
|  | case kPcDescriptorsCid: { | 
|  | const PcDescriptorsPtr raw_descriptors = | 
|  | static_cast<const PcDescriptorsPtr>(this); | 
|  | intptr_t length = raw_descriptors->untag()->length_; | 
|  | instance_size = PcDescriptors::InstanceSize(length); | 
|  | break; | 
|  | } | 
|  | case kCodeSourceMapCid: { | 
|  | const CodeSourceMapPtr raw_code_source_map = | 
|  | static_cast<const CodeSourceMapPtr>(this); | 
|  | intptr_t length = raw_code_source_map->untag()->length_; | 
|  | instance_size = CodeSourceMap::InstanceSize(length); | 
|  | break; | 
|  | } | 
|  | case kCompressedStackMapsCid: { | 
|  | const CompressedStackMapsPtr maps = | 
|  | static_cast<const CompressedStackMapsPtr>(this); | 
|  | intptr_t length = CompressedStackMaps::PayloadSizeOf(maps); | 
|  | instance_size = CompressedStackMaps::InstanceSize(length); | 
|  | break; | 
|  | } | 
|  | case kLocalVarDescriptorsCid: { | 
|  | const LocalVarDescriptorsPtr raw_descriptors = | 
|  | static_cast<const LocalVarDescriptorsPtr>(this); | 
|  | intptr_t num_descriptors = raw_descriptors->untag()->num_entries_; | 
|  | instance_size = LocalVarDescriptors::InstanceSize(num_descriptors); | 
|  | break; | 
|  | } | 
|  | case kExceptionHandlersCid: { | 
|  | const ExceptionHandlersPtr raw_handlers = | 
|  | static_cast<const ExceptionHandlersPtr>(this); | 
|  | intptr_t num_handlers = raw_handlers->untag()->num_entries(); | 
|  | instance_size = ExceptionHandlers::InstanceSize(num_handlers); | 
|  | break; | 
|  | } | 
|  | case kFreeListElement: { | 
|  | uword addr = UntaggedObject::ToAddr(this); | 
|  | FreeListElement* element = reinterpret_cast<FreeListElement*>(addr); | 
|  | instance_size = element->HeapSize(tags); | 
|  | break; | 
|  | } | 
|  | case kForwardingCorpse: { | 
|  | uword addr = UntaggedObject::ToAddr(this); | 
|  | ForwardingCorpse* element = reinterpret_cast<ForwardingCorpse*>(addr); | 
|  | instance_size = element->HeapSize(tags); | 
|  | break; | 
|  | } | 
|  | case kWeakSerializationReferenceCid: { | 
|  | instance_size = WeakSerializationReference::InstanceSize(); | 
|  | break; | 
|  | } | 
|  | default: { | 
|  | // Get the (constant) instance size out of the class object. | 
|  | // TODO(koda): Add Size(ClassTable*) interface to allow caching in loops. | 
|  | auto isolate_group = IsolateGroup::Current(); | 
|  | #if defined(DEBUG) | 
|  | auto class_table = isolate_group->heap_walk_class_table(); | 
|  | if (!class_table->IsValidIndex(class_id) || | 
|  | !class_table->HasValidClassAt(class_id)) { | 
|  | FATAL("Invalid cid: %" Pd ", obj: %p, tags: %x. Corrupt heap?", | 
|  | class_id, this, static_cast<uint32_t>(tags)); | 
|  | } | 
|  | ASSERT(class_table->SizeAt(class_id) > 0); | 
|  | #endif  // DEBUG | 
|  | instance_size = isolate_group->heap_walk_class_table()->SizeAt(class_id); | 
|  | } | 
|  | } | 
|  | ASSERT(instance_size != 0); | 
|  | #if defined(DEBUG) | 
|  | intptr_t tags_size = SizeTag::decode(tags); | 
|  | if ((class_id == kArrayCid) && (instance_size > tags_size && tags_size > 0)) { | 
|  | // TODO(22501): Array::MakeFixedLength could be in the process of shrinking | 
|  | // the array (see comment therein), having already updated the tags but not | 
|  | // yet set the new length. Wait a millisecond and try again. | 
|  | int retries_remaining = 1000;  // ... but not forever. | 
|  | do { | 
|  | OS::Sleep(1); | 
|  | const ArrayPtr raw_array = static_cast<const ArrayPtr>(this); | 
|  | intptr_t array_length = Smi::Value(raw_array->untag()->length()); | 
|  | instance_size = Array::InstanceSize(array_length); | 
|  | } while ((instance_size > tags_size) && (--retries_remaining > 0)); | 
|  | } | 
|  | if ((instance_size != tags_size) && (tags_size != 0)) { | 
|  | FATAL("Size mismatch: %" Pd " from class vs %" Pd " from tags %" Px "\n", | 
|  | instance_size, tags_size, tags); | 
|  | } | 
|  | #endif  // DEBUG | 
|  | return instance_size; | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedObject::VisitPointersPredefined(ObjectPointerVisitor* visitor, | 
|  | intptr_t class_id) { | 
|  | ASSERT(class_id < kNumPredefinedCids); | 
|  |  | 
|  | intptr_t size = 0; | 
|  |  | 
|  | switch (class_id) { | 
|  | #define RAW_VISITPOINTERS(clazz)                                               \ | 
|  | case k##clazz##Cid: {                                                        \ | 
|  | clazz##Ptr raw_obj = static_cast<clazz##Ptr>(this);                        \ | 
|  | size = Untagged##clazz::Visit##clazz##Pointers(raw_obj, visitor);          \ | 
|  | break;                                                                     \ | 
|  | } | 
|  | CLASS_LIST_NO_OBJECT(RAW_VISITPOINTERS) | 
|  | #undef RAW_VISITPOINTERS | 
|  | #define RAW_VISITPOINTERS(clazz) case kTypedData##clazz##Cid: | 
|  | CLASS_LIST_TYPED_DATA(RAW_VISITPOINTERS) { | 
|  | TypedDataPtr raw_obj = static_cast<TypedDataPtr>(this); | 
|  | size = UntaggedTypedData::VisitTypedDataPointers(raw_obj, visitor); | 
|  | break; | 
|  | } | 
|  | #undef RAW_VISITPOINTERS | 
|  | #define RAW_VISITPOINTERS(clazz) case kExternalTypedData##clazz##Cid: | 
|  | CLASS_LIST_TYPED_DATA(RAW_VISITPOINTERS) { | 
|  | auto raw_obj = static_cast<ExternalTypedDataPtr>(this); | 
|  | size = UntaggedExternalTypedData::VisitExternalTypedDataPointers(raw_obj, | 
|  | visitor); | 
|  | break; | 
|  | } | 
|  | #undef RAW_VISITPOINTERS | 
|  | case kByteDataViewCid: | 
|  | case kUnmodifiableByteDataViewCid: | 
|  | #define RAW_VISITPOINTERS(clazz)                                               \ | 
|  | case kTypedData##clazz##ViewCid:                                             \ | 
|  | case kUnmodifiableTypedData##clazz##ViewCid: | 
|  | CLASS_LIST_TYPED_DATA(RAW_VISITPOINTERS) { | 
|  | auto raw_obj = static_cast<TypedDataViewPtr>(this); | 
|  | size = | 
|  | UntaggedTypedDataView::VisitTypedDataViewPointers(raw_obj, visitor); | 
|  | break; | 
|  | } | 
|  | #undef RAW_VISITPOINTERS | 
|  | case kByteBufferCid: { | 
|  | InstancePtr raw_obj = static_cast<InstancePtr>(this); | 
|  | size = UntaggedInstance::VisitInstancePointers(raw_obj, visitor); | 
|  | break; | 
|  | } | 
|  | #define RAW_VISITPOINTERS(clazz) case kFfi##clazz##Cid: | 
|  | CLASS_LIST_FFI_TYPE_MARKER(RAW_VISITPOINTERS) { | 
|  | // NativeType do not have any fields or type arguments. | 
|  | size = HeapSize(); | 
|  | break; | 
|  | } | 
|  | #undef RAW_VISITPOINTERS | 
|  | case kFreeListElement: { | 
|  | uword addr = UntaggedObject::ToAddr(this); | 
|  | FreeListElement* element = reinterpret_cast<FreeListElement*>(addr); | 
|  | size = element->HeapSize(); | 
|  | break; | 
|  | } | 
|  | case kForwardingCorpse: { | 
|  | uword addr = UntaggedObject::ToAddr(this); | 
|  | ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr); | 
|  | size = forwarder->HeapSize(); | 
|  | break; | 
|  | } | 
|  | case kNullCid: | 
|  | case kNeverCid: | 
|  | size = HeapSize(); | 
|  | break; | 
|  | default: | 
|  | FATAL("Invalid cid: %" Pd ", obj: %p, tags: %x. Corrupt heap?", class_id, | 
|  | this, static_cast<uint32_t>(tags_)); | 
|  | break; | 
|  | } | 
|  |  | 
|  | #if defined(DEBUG) | 
|  | ASSERT(size != 0); | 
|  | const intptr_t expected_size = HeapSize(); | 
|  |  | 
|  | // In general we expect that visitors return exactly the same size that | 
|  | // HeapSize would compute. However in case of Arrays we might have a | 
|  | // discrepancy when concurrently visiting an array that is being shrunk with | 
|  | // Array::MakeFixedLength: the visitor might have visited the full array while | 
|  | // here we are observing a smaller HeapSize(). | 
|  | ASSERT(size == expected_size || | 
|  | (class_id == kArrayCid && size > expected_size)); | 
|  | return size;  // Prefer larger size. | 
|  | #else | 
|  | return size; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void UntaggedObject::VisitPointersPrecise(ObjectPointerVisitor* visitor) { | 
|  | intptr_t class_id = GetClassId(); | 
|  | if ((class_id != kInstanceCid) && (class_id < kNumPredefinedCids)) { | 
|  | VisitPointersPredefined(visitor, class_id); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // N.B.: Not using the heap size! | 
|  | uword next_field_offset = visitor->class_table() | 
|  | ->At(class_id) | 
|  | ->untag() | 
|  | ->host_next_field_offset_in_words_ | 
|  | << kCompressedWordSizeLog2; | 
|  | ASSERT(next_field_offset > 0); | 
|  | uword obj_addr = UntaggedObject::ToAddr(this); | 
|  | uword from = obj_addr + sizeof(UntaggedObject); | 
|  | uword to = obj_addr + next_field_offset - kCompressedWordSize; | 
|  | const auto first = reinterpret_cast<CompressedObjectPtr*>(from); | 
|  | const auto last = reinterpret_cast<CompressedObjectPtr*>(to); | 
|  |  | 
|  | const auto unboxed_fields_bitmap = | 
|  | visitor->class_table()->GetUnboxedFieldsMapAt(class_id); | 
|  |  | 
|  | if (!unboxed_fields_bitmap.IsEmpty()) { | 
|  | intptr_t bit = sizeof(UntaggedObject) / kCompressedWordSize; | 
|  | for (CompressedObjectPtr* current = first; current <= last; current++) { | 
|  | if (!unboxed_fields_bitmap.Get(bit++)) { | 
|  | visitor->VisitCompressedPointers(heap_base(), current, current); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | visitor->VisitCompressedPointers(heap_base(), first, last); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Most objects are visited with this function. It calls the from() and to() | 
|  | // methods on the raw object to get the first and last cells that need | 
|  | // visiting. | 
|  | #define REGULAR_VISITOR(Type)                                                  \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | ASSERT_UNCOMPRESSED(Type);                                                 \ | 
|  | visitor->VisitPointers(raw_obj->untag()->from(), raw_obj->untag()->to());  \ | 
|  | return Type::InstanceSize();                                               \ | 
|  | } | 
|  |  | 
|  | #if !defined(DART_COMPRESSED_POINTERS) | 
|  | #define COMPRESSED_VISITOR(Type) REGULAR_VISITOR(Type) | 
|  | #else | 
|  | #define COMPRESSED_VISITOR(Type)                                               \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | ASSERT_COMPRESSED(Type);                                                   \ | 
|  | visitor->VisitCompressedPointers(raw_obj->heap_base(),                     \ | 
|  | raw_obj->untag()->from(),                 \ | 
|  | raw_obj->untag()->to());                  \ | 
|  | return Type::InstanceSize();                                               \ | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // It calls the from() and to() methods on the raw object to get the first and | 
|  | // last cells that need visiting. | 
|  | // | 
|  | // Though as opposed to Similar to [REGULAR_VISITOR] this visitor will call the | 
|  | // specialized VisitTypedDataViewPointers | 
|  | #define TYPED_DATA_VIEW_VISITOR(Type)                                          \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | ASSERT_COMPRESSED(Type);                                                   \ | 
|  | visitor->VisitTypedDataViewPointers(raw_obj, raw_obj->untag()->from(),     \ | 
|  | raw_obj->untag()->to());               \ | 
|  | return Type::InstanceSize();                                               \ | 
|  | } | 
|  |  | 
|  | // For variable length objects. get_length is a code snippet that gets the | 
|  | // length of the object, which is passed to InstanceSize and the to() method. | 
|  | #define VARIABLE_VISITOR(Type, get_length)                                     \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | intptr_t length = get_length;                                              \ | 
|  | visitor->VisitPointers(raw_obj->untag()->from(),                           \ | 
|  | raw_obj->untag()->to(length));                      \ | 
|  | return Type::InstanceSize(length);                                         \ | 
|  | } | 
|  |  | 
|  | #if !defined(DART_COMPRESSED_POINTERS) | 
|  | #define VARIABLE_COMPRESSED_VISITOR(Type, get_length)                          \ | 
|  | VARIABLE_VISITOR(Type, get_length) | 
|  | #else | 
|  | #define VARIABLE_COMPRESSED_VISITOR(Type, get_length)                          \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | intptr_t length = get_length;                                              \ | 
|  | visitor->VisitCompressedPointers(raw_obj->heap_base(),                     \ | 
|  | raw_obj->untag()->from(),                 \ | 
|  | raw_obj->untag()->to(length));            \ | 
|  | return Type::InstanceSize(length);                                         \ | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // For fixed-length objects that don't have any pointers that need visiting. | 
|  | #define NULL_VISITOR(Type)                                                     \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | ASSERT_NOTHING_TO_VISIT(Type);                                             \ | 
|  | return Type::InstanceSize();                                               \ | 
|  | } | 
|  |  | 
|  | // For objects that don't have any pointers that need visiting, but have a | 
|  | // variable length. | 
|  | #define VARIABLE_NULL_VISITOR(Type, get_length)                                \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | /* Make sure that we got here with the tagged pointer as this. */          \ | 
|  | ASSERT(raw_obj->IsHeapObject());                                           \ | 
|  | ASSERT_NOTHING_TO_VISIT(Type);                                             \ | 
|  | intptr_t length = get_length;                                              \ | 
|  | return Type::InstanceSize(length);                                         \ | 
|  | } | 
|  |  | 
|  | // For objects that are never instantiated on the heap. | 
|  | #define UNREACHABLE_VISITOR(Type)                                              \ | 
|  | intptr_t Untagged##Type::Visit##Type##Pointers(                              \ | 
|  | Type##Ptr raw_obj, ObjectPointerVisitor* visitor) {                      \ | 
|  | UNREACHABLE();                                                             \ | 
|  | return 0;                                                                  \ | 
|  | } | 
|  |  | 
|  | COMPRESSED_VISITOR(Class) | 
|  | COMPRESSED_VISITOR(PatchClass) | 
|  | COMPRESSED_VISITOR(ClosureData) | 
|  | COMPRESSED_VISITOR(FfiTrampolineData) | 
|  | COMPRESSED_VISITOR(Script) | 
|  | COMPRESSED_VISITOR(Library) | 
|  | COMPRESSED_VISITOR(Namespace) | 
|  | COMPRESSED_VISITOR(KernelProgramInfo) | 
|  | COMPRESSED_VISITOR(WeakSerializationReference) | 
|  | VARIABLE_COMPRESSED_VISITOR(WeakArray, Smi::Value(raw_obj->untag()->length())) | 
|  | COMPRESSED_VISITOR(Type) | 
|  | COMPRESSED_VISITOR(FunctionType) | 
|  | COMPRESSED_VISITOR(RecordType) | 
|  | COMPRESSED_VISITOR(TypeParameter) | 
|  | COMPRESSED_VISITOR(Function) | 
|  | COMPRESSED_VISITOR(Closure) | 
|  | COMPRESSED_VISITOR(LibraryPrefix) | 
|  | COMPRESSED_VISITOR(Bytecode) | 
|  | REGULAR_VISITOR(SingleTargetCache) | 
|  | REGULAR_VISITOR(UnlinkedCall) | 
|  | NULL_VISITOR(MonomorphicSmiableCall) | 
|  | REGULAR_VISITOR(ICData) | 
|  | REGULAR_VISITOR(MegamorphicCache) | 
|  | COMPRESSED_VISITOR(ApiError) | 
|  | COMPRESSED_VISITOR(LanguageError) | 
|  | COMPRESSED_VISITOR(UnhandledException) | 
|  | COMPRESSED_VISITOR(UnwindError) | 
|  | COMPRESSED_VISITOR(GrowableObjectArray) | 
|  | COMPRESSED_VISITOR(Map) | 
|  | COMPRESSED_VISITOR(Set) | 
|  | COMPRESSED_VISITOR(ExternalTypedData) | 
|  | TYPED_DATA_VIEW_VISITOR(TypedDataView) | 
|  | COMPRESSED_VISITOR(ReceivePort) | 
|  | COMPRESSED_VISITOR(StackTrace) | 
|  | COMPRESSED_VISITOR(RegExp) | 
|  | COMPRESSED_VISITOR(WeakProperty) | 
|  | COMPRESSED_VISITOR(WeakReference) | 
|  | COMPRESSED_VISITOR(Finalizer) | 
|  | COMPRESSED_VISITOR(FinalizerEntry) | 
|  | COMPRESSED_VISITOR(NativeFinalizer) | 
|  | COMPRESSED_VISITOR(MirrorReference) | 
|  | COMPRESSED_VISITOR(UserTag) | 
|  | REGULAR_VISITOR(SubtypeTestCache) | 
|  | COMPRESSED_VISITOR(LoadingUnit) | 
|  | COMPRESSED_VISITOR(TypeParameters) | 
|  | VARIABLE_COMPRESSED_VISITOR(TypeArguments, | 
|  | Smi::Value(raw_obj->untag()->length())) | 
|  | VARIABLE_COMPRESSED_VISITOR(LocalVarDescriptors, raw_obj->untag()->num_entries_) | 
|  | VARIABLE_COMPRESSED_VISITOR(ExceptionHandlers, raw_obj->untag()->num_entries()) | 
|  | VARIABLE_COMPRESSED_VISITOR(Context, raw_obj->untag()->num_variables_) | 
|  | VARIABLE_COMPRESSED_VISITOR(Array, Smi::Value(raw_obj->untag()->length())) | 
|  | VARIABLE_COMPRESSED_VISITOR( | 
|  | TypedData, | 
|  | TypedData::ElementSizeInBytes(raw_obj->GetClassIdOfHeapObject()) * | 
|  | Smi::Value(raw_obj->untag()->length())) | 
|  | VARIABLE_COMPRESSED_VISITOR(ContextScope, raw_obj->untag()->num_variables_) | 
|  | VARIABLE_COMPRESSED_VISITOR(Record, | 
|  | RecordShape(raw_obj->untag()->shape()).num_fields()) | 
|  | NULL_VISITOR(Sentinel) | 
|  | REGULAR_VISITOR(InstructionsTable) | 
|  | NULL_VISITOR(Mint) | 
|  | NULL_VISITOR(Double) | 
|  | NULL_VISITOR(Float32x4) | 
|  | NULL_VISITOR(Int32x4) | 
|  | NULL_VISITOR(Float64x2) | 
|  | NULL_VISITOR(Bool) | 
|  | NULL_VISITOR(Capability) | 
|  | NULL_VISITOR(SendPort) | 
|  | NULL_VISITOR(TransferableTypedData) | 
|  | COMPRESSED_VISITOR(Pointer) | 
|  | NULL_VISITOR(DynamicLibrary) | 
|  | VARIABLE_NULL_VISITOR(Instructions, Instructions::Size(raw_obj)) | 
|  | VARIABLE_NULL_VISITOR(InstructionsSection, InstructionsSection::Size(raw_obj)) | 
|  | VARIABLE_NULL_VISITOR(PcDescriptors, raw_obj->untag()->length_) | 
|  | VARIABLE_NULL_VISITOR(CodeSourceMap, raw_obj->untag()->length_) | 
|  | VARIABLE_NULL_VISITOR(CompressedStackMaps, | 
|  | CompressedStackMaps::PayloadSizeOf(raw_obj)) | 
|  | VARIABLE_NULL_VISITOR(OneByteString, Smi::Value(raw_obj->untag()->length())) | 
|  | VARIABLE_NULL_VISITOR(TwoByteString, Smi::Value(raw_obj->untag()->length())) | 
|  | // Abstract types don't have their visitor called. | 
|  | UNREACHABLE_VISITOR(AbstractType) | 
|  | UNREACHABLE_VISITOR(CallSiteData) | 
|  | UNREACHABLE_VISITOR(TypedDataBase) | 
|  | UNREACHABLE_VISITOR(Error) | 
|  | UNREACHABLE_VISITOR(FinalizerBase) | 
|  | UNREACHABLE_VISITOR(Number) | 
|  | UNREACHABLE_VISITOR(Integer) | 
|  | UNREACHABLE_VISITOR(String) | 
|  | UNREACHABLE_VISITOR(FutureOr) | 
|  | // Smi has no heap representation. | 
|  | UNREACHABLE_VISITOR(Smi) | 
|  |  | 
|  | intptr_t UntaggedField::VisitFieldPointers(FieldPtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | ASSERT(raw_obj->IsHeapObject()); | 
|  | ASSERT_COMPRESSED(Field); | 
|  | visitor->VisitCompressedPointers( | 
|  | raw_obj->heap_base(), raw_obj->untag()->from(), raw_obj->untag()->to()); | 
|  |  | 
|  | if (visitor->trace_values_through_fields()) { | 
|  | if (Field::StaticBit::decode(raw_obj->untag()->kind_bits_)) { | 
|  | visitor->isolate_group()->ForEachIsolate( | 
|  | [&](Isolate* isolate) { | 
|  | intptr_t index = | 
|  | Smi::Value(raw_obj->untag()->host_offset_or_field_id()); | 
|  | visitor->VisitPointer(&isolate->field_table()->table()[index]); | 
|  | }, | 
|  | /*at_safepoint=*/true); | 
|  | } | 
|  | } | 
|  | return Field::InstanceSize(); | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedSuspendState::VisitSuspendStatePointers( | 
|  | SuspendStatePtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | ASSERT(raw_obj->IsHeapObject()); | 
|  | ASSERT_COMPRESSED(SuspendState); | 
|  |  | 
|  | if (visitor->CanVisitSuspendStatePointers(raw_obj)) { | 
|  | visitor->VisitCompressedPointers( | 
|  | raw_obj->heap_base(), raw_obj->untag()->from(), raw_obj->untag()->to()); | 
|  |  | 
|  | const uword pc = raw_obj->untag()->pc_; | 
|  | if (pc != 0) { | 
|  | Thread* thread = Thread::Current(); | 
|  | ASSERT(thread != nullptr); | 
|  | ASSERT(thread->isolate_group() == visitor->isolate_group()); | 
|  | const uword sp = reinterpret_cast<uword>(raw_obj->untag()->payload()); | 
|  | StackFrame frame(thread); | 
|  | frame.pc_ = pc; | 
|  | frame.sp_ = sp; | 
|  | frame.fp_ = sp + raw_obj->untag()->frame_size_; | 
|  | frame.VisitObjectPointers(visitor); | 
|  | } | 
|  | } | 
|  |  | 
|  | return SuspendState::InstanceSize(raw_obj->untag()->frame_capacity()); | 
|  | } | 
|  |  | 
|  | bool UntaggedCode::ContainsPC(const ObjectPtr raw_obj, uword pc) { | 
|  | if (!raw_obj->IsCode()) return false; | 
|  | auto const raw_code = static_cast<const CodePtr>(raw_obj); | 
|  | const uword start = Code::PayloadStartOf(raw_code); | 
|  | const uword size = Code::PayloadSizeOf(raw_code); | 
|  | return (pc - start) <= size;  // pc may point just past last instruction. | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedCode::VisitCodePointers(CodePtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | visitor->VisitPointers(raw_obj->untag()->from(), raw_obj->untag()->to()); | 
|  |  | 
|  | UntaggedCode* obj = raw_obj->untag(); | 
|  | intptr_t length = Code::PtrOffBits::decode(obj->state_bits_); | 
|  | #if defined(TARGET_ARCH_IA32) | 
|  | // On IA32 only we embed pointers to objects directly in the generated | 
|  | // instructions. The variable portion of a Code object describes where to | 
|  | // find those pointers for tracing. | 
|  | if (Code::AliveBit::decode(obj->state_bits_)) { | 
|  | uword entry_point = Code::PayloadStartOf(raw_obj); | 
|  | for (intptr_t i = 0; i < length; i++) { | 
|  | int32_t offset = obj->data()[i]; | 
|  | visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(entry_point + offset)); | 
|  | } | 
|  | } | 
|  | return Code::InstanceSize(length); | 
|  | #else | 
|  | // On all other architectures, objects are referenced indirectly through | 
|  | // either an ObjectPool or Thread. | 
|  | ASSERT(length == 0); | 
|  | return Code::InstanceSize(0); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool UntaggedBytecode::ContainsPC(ObjectPtr raw_obj, uword pc) { | 
|  | if (raw_obj->IsBytecode()) { | 
|  | BytecodePtr raw_bytecode = static_cast<BytecodePtr>(raw_obj); | 
|  | uword start = raw_bytecode->untag()->instructions_; | 
|  | uword size = raw_bytecode->untag()->instructions_size_; | 
|  | return (pc - start) <= size;  // pc may point past last instruction. | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedObjectPool::VisitObjectPoolPointers( | 
|  | ObjectPoolPtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | const intptr_t length = raw_obj->untag()->length_; | 
|  | UntaggedObjectPool::Entry* entries = raw_obj->untag()->data(); | 
|  | uint8_t* entry_bits = raw_obj->untag()->entry_bits(); | 
|  | for (intptr_t i = 0; i < length; ++i) { | 
|  | ObjectPool::EntryType entry_type = | 
|  | ObjectPool::TypeBits::decode(entry_bits[i]); | 
|  | if (entry_type == ObjectPool::EntryType::kTaggedObject) { | 
|  | visitor->VisitPointer(&entries[i].raw_obj_); | 
|  | } | 
|  | } | 
|  | return ObjectPool::InstanceSize(length); | 
|  | } | 
|  |  | 
|  | bool UntaggedInstructions::ContainsPC(const InstructionsPtr raw_instr, | 
|  | uword pc) { | 
|  | const uword start = Instructions::PayloadStart(raw_instr); | 
|  | const uword size = Instructions::Size(raw_instr); | 
|  | // We use <= instead of < here because the saved-pc can be outside the | 
|  | // instruction stream if the last instruction is a call we don't expect to | 
|  | // return (e.g. because it throws an exception). | 
|  | return (pc - start) <= size; | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedInstance::VisitInstancePointers( | 
|  | InstancePtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | // Make sure that we got here with the tagged pointer as this. | 
|  | ASSERT(raw_obj->IsHeapObject()); | 
|  | uword tags = raw_obj->untag()->tags_; | 
|  | intptr_t instance_size = SizeTag::decode(tags); | 
|  | if (instance_size == 0) { | 
|  | instance_size = | 
|  | visitor->class_table()->SizeAt(raw_obj->GetClassIdOfHeapObject()); | 
|  | } | 
|  |  | 
|  | // Calculate the first and last raw object pointer fields. | 
|  | uword obj_addr = UntaggedObject::ToAddr(raw_obj); | 
|  | uword from = obj_addr + sizeof(UntaggedObject); | 
|  | uword to = obj_addr + instance_size - kCompressedWordSize; | 
|  | visitor->VisitCompressedPointers(raw_obj->heap_base(), | 
|  | reinterpret_cast<CompressedObjectPtr*>(from), | 
|  | reinterpret_cast<CompressedObjectPtr*>(to)); | 
|  | return instance_size; | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedImmutableArray::VisitImmutableArrayPointers( | 
|  | ImmutableArrayPtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | return UntaggedArray::VisitArrayPointers(raw_obj, visitor); | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedConstMap::VisitConstMapPointers( | 
|  | ConstMapPtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | return UntaggedMap::VisitMapPointers(raw_obj, visitor); | 
|  | } | 
|  |  | 
|  | intptr_t UntaggedConstSet::VisitConstSetPointers( | 
|  | ConstSetPtr raw_obj, | 
|  | ObjectPointerVisitor* visitor) { | 
|  | return UntaggedSet::VisitSetPointers(raw_obj, visitor); | 
|  | } | 
|  |  | 
|  | void UntaggedObject::RememberCard(ObjectPtr const* slot) { | 
|  | Page::Of(static_cast<ObjectPtr>(this))->RememberCard(slot); | 
|  | } | 
|  |  | 
|  | #if defined(DART_COMPRESSED_POINTERS) | 
|  | void UntaggedObject::RememberCard(CompressedObjectPtr const* slot) { | 
|  | Page::Of(static_cast<ObjectPtr>(this))->RememberCard(slot); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | const char* UntaggedPcDescriptors::KindToCString(Kind k) { | 
|  | switch (k) { | 
|  | #define ENUM_CASE(name, init)                                                  \ | 
|  | case Kind::k##name:                                                          \ | 
|  | return #name; | 
|  | FOR_EACH_RAW_PC_DESCRIPTOR(ENUM_CASE) | 
|  | #undef ENUM_CASE | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool UntaggedPcDescriptors::ParseKind(const char* cstr, Kind* out) { | 
|  | ASSERT(cstr != nullptr && out != nullptr); | 
|  | #define ENUM_CASE(name, init)                                                  \ | 
|  | if (strcmp(#name, cstr) == 0) {                                              \ | 
|  | *out = Kind::k##name;                                                      \ | 
|  | return true;                                                               \ | 
|  | } | 
|  | FOR_EACH_RAW_PC_DESCRIPTOR(ENUM_CASE) | 
|  | #undef ENUM_CASE | 
|  | return false; | 
|  | } | 
|  | #undef PREFIXED_NAME | 
|  |  | 
|  | }  // namespace dart |