Давайте поймем Chrome V8 — Глава 14: Что в V8 представляет собой JS Dynamically Type?

Первоисточник: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-14-what-in-v8-is-the-js-dynamically-type-c68bb09caa4f
Добро пожаловать в другие главы «Давайте поймем Chrome V8».


JavaScript — динамически типизированный язык, поэтому нам не важен тип данных, достаточно использовать var для определения нужной нам переменной. Но в C++ для простой программы hello world вам придется разобраться и прочитать много о целых числах, longs, chars и так далее. Это C++, который является статически типизированным языком. Итак, как статический язык, такой как C++, может работать с динамически типизированным JavaScript? Об этом я и хотел бы поговорить в этой статье.

1. Разметка карты

В C++ объект JavaScript — это просто кусок памяти без типа. C++ не знает, сколько и какие значения находятся в этой памяти. В философии JavaScript эти значения могут меняться динамически, так как же C++ может знать последний тип, чтобы поддерживать объект JavaScript?


Посмотрите на рисунок 1, давайте поговорим о классе map, используемом в V8, а именно о классе hidden.

  • С точки зрения разработчика JavaScript, вы можете видеть только синюю память, которая является вашим объектом JavaScript;

  • В V8 я могу видеть всю память, зеленую память (карта) и синюю память (объекты JavaScript).

Размер карты составляет 80 байт, и она используется для защиты типа объекта JavaScript, который является макетом синей памяти — какие данные хранятся, строки, массивы или другие? Как прочитать эти данные? А именно, метод адресации. Примечание: Map сохраняет только метод адресации, а не данные в объекте JavaScript.

Перед сохранением объекта JavaScript, V8 сначала извлекает метод адресации из карты, после чего может правильно читать и записывать синюю память. V8 своевременно обновляет последний метод адресации в карте объекта JavasCript при изменении его типа. Таким образом, разработчики JavaScript могут свободно изменять тип объекта.

Подводя итог, можно сказать, что в C++ тип данных остается статичным, и самое главное — вовремя обновлять карту при изменении типа. Это истина, которую вы хотели бы знать. Технология, лежащая в основе динамических функций, заложена в C++.

Описание карты приведено ниже.

