Приведенная выше диаграмма демонстрирует взаимодействия между основными компонентами 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
или циклом 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, такие как строки, числа, функции и шаблоны регулярных выражений. Количество литералов хранится в поле 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
заголовка относятся к этому типу. Все эти литералы должны быть строками или undefined
. Undefined
может использоваться только для тех литералов, которые нельзя получить по имени. Например, в функции (arg,arg)
имеются два аргумента, но идентификатор arg
ссылается только на второй аргумент. В этом случае имя первого аргумента undefined
. Кроме того, оптимизации, такие как CSE, могут также вводить литералы без имени.
ident_end
и const_literal_end
являются постоянными значениями, такими как числа или строки. Эти литералы могут быть использованы напрямую Виртуальной машиной. Литералы между const_literal_end
и literal_end
являются шаблонными литералами. При каждом обращении к их значению требуется создание нового объекта. Эти литералы являются функциями и регулярными выражениями.Ещё два подтипа идентификаторов: регистры — это идентификаторы, которые хранятся в стэке вызова функции; аргументы — это регистры, передаваемые вызывающей функцией.
В CBC существует две формы кодирования литералов. Обе имеют переменную длину, где длина составляет один или два байта.
Один байт для литералов 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 много инструкций представляют множественные атомарные задачи, существуют также множество инструкций для помещения объектов на стэк согласно количеству и типу аргументов. Ниже приведена таблица некоторых этих опкодов с кратким описанием.
байт-код | описание |
---|---|
CBC_PUSH_LITERAL | Устанавливает значение данного литерального аргумента. |
CBC_PUSH_TWO_LITERALS | Устанавливает значения двух данном литеральных аргументов. |
CBC_PUSH_UNDEFINED | Устанавливает значение 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 | Присваивает значение свойству. У него есть три аргумента: основной объект, имя свойства, значение для присваивания. Результат будет помещен в стек. |
байт-код | описание |
---|---|
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 байт. |
Скомпилированный байт-код может быть сохранен в виде снимка экрана, который также может быть загружен обратно для выполнения. Прямое выполнение снимка экрана экономит затраты на парсинг исходного кода в плане потребления памяти и производительности. Снимок экрана также может быть выполнен из ROM, в этом случае можно также сэкономить время загрузки его в память.
Виртуальная машина — это интерпретатор, который выполняет инструкции байт-кода по одному. Функция, которая начинает интерпретацию, называется vm_run
в ./jerry-core/vm/vm.c
. vm_loop
— это главный цикл виртуальной машины, который отличается тем, что он не рекурсивный. Это значит, что при вызовах функций она не вызывает себя рекурсивно, а возвращает управление, что позволяет избежать нагрузки на стек, как в рекурсивной реализации.
Компонент ECMA движка отвечает за следующие понятия:
Основная структура для представления данных — это ECMA_value
. Нижние три бита этой структуры кодируют тэг значения, который определяет тип значения:
В случае чисел, строк и объектов значение содержит закодированную указатель, а простое значение — это зарезервированная константа, которая может быть:
Сжатые указатели были введены для экономии места в куче.
Эти указатели являются 8-байтовыми выравненными 16-битными указателями, которые могут адресовать 512 Кб памяти, что также является максимальным размером кучи JerryScript. Для поддержки еще большего объема памяти размер сжатых указателей может быть расширен до 32 бит для покрытия всего адресного пространства 32-битной системы путем передачи "--cpointer_32_bit on" системе сборки. Эти "нерасжатые указатели" увеличивают потребление памяти примерно на 20%.
Существуют две возможные формы представления чисел согласно стандарту IEEE 754: По умолчанию используется 8-байтовое (double), но движок поддерживает представление 4-байтового (однопrecision) числа путем установки JERRY_NUMBER_TYPE_FLOAT64 в 0.
Несколько ссылок на одно выделенное число не поддерживаются. Каждая ссылка хранит свою собственную копию числа.
Строки в JerryScript не являются просто последовательностями символов, они могут содержать числа и так называемые магические идентификаторы. Для обычных последовательностей символов (определённых в ./jerry-core/lit/lit-magic-strings.ini
) существует таблица в только-для-чтения памяти, содержащая пары магических идентификаторов и последовательностей символов. Если строка уже находится в этой таблице, то её магический идентификатор записывается, а не сама последовательность символов. Использование чисел ускоряет доступ к свойствам. Эти методы экономят память.
Объект может быть обычным данными или лексической средой. В отличие от других типов данных, объект может иметь ссылки (называемые свойствами) на другие типы данных. Из-за круговых ссылок, учёт ссылок не всегда достаточно для определения мёртвых объектов. Поэтому создается связный список всех существующих объектов, который может использоваться для поиска неприсоединённых объектов во время сборки мусора. Поле gc-next
каждого объекта показывает следующий выделенный объект в списке.
Лексические среды реализованы как объекты в JerryScript, поскольку лексические среды содержат пары ключ-значение (называемые связями), как объекты. Это упрощает реализацию и снижает размер кода.
Объекты представлены следующей структурой:
Объекты имеют связанную структуру, которая содержит их свойства. Эта структура действительно содержит пары свойств, чтобы сэкономить место, как описано ниже:
Свойство имеет длину 7 бит, а поле типа — 2 бита, что потребляет 9 бит, которые не помещаются в один байт, но занимают 2 байта. Поэтому, объединение двух свойств (14 бит) с 2-битным полем типа помещается в 2 байта.
Если количество пар свойств достигает определенного лимита (в настоящее время этот лимит установлен равным 16), в начале списка пар свойств вставляется хеш-карта (называемая Хеш-картой свойств), чтобы использовать её для поиска свойства вместо линейного перебора пар свойств.
Хеш-карта содержит 2^n элементов, где 2^n больше чем количество свойств объекта. Каждый элемент может иметь три типа значений:
Эта хеш-карта является обязательной кэш-системой, что означает, что каждое свойство, которое имеет объект, может быть найдено с помощью неё.
Внутренние свойства — это специальные свойства, которые содержат метаданные, которые не могут быть получены через JavaScript-код, но важны для самого движка. Некоторые примеры внутренних свойств приведены ниже:
LCache представляет собой хеш-карту для поиска свойства, указанного объектом и именем свойства. Размещение объекта-имя-свойства в LCache повторяется несколько раз, как показано на рисунке ниже.
Когда происходит доступ к свойству, из имени требуемого свойства вытягивается хэш-значение, а затем этот хеш используется для индексации LCache. После этого в индексированной строке будет выполнен поиск указанного объекта и имени свойства.
Важно отметить, что если указываемое свойство не найдено в LCache, это не означает, что оно не существует (то есть LCache — это может вернуть кеш). Если свойство не найдено, его будет искать в списке свойств объекта, и если он там найден, то свойство будет помещено в LCache.
Коллекции — это массивоподобные структуры данных, оптимизированные для экономии памяти. На самом деле коллекция представляет собой связный список, чьи элементы не являются отдельными элементами, а массивами, содержащими несколько элементов.
Чтобы реализовать чувство обработки исключений, значения возврата функций JerryScript способны указывать на их сбой или "исключительное" поведение. Возвращаемые значения представляют собой ECMA значения (см. раздел Представление данных), и если произошла ошибочная операция, возвращается простое значение ECMA_VALUE_ERROR.
Каждое ECMA значение, хранимое движком, связано с виртуальным "владением", которое определяет, как управлять значением: когда освобождать его, когда он больше не требуется, и как передавать значение другой функции.
Изначально значение выделяется владельцем (то есть с владением). Владелец несет ответственность за освобождение выделенного значения. Когда значение передается функции в качестве аргумента, владение им не переходит, вызванная функция должна сделать свою копию значения. Однако, пока функция возвращает значение, владение переходит, поэтому вызывающая сторона будет нести ответственность за его освобождение.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарий ( 0 )