Первоисточник: 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