1.  Map layout:
2.  // +---------------+---------------------------------------------+
3.  // |   _ Type _    | _ Description _                             |
4.  // +---------------+---------------------------------------------+
5.  // | TaggedPointer | map - Always a pointer to the MetaMap root  |
6.  // +---------------+---------------------------------------------+
7.  // | Int           | The first int field                         |
8.  //  `---+----------+---------------------------------------------+
9.  //      | Byte     | [instance_size]                             |
10.  //      +----------+---------------------------------------------+
11.  //      | Byte     | If Map for a primitive type:                |
12.  //      |          |   native context index for constructor fn   |
13.  //      |          | If Map for an Object type:                  |
14.  //      |          |   inobject properties start offset in words |
15.  //      +----------+---------------------------------------------+
16.  //      | Byte     | [used_or_unused_instance_size_in_words]     |
17.  //      |          | For JSObject in fast mode this byte encodes |
18.  //      |          | the size of the object that includes only   |
19.  //      |          | the used property fields or the slack size  |
20.  //      |          | in properties backing store.                |
21.  //      +----------+---------------------------------------------+
22.  //      | Byte     | [visitor_id]                                |
23.  // +----+----------+---------------------------------------------+
24.  // | Int           | The second int field                        |
25.  //  `---+----------+---------------------------------------------+
26.  //      | Short    | [instance_type]                             |
27.  //      +----------+---------------------------------------------+
28.  //      | Byte     | [bit_field]                                 |
29.  //      |          |   - has_non_instance_prototype (bit 0)      |
30.  //      |          |   - is_callable (bit 1)                     |
31.  //      |          |   - has_named_interceptor (bit 2)           |
32.  //      |          |   - has_indexed_interceptor (bit 3)         |
33.  //      |          |   - is_undetectable (bit 4)                 |
34.  //      |          |   - is_access_check_needed (bit 5)          |
35.  //      |          |   - is_constructor (bit 6)                  |
36.  //      |          |   - has_prototype_slot (bit 7)              |
37.  //      +----------+---------------------------------------------+
38.  //      | Byte     | [bit_field2]                                |
39.  //      |          |   - new_target_is_base (bit 0)              |
40.  //      |          |   - is_immutable_proto (bit 1)              |
41.  //      |          |   - unused bit (bit 2)                      |
42.  //      |          |   - elements_kind (bits 3..7)               |
43.  // +----+----------+---------------------------------------------+
44.  // | Int           | [bit_field3]                                |
45.  // |               |   - enum_length (bit 0..9)                  |
46.  // |               |   - number_of_own_descriptors (bit 10..19)  |
47.  // |               |   - is_prototype_map (bit 20)               |
48.  // |               |   - is_dictionary_map (bit 21)              |
49.  // |               |   - owns_descriptors (bit 22)               |
50.  // |               |   - is_in_retained_map_list (bit 23)        |
51.  // |               |   - is_deprecated (bit 24)                  |
52.  // |               |   - is_unstable (bit 25)                    |
53.  // |               |   - is_migration_target (bit 26)            |
54.  // |               |   - is_extensible (bit 28)                  |
55.  // |               |   - may_have_interesting_symbols (bit 28)   |
56.  // |               |   - construction_counter (bit 29..31)       |
57.  // |               |                                             |
58.  // +*************************************************************+
59.  // | Int           | On systems with 64bit pointer types, there  |
60.  // |               | is an unused 32bits after bit_field3        |
61.  // +*************************************************************+
62.  // | TaggedPointer | [prototype]                                 |
63.  // +---------------+---------------------------------------------+
64.  // | TaggedPointer | [constructor_or_backpointer]                |
65.  // +---------------+---------------------------------------------+
66.  // | TaggedPointer | [instance_descriptors]                      |
67.  // +*************************************************************+
68.  // ! TaggedPointer ! [layout_descriptors]                        !
69.  // !               ! Field is only present if compile-time flag  !
70.  // !               ! FLAG_unbox_double_fields is enabled         !
71.  // !               ! (basically on 64 bit architectures)         !
72.  // +*************************************************************+
73.  // | TaggedPointer | [dependent_code]                            |
74.  // +---------------+---------------------------------------------+
75.  // | TaggedPointer | [prototype_validity_cell]                   |
76.  // +---------------+---------------------------------------------+
77.  // | TaggedPointer | If Map is a prototype map:                  |
78.  // |               |   [prototype_info]                          |
79.  // |               | Else:                                       |
80.  // |               |   [raw_transitions]                         |
81.  // +---------------+---------------------------------------------+
Вход в полноэкранный режим Выход из полноэкранного режима

Размер и расположение карты являются предопределенными константами, которые не меняются. Каждый объект, управляемый кучей, имеет свою карту. Форма обоих объектов одинакова, другими словами, оба объекта имеют одинаковые внутренние члены и позиции членов, что означает, что у них одинаковый метод адресации, поэтому они используют одну и ту же карту.

Смотрите код ниже.

function Point(x,y) {
    this.x = x;
    this.y = y;
}

var fun1 = new Point(1,2);
var fun2 = new Point(3,4);
fun2.z = 80;
Вход в полноэкранный режим Выход из полноэкранного режима

Fun1 и fun2 происходят из одного конструктора, имеют одинаковую форму, поэтому у них одна и та же карта. Но после того, как fun2.z=80, форма fun2 меняется, поэтому V8 назначает для него новую карту.

Карта класса показана ниже.

