Документация Engee

Общий обзор процесса генерации машинного кода

Представление указателей

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

В противном случае они будут выводиться как литеральные константы.

Чтобы вывести один из этих объектов, вызовите функцию literal_pointer_val. Она будет отслеживать значение Julia и глобального объекта LLVM, гарантируя, что они допустимы как для текущей среды выполнения, так и после десериализации.

При передаче в файл объекта эти глобальные объекты хранятся в виде ссылок в большой таблице gvals. В этом случае десериализатор может ссылаться на них по индексу и реализовать для их восстановления пользовательский ручной механизм, подобный глобальной таблице смещения (GOT).

Указатели функций обрабатываются аналогичным образом. Они хранятся как значения в большой таблице fvals. Как и в случае с глобальными объектами, десериализатор может ссылаться на них по индексу.

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

Учтите, что функции ccall также обрабатываются отдельно с помощью используемой вручную таблицы GOT и таблицы компоновки процедур (PLT).

Представление промежуточных значений

Значения передаются в структуре jl_cgval_t. Она представляет R-значение и содержит достаточно информации, чтобы определить, как его присвоить или передать его куда-либо.

Значения создаются с помощью одного из вспомогательных конструкторов, обычно следующего: mark_julia_type (для непосредственных значений) и mark_julia_slot (для указателей на значения).

Функция convert_julia_type может выполнять преобразование между любыми двумя типами. Она возвращает R-значение с cgval.typ, имеющим значение typ. Она приведет объект к требуемому представлению, создавая указатели кучи, выделяя копии стека и вычисляя размеченные объединения по мере необходимости для изменения представления.

Напротив, функция update_julia_type изменит cgval.typ на typ, только если это можно сделать с нулевыми затратами (т. е. не создавая кода).

Представление объединения

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

Для обработки размеченных объединений используются следующие простые процедуры.

  • mark-type

  • load-local

  • store-local

  • isa

  • is

  • emit_typeof

  • emit_sizeof

  • boxed

  • unbox

  • specialized cc-ret

Для всего остального должна существовать возможность обработки в выводе с использованием этих примитивов для реализации разделения объединения.

Представление размеченного объединения в виде пары < void* union, byte selector >. Селектор имеет фиксированный размер byte & 0x7f и разметит объединение первых 126 типов isbits. Он записывает количество в глубину на основе единицы в объединение типов объектов isbits. Индекс, равный нулю, указывает, что union* на самом деле является размеченным выделенным в куче jl_value_t* и должен рассматриваться как обычный упакованный объект, а не как размеченное объединение.

Старший бит селектора (byte & 0x80) можно проверить, чтобы определить, является ли void* на самом деле указателем, выделенным в куче (jl_value_t*), что позволяет избежать затрат на повторное выделение блока, сохраняя при этом возможность эффективной обработки разделения объединения на основе младших битов.

Гарантируется, что byte & 0x7f является точным тестом для типа; если значение может быть представлено меткой, оно никогда не будет помечено byte = 0x80. При тестировании isa нет необходимости также проверять метку типа.

Выделенная область памяти union* может иметь любой размер. Единственным ограничением является то, что она должна быть достаточно большой, чтобы содержать данные, указываемый в данный момент селектором (selector). Она может быть недостаточно большой, чтобы вмещать объединение всех типов, которые могут храниться в нем в соответствии со связанным полем типа объединения. Копирование следует выполнять с осторожностью.

Представление специализированной сигнатуры соглашения о вызовах

Объект jl_returninfo_t описывает детали специализированной конвенции вызова любого вызываемого объекта. Он может быть сгенерирован из любой пары (specTypes, rettype), такой как CodeInstance, или другого места, где они объявлены. Это ожидаемая конвенция вызова для specptr, но там могут храниться и другие данные. Только если хранящийся там указатель функции имеет ожидаемое специализированное соглашение о вызове, в specsigflags будет установлен соответствующий флаг, указывающий, что он может быть использован.

Если любой из аргументов или тип возврата метода может быть представлен без упаковки, и ни один из них не может быть представлен без упаковки (например, неограниченный vararg), ему будет присвоена оптимизированная сигнатура конвенции вызова на основе значений specTypes и rettype.

Общие принципы заключаются в следующем.

  • Примитивные типы передаются в регистрах целых чисел или чисел с плавающей запятой.

  • Типы VecElement передаются в векторных регистрах.

  • Структуры передаются в стеке.

  • Возвращаемые значения обрабатываются аналогично аргументам, с ограничением размера, при котором они будут возвращены с помощью скрытого аргумента sret.

Общая логика реализуется с помощью get_specsig_function и deserves_sret.

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