1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/openharmony-third_party_jerryscript

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
Клонировать/Скачать
04.INTERNALS.md 35 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.03.2025 00:58 c3796b3

Высокий уровень дизайна

Высокий уровень дизайна

Приведенная выше диаграмма демонстрирует взаимодействия между основными компонентами 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 структура

Заголовок — это структура 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 существует две формы кодирования литералов. Обе имеют переменную длину, где длина составляет один или два байта.

  • маленький: максимум 511 литералов могут быть закодированы.

Один байт для литералов 0 - 254.

byte[0] = literal_index

Два байта для литералов 255 - 510.

byte[0] = 0xff
byte[1] = literal_index - 0xff
  • полный: максимум 32767 литералов могут быть закодированы.

Один байт для литералов 0 - 127.

byte[0] = literal_index

Два байта для литералов 128 - 32767.

byte[0] = (literal_index >> 8) | 0x80
byte[1] = (literal_index & 0xff)

Так как большинство функций требуют менее 255 литералов, маленькое кодирование предоставляет одиночный байт для всех литералов. Маленькое кодирование занимает меньше места, чем полное, но имеет ограниченный диапазон.

Хранилище литералов

JerryScript не имеет глобальной таблицы строк для литералов, но хранит их в хранилище литералов. Во время этапа парсинга, когда новый литерал появляется с тем же идентификатором, который уже был встречен ранее, строка не будет заново сохраняться, а вместо этого будет использован идентификатор в хранилище литералов. Если новый литерал ещё не находится в хранилище литералов, он будет вставлен.

Категории байт-кода

Байт-коды могут быть разделены на четыре основные категории.

Инструкции PUSH

Инструкции этой категории используются для помещения объектов на стэк. Так как в CBC много инструкций представляют множественные атомарные задачи, существуют также множество инструкций для помещения объектов на стэк согласно количеству и типу аргументов. Ниже приведена таблица некоторых этих опкодов с кратким описанием.

байт-код описание
CBC_PUSH_LITERAL Устанавливает значение данного литерального аргумента.
CBC_PUSH_TWO_LITERALS Устанавливает значения двух данном литеральных аргументов.
CBC_PUSH_UNDEFINED Устанавливает значение undefined.
CBC_PUSH_TRUE Устанавливает значение логической true.
CBC_PUSH_PROP_LITERAL Устанавливает свойство, базовый объект которого выгружен из стэка, а имя свойства передано как литеральный аргумент.

Инструкции CALL

Инструкции этой категории выполняют вызовы различными способами.

байт-код описание
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 байт.

Снимок экрана

Скомпилированный байт-код может быть сохранен в виде снимка экрана, который также может быть загружен обратно для выполнения. Прямое выполнение снимка экрана экономит затраты на парсинг исходного кода в плане потребления памяти и производительности. Снимок экрана также может быть выполнен из ROM, в этом случае можно также сэкономить время загрузки его в память.

Виртуальная машина

Виртуальная машина — это интерпретатор, который выполняет инструкции байт-кода по одному. Функция, которая начинает интерпретацию, называется vm_run в ./jerry-core/vm/vm.c. vm_loop — это главный цикл виртуальной машины, который отличается тем, что он не рекурсивный. Это значит, что при вызовах функций она не вызывает себя рекурсивно, а возвращает управление, что позволяет избежать нагрузки на стек, как в рекурсивной реализации.

ECMA

Компонент ECMA движка отвечает за следующие понятия:

  • Представление данных
  • Временная модель выполнения
  • Сборка мусора (GC)

Представление данных

Основная структура для представления данных — это ECMA_value. Нижние три бита этой структуры кодируют тэг значения, который определяет тип значения:

  • простой
  • число
  • строка
  • объект
  • символ
  • ошибка

Представление ECMA значения

В случае чисел, строк и объектов значение содержит закодированную указатель, а простое значение — это зарезервированная константа, которая может быть:

  • неопределенное
  • null
  • true
  • false
  • пустое (неразмеченное значение)

Сжатые указатели

Сжатые указатели были введены для экономии места в куче.

Сжатый указатель

Эти указатели являются 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 больше чем количество свойств объекта. Каждый элемент может иметь три типа значений:

  • null, указывающее на пустой элемент,
  • удалённое, указывающее на удалённое свойство,
  • или ссылка на существующее свойство.

Эта хеш-карта является обязательной кэш-системой, что означает, что каждое свойство, которое имеет объект, может быть найдено с помощью неё.

Внутренние свойства

Внутренние свойства — это специальные свойства, которые содержат метаданные, которые не могут быть получены через JavaScript-код, но важны для самого движка. Некоторые примеры внутренних свойств приведены ниже:

  • [[Class]] - класс (тип) объекта (определённый в ECMA)
  • [[Code]] - указывает, где найти байт-код функции
  • native code - указывает, где найти код для встроенной функции
  • [[PrimitiveValue]] для Boolean - хранит булево значение объекта Boolean
  • [[PrimitiveValue]] для Number - хранит числовое значение объекта Number

LCache

LCache представляет собой хеш-карту для поиска свойства, указанного объектом и именем свойства. Размещение объекта-имя-свойства в LCache повторяется несколько раз, как показано на рисунке ниже.

LCache

Когда происходит доступ к свойству, из имени требуемого свойства вытягивается хэш-значение, а затем этот хеш используется для индексации LCache. После этого в индексированной строке будет выполнен поиск указанного объекта и имени свойства.

Важно отметить, что если указываемое свойство не найдено в LCache, это не означает, что оно не существует (то есть LCache — это может вернуть кеш). Если свойство не найдено, его будет искать в списке свойств объекта, и если он там найден, то свойство будет помещено в LCache.

Коллекции

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

Обработка исключений

Чтобы реализовать чувство обработки исключений, значения возврата функций JerryScript способны указывать на их сбой или "исключительное" поведение. Возвращаемые значения представляют собой ECMA значения (см. раздел Представление данных), и если произошла ошибочная операция, возвращается простое значение ECMA_VALUE_ERROR.

Управление значениями и владением

Каждое ECMA значение, хранимое движком, связано с виртуальным "владением", которое определяет, как управлять значением: когда освобождать его, когда он больше не требуется, и как передавать значение другой функции.

Изначально значение выделяется владельцем (то есть с владением). Владелец несет ответственность за освобождение выделенного значения. Когда значение передается функции в качестве аргумента, владение им не переходит, вызванная функция должна сделать свою копию значения. Однако, пока функция возвращает значение, владение переходит, поэтому вызывающая сторона будет нести ответственность за его освобождение.

Комментарий ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://gitlife.ru/oschina-mirror/openharmony-third_party_jerryscript.git
git@gitlife.ru:oschina-mirror/openharmony-third_party_jerryscript.git
oschina-mirror
openharmony-third_party_jerryscript
openharmony-third_party_jerryscript
master