1.  class Map : public HeapObject {
2.   public:
3.  //.............omit..................
4.     DECL_PRIMITIVE_ACCESSORS(bit_field, byte)
5.     DECL_PRIMITIVE_ACCESSORS(relaxed_bit_field, byte)
6.   // Bit positions for |bit_field|.
7.   #define MAP_BIT_FIELD_FIELDS(V, _)          
8.     V(HasNonInstancePrototypeBit, bool, 1, _) 
9.     V(IsCallableBit, bool, 1, _)              
10.     V(HasNamedInterceptorBit, bool, 1, _)     
11.     V(HasIndexedInterceptorBit, bool, 1, _)   
12.     V(IsUndetectableBit, bool, 1, _)          
13.     V(IsAccessCheckNeededBit, bool, 1, _)     
14.     V(IsConstructorBit, bool, 1, _)           
15.     V(HasPrototypeSlotBit, bool, 1, _)
16.     DEFINE_BIT_FIELDS(MAP_BIT_FIELD_FIELDS)
17.   #undef MAP_BIT_FIELD_FIELDS
18.     // Bit field 2.
19.     DECL_PRIMITIVE_ACCESSORS(bit_field2, byte)
20.   // Bit positions for |bit_field2|.
21.   #define MAP_BIT_FIELD2_FIELDS(V, _)      
22.     V(NewTargetIsBaseBit, bool, 1, _)      
23.     V(IsImmutablePrototypeBit, bool, 1, _) 
24.     V(UnusedBit, bool, 1, _)               
25.     V(ElementsKindBits, ElementsKind, 5, _)
26.     DEFINE_BIT_FIELDS(MAP_BIT_FIELD2_FIELDS)
27.   #undef MAP_BIT_FIELD2_FIELDS
28.     DECL_PRIMITIVE_ACCESSORS(bit_field3, uint32_t)
29.     V8_INLINE void clear_padding();
30.      DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,
31.                                    TORQUE_GENERATED_MAP_FIELDS)
32.      //.............omit..................
33.      OBJECT_CONSTRUCTORS(Map, HeapObject);
34.  };
Вход в полноэкранный режим Выход из полноэкранного режима

Ниже приведено расширение макроса DEFINE_FIELD_OFFSET_CONSTANTS.

1.    enum {
2.  TORQUE_GENERATED_MAP_FIELDS_StartOffset= 7,
3.  kInstanceSizeInWordsOffset=8, kInstanceSizeInWordsOffsetEnd = 8,
4.  kInObjectPropertiesStartOrConstructorFunctionIndexOffset=9, kInObjectPropertiesStartOrConstructorFunctionIndexOffsetEnd = 9,
5.  kUsedOrUnusedInstanceSizeInWordsOffset=10, kUsedOrUnusedInstanceSizeInWordsOffsetEnd = 10,
6.  kVisitorIdOffset=11, kVisitorIdOffsetEnd = 11,
7.  kInstanceTypeOffset=12, kInstanceTypeOffsetEnd = 13,
8.  kBitFieldOffset=14, kBitFieldOffsetEnd = 14,
9.  kBitField2Offset=15, kBitField2OffsetEnd = 15,
10.  kBitField3Offset=16, kBitField3OffsetEnd = 19,
11.  kOptionalPaddingOffset=20, kOptionalPaddingOffsetEnd = 23,
12.  kStartOfStrongFieldsOffset=24, kStartOfStrongFieldsOffsetEnd = 23,
13.  kPrototypeOffset=24, kPrototypeOffsetEnd = 31,
14.  kConstructorOrBackPointerOffset=32, kConstructorOrBackPointerOffsetEnd = 39,
15.  kInstanceDescriptorsOffset=40, kInstanceDescriptorsOffsetEnd = 47,
16.  kLayoutDescriptorOffset=48, kLayoutDescriptorOffsetEnd = 55,
17.  kDependentCodeOffset=56, kDependentCodeOffsetEnd = 63,
18.  kPrototypeValidityCellOffset=64, kPrototypeValidityCellOffsetEnd = 71,
19.  kEndOfStrongFieldsOffset=72, kEndOfStrongFieldsOffsetEnd = 71,
20.  kStartOfWeakFieldsOffset=72, kStartOfWeakFieldsOffsetEnd = 71,
21.  kTransitionsOrPrototypeInfoOffset=72, kTransitionsOrPrototypeInfoOffsetEnd = 79,
22.  kEndOfWeakFieldsOffset=80, kEndOfWeakFieldsOffsetEnd = 79,
23.  kSize=80, kSizeEnd = 79,
24.    }
Вход в полноэкранный режим Выход из полноэкранного режима

