Схема выше показывает взаимодействие между основными компонентами JerryScript: Парсер и Виртуальная Машина (VM). Парсер выполняет перевод входного приложения ECMAScript в байт-код с указанным форматом (см. Байт-код и Парсер для подробностей). Подготовленный байт-код выполняется Виртуальной Машиной, которая выполняет интерпретацию (см. Виртуальная Машина и ECMA для подробностей).
Парсер реализован как рекурсивный спуск парсер. Парсер прямым образом переводит исходный код JavaScript в байт-код без построения абстрактного синтаксического дерева. Парсер зависит от следующих подкомпонентов.
Лексер разбивает входную строку (программу ECMAScript) на последовательность токенов. Он способен сканировать входную строку не только вперед, но и перемещаться в произвольную позицию. Структура токена описана структурой lexer_token_t
в ./jerry-core/parser/js/js-lexer.h
.
Сканер (./jerry-core/parser/js/js-parser-scanner.c
) предварительно сканирует входную строку для поиска определенных токенов. Например, сканер определяет, является ли ключевое слово for
общим циклом или циклом for-in
. Чтение токенов в цикле while
недостаточно, так как слеш (/
) может указывать на начало регулярного выражения или быть оператором деления.## Парсер Выражений
Парсер выражений отвечает за разбор выражений JavaScript. Он реализован в ./jerry-core/parser/js/js-parser-expr.c
.
Инструкции JavaScript разбираются этим компонентом. Он использует Парсер выражений для разбора составных выражений. Реализация Парсера инструкций находится в ./jerry-core/parser/js/js-parser-statm.c
.
Функция parser_parse_source
выполняет разбор и компиляцию входного исходного кода ECMAScript. Когда появляется функция в исходном коде, parser_parse_source
вызывает parser_parse_function
, который отвечает за обработку исходного кода функций рекурсивно, включая разбор аргументов и обработку контекста. После разбора функция parser_post_processing
записывает созданные опкоды и возвращает указатель ecma_compiled_code_t*
на скомпилированный байт-код. Взаимодействия между основными компонентами, показанные на следующем рисунке.
В этом разделе описывается компактное представление байт-кода (CBC). Основное внимание уделяется уменьшению потребления памяти байт-кодом без значительного ущерба для производительности. Другие представления байт-кода часто фокусируются только на производительности, поэтому создание этого представления является оригинальным исследованием.CBC — это набор инструкций в стиле CISC, который присваивает более короткие инструкции для часто используемых операций. Многие инструкции представляют собой несколько атомарных задач, что уменьшает размер байт-кода. Эта техника в основном является методом сжатия данных.## Формат скомпилированного кода
Планирование памяти скомпилированного байт-кода следующее.
Заголовок — это структура cbc_compiled_code
с несколькими полями. Эти поля содержат ключевые свойства скомпилированного кода.
Часть литералов — это массив значений ECMAScript. Эти значения могут содержать любые типы значений ECMAScript, например строки, числа, функции и шаблоны регулярных выражений. Количество литералов хранится в поле literal_end
заголовка.
Список инструкций CBC — это последовательность инструкций байт-кода, которая представляет собой скомпилированный код.
Планирование памяти байт-кода следующее:
Каждый байт-код начинается с опкода. Опкод имеет длину одного байта для часто используемых инструкций и двух байтов для редко используемых. Первый байт редко используемых инструкций всегда равен нулю (CBC_EXT_OPCODE
), а второй байт представляет собой расширенный опкод. Имена часто используемых и редко используемых инструкций начинаются с префиксов CBC_
и CBC_EXT_
соответственно.
Максимальное количество опкодов — 511, так как можно определить 255 часто используемых (исключая нулевое значение) и 256 редко используемых инструкций. В настоящее время доступно около 215 часто используемых и 70 редко используемых инструкций.В CBC есть три типа аргументов байт-кода:
Байтовый аргумент: Значение от 0 до 255, которое часто представляет количество аргументов для инструкций типа вызова (вызов функции, new, eval и т.д.).
Литеральный аргумент: Целочисленный индекс, который больше или равен нулю и меньше значения поля literal_end
заголовка. Для дополнительной информации см. следующий раздел Литералы (следующий).
Относительная ветвь: Смещение длиной от 1 до 3 байт. Аргумент ветви также может представлять конец диапазона инструкций. Например, аргумент ветви CBC_EXT_WITH_CREATE_CONTEXT
показывает конец инструкции with
. Более точно, это позиция после последней инструкции в блоке with
.
Комбинации аргументов ограничены следующими семью формами:
Литералы организованы в группы, которые представляют различные типы литералов. Имея эти группы, можно сэкономить место, чем если бы присваивать флаги для каждого литерала.(В следующих описаниях упомянутые диапазоны представляют индексы, которые больше или равны левой стороне диапазона и меньше правой стороны диапазона. Например, диапазон между полями ident_end
и literal_end
заголовка байт-кода содержит те индексы, которые больше или равны ident_end
и меньше literal_end
. Если ident_end
равен literal_end
, диапазон пуст.)Две основные группы литералов — это идентификаторы и значения.
идентификатор: Названный ссылочный объект для переменной. Литералы между нулем и ident_end
заголовка относятся к этой группе. Все эти литералы должны быть строками или неопределенными. Неопределенное значение может использоваться только для тех литералов, которые не могут быть доступны по имени. Например, в функции function (arg, arg)
есть два аргумента, но идентификатор arg
ссылается только на второй аргумент. В таких случаях имя первого аргумента неопределено. Кроме того, оптимизации, такие как CSE, могут также вводить литералы без имени.
значение: Ссылка на немедленное значение. Литералы между ident_end
и const_literal_end
являются постоянными значениями, такими как числа или строки. Эти литералы могут быть использованы напрямую виртуальной машиной. Литералы между const_literal_end
и literal_end
являются шаблонными литералами. Новый объект должен создаваться каждый раз, когда их значение используется. Эти литералы являются функциями и регулярными выражениями.
Ещё есть две подгруппы идентификаторов. Регистры — это идентификаторы, которые хранятся в стеке вызова функции. Аргументы — это регистры, которые передаются вызывающей функцией. В CBC используется два типа кодирования литералов. Оба они переменной длины, где длина может быть от одного до двух байт. * малый: максимальное количество кодируемых литералов — 511.
Одно байтовое кодирование для литералов 0 - 254.
byte[0] = literal_index
Два байтовое кодирование для литералов 255 - 510.
byte[0] = 0xff
byte[1] = literal_index - 0xff
Одно байтовое кодирование для литералов 0 - 127.
byte[0] = literal_index
Два байтовое кодирование для литералов 128 - 32767.
byte[0] = (literal_index >> 8) | 0x80
byte[1] = (literal_index & 0xff)
Так как большинство функций требуют менее 255 литералов, малое кодирование предоставляет однобайтовый индекс литерала для всех литералов. Малое кодирование занимает меньше места, чем полное, но имеет ограниченный диапазон.
JerryScript не имеет глобальной таблицы строк для литералов, но хранит их в хранилище литералов. В процессе парсинга, когда новый литерал появляется с тем же идентификатором, который уже появлялся ранее, строка не будет заново сохранена, а будет использован идентификатор из хранилища литералов. Если новый литерал ещё не находится в хранилище литералов, он будет вставлен.
Байт-коды могут быть разделены на четыре основные категории.
байт-код | описание |
---|---|
CBC_PUSH_LITERAL | Помещает значение данного литерального аргумента. |
CBC_PUSH_TWO_LITERALS | Помещает значения двух данного литеральных аргументов. |
CBC_PUSH_UNDEFINED | Помещает неопределенное значение. |
CBC_PUSH_TRUE | Помещает логическое значение true. |
CBC_PUSH_PROP_LITERAL | Помещает свойство, базовый объект которого снят с стека, а имя свойства передано как литеральный аргумент. |
Байт-коды этой категории выполняют вызовы различными способами.
| байт-код | описание | | --------------------- | --------------------------------------------------------------------------------- | | CBC_CALL0 | Вызывает функцию без аргументов. Значение возврата не будет помещено на стек. | | CBC_CALL1 | Вызывает функцию с одним аргументом. Значение возврата не будет помещено на стек. | | CBC_CALL | Вызывает функцию с n аргументами. n передается как байт-аргумент. Значение возврата не будет помещено на стек. | | CBC_CALL0_PUSH_RESULT | Вызывает функцию без аргументов. Значение возврата будет помещено на стек. | | CBC_CALL1_PUSH_RESULT | Вызывает функцию с одним аргументом. Значение возврата будет помещено на стек. | | CBC_CALL2_PROP | Вызывает функцию свойства с двумя аргументами. Основной объект, имя свойства и два аргумента находятся на стеке. |
Опкоды этой категории выполняют арифметические, логические, побитовые и операции присваивания.
| байт-код | описание | | ----------------------- | --------------------------------------------------------------------------------------------------- | | CBC_LOGICAL_NOT | Негативирует логическое значение, которое было выведено с вершины стека. Результат помещается обратно на вершину стека. | | CBC_LOGICAL_NOT_LITERAL | Негативирует логическое значение, указанное в литеральном аргументе. Результат помещается обратно на вершину стека. | | CBC_ADD | Складывает два значения, которые были выведены с вершины стека. Результат помещается обратно на вершину стека. | | CBC_ADD_RIGHT_LITERAL | Складывает два значения. Левое значение выведено с вершины стека, правое значение указано в литеральном аргументе. | | CBC_ADD_TWO_LITERALS | Складывает два значения. Оба значения указаны в литеральных аргументах. | | CBC_ASSIGN | Присваивает значение свойству. Имеет три аргумента: базовый объект, имя свойства, значение для присваивания. | | CBC_ASSIGN_PUSH_RESULT | Присваивает значение свойству. Имеет три аргумента: базовый объект, имя свойства, значение для присваивания. Результат помещается обратно на вершину стека. |### Ветвные байт-коды
Ветвные байт-коды используются для выполнения условных и безусловных прыжков в байт-коде. Аргументы этих инструкций имеют длину от 1 до 3 байт относительных смещений. Количество байт входит в состав опкода, поэтому каждая инструкция с аргументом ветвления имеет три формы. Направление (вперед, назад) также определяется опкодом, так как смещение является беззнаковым значением. Таким образом, некоторые инструкции ветвления имеют шесть форм. Некоторые примеры можно найти в следующей таблице.
байт-код | описание |
---|---|
CBC_JUMP_FORWARD | Прыгает вперед на относительное смещение длиной 1 байт. |
CBC_JUMP_FORWARD_2 | Прыгает вперед на относительное смещение длиной 2 байта. |
CBC_JUMP_FORWARD_3 | Прыгает вперед на относительное смещение длиной 3 байта. |
CBC_JUMP_BACKWARD | Прыгает назад на относительное смещение длиной 1 байт. |
CBC_JUMP_BACKWARD_2 | Прыгает назад на относительное смещение длиной 2 байта. |
CBC_JUMP_BACKWARD_3 | Прыгает назад на относительное смещение длиной 3 байта. |
CBC_BRANCH_IF_TRUE_FORWARD | Если значение на вершине стека истинно, прыгает вперед на относительное смещение длиной 1 байт. |
Виртуальная машина — это интерпретатор, который выполняет инструкции байт-кода по одному. Функция, которая запускает интерпретацию, это vm_run
в ./jerry-core/vm/vm.c
. vm_loop
— это основной цикл виртуальной машины, который имеет особенность быть нерекурсивным. Это означает, что в случае вызова функций она не вызывает себя рекурсивно, а возвращается, что имеет преимущество в том, что она не нагружает стек, как рекурсивная реализация.
ЭКМА-компонента движка отвечает за следующие понятия:
Основная структура для представления данных — это ECMA_value
. Нижние три бита этой структуры кодируют тег значения, который определяет тип значения:
В случае чисел, строк и объектов значение содержит закодированный указатель, а простое значение — это заранее определенная константа, которая может быть:
Сжатые указатели были введены для экономии пространства кучи.
Эти указатели — это 16-битные указатели, выравненные по 8 байтам, которые могут адресовать 512 Кб памяти, что также является максимальным размером кучи JerryScript. Для поддержки ещё большего объёма памяти размер сжатых указателей может быть расширен до 32 бит, чтобы охватить весь адресный пространство 32-битной системы, передавая "--cpointer_method 32_bit_on" в систему сборки. Эти "нераспакованные указатели" увеличивают потребление памяти примерно на 20%.### Числа
Согласно стандарту IEEE 754 существует два возможных представления чисел: по умолчанию это 8-байтовое (double), но движок поддерживает представление 4-байтовое (одинарной точности) путём установки JERRY_NUMBER_TYPE_FLOAT64 в 0.
Несколько ссылок на одно выделенное число не поддерживаются. Каждая ссылка хранит собственную копию числа.
Строки в JerryScript не являются просто последовательностями символов, но могут содержать числа и так называемые магические идентификаторы. Для обычных последовательностей символов (определённых в ./jerry-core/lit/lit-magic-strings.ini
) существует таблица в только для чтения памяти, содержащая пары магических идентификаторов и последовательностей символов. Если строка уже находится в этой таблице, то хранится магический идентификатор строки, а не сама последовательность символов. Использование чисел ускоряет доступ к свойствам. Эти техники экономят память.### Объект / Лексическое окружение
Объект может быть обычным объектом данных или объектом лексической среды. В отличие от других типов данных, объект может иметь ссылки (называемые свойствами) на другие типы данных. Из-за циклических ссылок подсчет ссылок не всегда достаточно для определения мертвых объектов. Поэтому формируется цепочка объектов из всех существующих объектов, которая может использоваться для поиска нессылочных объектов во время сборки мусора. Указатель gc-next
каждого объекта указывает на следующий выделенный объект в цепочке.
Лексические окружения реализуются в виде объектов в JerryScript, так как лексические окружения содержат пары ключ-значение (называемые связями) подобно объектам. Это упрощает реализацию и уменьшает размер кода.
Объекты представляются следующей структурой:
Объекты имеют связанный список, содержащий их свойства. Этот список фактически содержит пары свойств, чтобы сэкономить память, как описано ниже: Свойство имеет длину 7 бит, а его поле типа — 2 бита, что потребляет 9 бит, которые не помещаются в 1 байт, но потребляют 2 байта. Поэтому, объединение двух свойств (14 бит) с 2-битным полем типа помещается в 2 байта.
Если количество пар свойств достигает определенного предела (в настоящее время этот предел определен как 16), вставляется хэш-карта (называемая Хэш-картой свойств), которая вставляется на первое место в списке пар свойств, чтобы найти свойство с ее помощью, а не итерируясь линейно по парам свойств.
Хэш-карта свойств содержит 2n элементов, где 2n больше, чем количество свойств объекта. Каждый элемент может иметь три типа значений:
Внутренние свойства — это специальные свойства, которые содержат метаданные, которые не могут быть доступны JavaScript-кодом, но важны для самого движка. Некоторые примеры внутренних свойств приведены ниже:
LCache — это хеш-карта для поиска свойства, указанного объектом и именем свойства. Структура LCache с объектами и именами свойств повторяется несколько раз, как показано на рисунке ниже.
При обращении к свойству извлекается хеш-значение из требуемого имени свойства, а затем этот хеш используется для индексации LCache. После этого в индексированной строке ищется указанный объект и имя свойства.
Важно отметить, что если указанное свойство не найдено в LCache, это не означает, что оно не существует (т.е. LCache — это кеш, который может вернуть значение). Если свойство не найдено, оно будет искаться в списке свойств объекта, и если оно найдено там, свойство будет помещено в LCache.### Коллекции
Коллекции — это массивоподобные структуры данных, оптимизированные для экономии памяти. На самом деле, коллекция — это связный список, элементы которого не являются отдельными элементами, а массивами, которые могут содержать несколько элементов.
Чтобы реализовать обработку исключений, возвращаемые значения функций JerryScript могут указывать на их сбой или "исключительное" поведение. Возвращаемые значения — это ECMA-значения (см. раздел Представление данных), и если произошла ошибка, возвращается простое значение ECMA_VALUE_ERROR.
Каждое ECMA-значение, сохраненное движком, ассоциируется с виртуальным "владением", которое определяет, как управлять значением: когда освобождать его, когда он больше не требуется, и как передавать значение другой функции.
Изначально значение выделяется его владельцем (т.е. с владением). Владелец несет ответственность за освобождение выделенного значения. Когда значение передается функции в качестве аргумента, владение им не передается, и вызываемая функция должна сделать свой собственный копию значения. Однако, пока функция возвращает значение, владение передается, и вызывающая функция будет нести ответственность за его освобождение.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )