Общий обзор процесса генерации машинного кода
Представление указателей
При передаче кода в файл объекта указатели будут передаваться как перемещения. Код десериализации гарантирует, что любой объект, который указывал на одну из этих констант, будет создан повторно и будет содержать правильный указатель среды выполнения.
В противном случае они будут выводиться как литеральные константы.
Чтобы вывести один из этих объектов, вызовите функцию 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
.
Общие принципы заключаются в следующем.
-
Примитивные типы передаются в регистрах целых чисел или чисел с плавающей запятой.
-
Типы VecElement передаются в векторных регистрах.
-
Структуры передаются в стеке.
-
Возвращаемые значения обрабатываются аналогично аргументам, с ограничением размера, при котором они будут возвращены с помощью скрытого аргумента sret.
Общая логика реализуется с помощью get_specsig_function
и deserves_sret
.
Кроме того, если возвращаемый тип является объединением, он может быть возвращен как пара значений (указатель и метка). Если значения объединения могут быть размещены в стеке, достаточное пространство для их хранения также будет передано в качестве скрытого первого аргумента. На что будет указывать возвращаемый указатель — на это пространство, упакованный объект или даже на другую постоянную память, зависит от вызываемого объекта.