Перечисление здесь соответствует описанию карты, упомянутому выше. Обратите внимание, что в новой версии V8 в будущем карта может измениться, и в этом перечислении могут появиться новые переменные-члены.

Карта — это объект V8, управляемый кучей. Когда создается новая карта, V8 использует следующую функцию для выделения памяти из кучи V8.

1.  AllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationType type,
2.                                     AllocationOrigin origin,
3.                                     AllocationAlignment alignment) {
4.  //.....omit.......
5.    if (AllocationType::kYoung == type) {
6.  //.....omit.......
7.    } else if (AllocationType::kOld == type) {
8.  //.....omit.......
9.    } else if (AllocationType::kCode == type) {
10.      if (size_in_bytes <= code_space()->AreaSize() && !large_object) {
11.        allocation = code_space_->AllocateRawUnaligned(size_in_bytes);
12.      } else {
13.        allocation = code_lo_space_->AllocateRaw(size_in_bytes);
14.      }
15.    } else if (AllocationType::kMap == type) {
16.      allocation = map_space_->AllocateRawUnaligned(size_in_bytes);
17.    } else if (AllocationType::kReadOnly == type) {
18.  #ifdef V8_USE_SNAPSHOT
19.      DCHECK(isolate_->serializer_enabled());
20.  #endif
21.      DCHECK(!large_object);
22.      DCHECK(CanAllocateInReadOnlySpace());
23.      DCHECK_EQ(AllocationOrigin::kRuntime, origin);
24.      allocation =
25.          read_only_space_->AllocateRaw(size_in_bytes, alignment, origin);
26.    } else {
27.      UNREACHABLE();
28.    }
29.    return allocation;
30.  }
Вход в полноэкранный режим Выход из полноэкранного режима

Когда строка 15 истинна, тип — kMap, а размер_в_байтах — 80, что означает, что выделяется память для новой карты. Рисунок 2 — стек вызовов.

2. Инициализация карты

Во время запуска V8 нижеприведенный CreateInitialMaps инициализирует пустые карты для всех типов JavaScript. Пустая карта описывает минимальные требования к объекту JavaScript. Когда вы создаете новый объект JavaScript, V8 заполняет некоторую необходимую информацию (тип объекта, размер …) в соответствующую пустую карту.

