High-level Overview of the Native-Code Generation Process
Representation of Pointers
При генерации кода в объектный файл указатели будут выданы как релокации. Код десериализации обеспечит, чтобы любой объект, который указывал на одну из этих констант, был воссоздан и содержал правильный указатель времени выполнения.
В противном случае они будут выведены как буквальные константы.
Чтобы создать один из этих объектов, вызовите literal_pointer_val
. Он будет отслеживать значение Julia и глобальную переменную LLVM, обеспечивая их корректность как для текущего времени выполнения, так и после десериализации.
Когда они выводятся в объектный файл, эти глобальные переменные хранятся как ссылки в большой таблице gvals
. Это позволяет десериализатору ссылаться на них по индексу и реализовать собственный ручной механизм, аналогичный таблице глобальных смещений (GOT), для их восстановления.
Указатели на функции обрабатываются аналогично. Они хранятся как значения в большой таблице fvals
. Как и глобальные переменные, это позволяет десериализатору ссылаться на них по индексу.
Обратите внимание, что функции extern
обрабатываются отдельно, с именами, через обычный механизм разрешения символов в компоновщике.
Обратите внимание, что функции ccall
также обрабатываются отдельно, через ручную таблицу GOT и таблицу связывания процедур (PLT).
Representation of Intermediate Values
Значения передаются в структуре jl_cgval_t
. Это представляет собой R-значение и включает достаточно информации, чтобы определить, как присвоить или передать его куда-либо.
Они создаются с помощью одного из вспомогательных конструкторов, обычно: mark_julia_type
(для немедленных значений) и mark_julia_slot
(для указателей на значения).
Функция convert_julia_type
может преобразовывать между любыми двумя типами. Она возвращает R-значение с установленным cgval.typ
равным typ
. Она приведет объект к запрашиваемому представлению, создавая кучи, выделяя копии в стеке и вычисляя помеченные объединения по мере необходимости для изменения представления.
В отличие от этого, update_julia_type
изменит cgval.typ
на typ
, только если это можно сделать без затрат (т.е. без генерации какого-либо кода).
Union representation
Выведенные объединенные типы могут быть выделены в стеке с помощью представления типизированного типа.
Примитивные процедуры, которые должны уметь обрабатывать помеченные объединения, это:
- mark-type
- load-local
- store-local
- isa
- является
- emit_typeof
- emit_sizeof
- заблокированный
- разобрать
- специализированный 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
. Он может быть недостаточно большим, чтобы содержать объединение всех типов, которые могут быть там сохранены в соответствии с соответствующим полем типа Union. Используйте соответствующие меры предосторожности при копировании.
Specialized Calling Convention Signature Representation
Объект jl_returninfo_t
описывает детали соглашения о вызове для любого вызываемого объекта.
Если любой из аргументов или возвращаемый тип метода может быть представлен без упаковки, и метод не является varargs, ему будет присвоен оптимизированный сигнатурный вызов на основе его полей specTypes
и rettype
.
Общие принципы заключаются в том, что:
- Примитивные типы передаются в регистрах int/float.
- Кортежи типов VecElement передаются в векторных регистрах.
- Структуры передаются по стеку.
- Возвращаемые значения обрабатываются аналогично аргументам, с порогом размера, при котором они будут возвращены через скрытый аргумент sret.
Общая логика для этого реализована в get_specsig_function
и deserves_sret
.
Кроме того, если возвращаемый тип является объединением, он может быть возвращен в виде пары значений (указатель и тег). Если значения объединения могут быть выделены в стеке, то достаточное пространство для их хранения также будет передано в качестве скрытого первого аргумента. Решение о том, будет ли возвращаемый указатель указывать на это пространство, упакованный объект или даже другую постоянную память, остается за вызываемой функцией.