1.  bool Heap::CreateInitialMaps() {
2.    HeapObject obj;
3.    {
4.      AllocationResult allocation = AllocatePartialMap(MAP_TYPE, Map::kSize);
5.      if (!allocation.To(&obj)) return false;
6.    }
7.    Map new_meta_map = Map::unchecked_cast(obj);
8.    set_meta_map(new_meta_map);
9.    new_meta_map.set_map_after_allocation(new_meta_map);
10.  //...................omit...................
11.    ReadOnlyRoots roots(this);
12.    {  // Partial map allocation
13.  #define ALLOCATE_PARTIAL_MAP(instance_type, size, field_name)                
14.    {                                                                          
15.      Map map;                                                                 
16.      if (!AllocatePartialMap((instance_type), (size)).To(&map)) return false; 
17.      set_##field_name##_map(map);                                             
18.    }
19.      ALLOCATE_PARTIAL_MAP(FIXED_ARRAY_TYPE, kVariableSizeSentinel, fixed_array);
20.      ALLOCATE_PARTIAL_MAP(WEAK_FIXED_ARRAY_TYPE, kVariableSizeSentinel,
21.                           weak_fixed_array);
22.      ALLOCATE_PARTIAL_MAP(WEAK_ARRAY_LIST_TYPE, kVariableSizeSentinel,
23.  //..................omit...................
24.  #undef ALLOCATE_PARTIAL_MAP
25.    }
26.    // Allocate the empty array.
27.    {
28.      AllocationResult alloc =
29.          AllocateRaw(FixedArray::SizeFor(0), AllocationType::kReadOnly);
30.      if (!alloc.To(&obj)) return false;
31.      obj.set_map_after_allocation(roots.fixed_array_map(), SKIP_WRITE_BARRIER);
32.      FixedArray::cast(obj).set_length(0);
33.    }
34.    set_empty_fixed_array(FixedArray::cast(obj));
35.  //...................omit....................
36.    FinalizePartialMap(roots.meta_map());
37.    FinalizePartialMap(roots.fixed_array_map());
38.    FinalizePartialMap(roots.weak_fixed_array_map());
39.    {
40.      if (!AllocateRaw(FixedArray::SizeFor(0), AllocationType::kReadOnly)
41.               .To(&obj)) {
42.        return false;
43.      }
44.      obj.set_map_after_allocation(roots.closure_feedback_cell_array_map(),
45.                                   SKIP_WRITE_BARRIER);
46.      FixedArray::cast(obj).set_length(0);
47.      set_empty_closure_feedback_cell_array(ClosureFeedbackCellArray::cast(obj));
48.    }
49.    DCHECK(!InYoungGeneration(roots.empty_fixed_array()));
50.    roots.bigint_map().SetConstructorFunctionIndex(
51.        Context::BIGINT_FUNCTION_INDEX);
52.    return true;
53.  }
Вход в полноэкранный режим Выход из полноэкранного режима

В строках 4, 5, 6, 7 и 8 создаются метаданные для всех карт. В строках с 13 по 22 с помощью макроса ALLOCATE_PARTIAL_MAP создаются пустые карты для ARRAY_LIST и ARRAY. В строках с 27 по 34 создайте пустые карты для других типов. Создание карт происходит пакетно, поскольку последующее создание должно использовать предыдущие результаты.

В строке 36 все пустые карты сохраняются в roots_table, как показано на рисунке 3.


Root_table определяется следующим макросом.

#define READ_ONLY_ROOT_LIST(V)     
  STRONG_READ_ONLY_ROOT_LIST(V)    
  INTERNALIZED_STRING_ROOT_LIST(V) 
  PRIVATE_SYMBOL_ROOT_LIST(V)      
  PUBLIC_SYMBOL_ROOT_LIST(V)       
  WELL_KNOWN_SYMBOL_ROOT_LIST(V)   
  STRUCT_MAPS_LIST(V)              
  ALLOCATION_SITE_MAPS_LIST(V)     
  DATA_HANDLER_MAPS_LIST(V)

#define MUTABLE_ROOT_LIST(V)                
  STRONG_MUTABLE_IMMOVABLE_ROOT_LIST(V)     
  STRONG_MUTABLE_MOVABLE_ROOT_LIST(V)       
  V(StringTable, string_table, StringTable) 
  SMI_ROOT_LIST(V)

#define ROOT_LIST(V)     
  READ_ONLY_ROOT_LIST(V) 
  MUTABLE_ROOT_LIST(V)
Вход в полноэкранный режим Выйти из полноэкранного режима

В Root_table хранится не только карта, но и другие объекты кучи. По параметрам шаблона макроса мы можем примерно догадаться о роли каждого элемента. Если вы хотите погрузиться глубже, пожалуйста, отлаживайте.


Ну вот, на этом мы закончили. Увидимся в следующий раз, ребята, берегите себя!
_
Пожалуйста, свяжитесь со мной, если у вас возникнут какие-либо вопросы. WeChat: qq9123013 Email: v8blink@outlook.com

Оцените статью
devanswers.ru
Добавить комментарий