Julia ASTs
Джулия имеет два представления кода. Сначала есть синтаксическое дерево (AST), возвращаемое парсером (например, функция Meta.parse), и манипулируемое макросами. Это структурированное представление кода, как он написан, построенное с помощью julia-parser.scm из потока символов. Затем есть пониженная форма, или IR (промежуточное представление), которая используется для вывода типов и генерации кода. В пониженной форме меньше типов узлов, все макросы развернуты, и все потоки управления преобразованы в явные ветвления и последовательности операторов. Пониженная форма строится с помощью julia-syntax.scm.
Сначала мы сосредоточимся на AST, так как он необходим для написания макросов.
Surface syntax AST
Фронтенд ASTs состоят почти полностью из Expr и атомов (например, символов, чисел). Обычно для каждой визуально отличительной синтаксической формы существует различная голова выражения. Примеры будут приведены в синтаксисе s-выражений. Каждый список в скобках соответствует Expr, где первый элемент - это голова. Например, (call f x) соответствует Expr(:call, :f, :x) в Julia.
Calls
| Input | AST |
|---|---|
f(x) | (call f x) |
f(x, y=1, z=2) | (call f x (kw y 1) (kw z 2)) |
f(x; y=1) | (call f (parameters (kw y 1)) x) |
f(x...) | (call f (... x)) |
do синтаксис:
f(x) do a,b
body
endразбирается как (do (call f x) (-> (tuple a b) (block body))).
Operators
Большинство использований операторов — это просто вызовы функций, поэтому они разбираются с заголовком call. Однако некоторые операторы являются специальными формами (не обязательно вызовами функций), и в этих случаях сам оператор является заголовком выражения. В julia-parser.scm они называются "синтаксическими операторами". Некоторые операторы (+ и *) используют N-арное разбор; цепочные вызовы разбираются как единый N-арный вызов. Наконец, цепочки сравнений имеют свою собственную специальную структуру выражения.
| Input | AST |
|---|---|
x+y | (call + x y) |
a+b+c+d | (call + a b c d) |
2x | (call * 2 x) |
a&&b | (&& a b) |
x += 1 | (+= x 1) |
a ? 1 : 2 | (if a 1 2) |
a,b | (tuple a b) |
a==b | (call == a b) |
1<i<=n | (comparison 1 < i <= n) |
a.b | (. a (quote b)) |
a.(b) | (. a (tuple b)) |
Bracketed forms
| Input | AST |
|---|---|
a[i] | (ref a i) |
t[i;j] | (typed_vcat t i j) |
t[i j] | (typed_hcat t i j) |
t[a b; c d] | (typed_vcat t (row a b) (row c d)) |
t[a b;;; c d] | (typed_ncat t 3 (row a b) (row c d)) |
a{b} | (curly a b) |
a{b;c} | (curly a (parameters c) b) |
[x] | (vect x) |
[x,y] | (vect x y) |
[x;y] | (vcat x y) |
[x y] | (hcat x y) |
[x y; z t] | (vcat (row x y) (row z t)) |
[x;y;; z;t;;;] | (ncat 3 (nrow 2 (nrow 1 x y) (nrow 1 z t))) |
[x for y in z, a in b] | (comprehension (generator x (= y z) (= a b))) |
T[x for y in z] | (typed_comprehension T (generator x (= y z))) |
(a, b, c) | (tuple a b c) |
(a; b; c) | (block a b c) |
Macros
| Input | AST |
|---|---|
@m x y | (macrocall @m (line) x y) |
Base.@m x y | (macrocall (. Base (quote @m)) (line) x y) |
@Base.m x y | (macrocall (. Base (quote @m)) (line) x y) |
Strings
| Input | AST |
|---|---|
"a" | "a" |
x"y" | (macrocall @x_str (line) "y") |
x"y"z | (macrocall @x_str (line) "y" "z") |
"x = $x" | (string "x = " x) |
`a b c` | (macrocall @cmd (line) "a b c") |
Синтаксис строк документации:
"some docs"
f(x) = xразбирается как (macrocall (|.| Core '@doc) (line) "некоторые документы" (= (call f x) (block x))).
Imports and such
| Input | AST |
|---|---|
import a | (import (. a)) |
import a.b.c | (import (. a b c)) |
import ...a | (import (. . . . a)) |
import a.b, c.d | (import (. a b) (. c d)) |
import Base: x | (import (: (. Base) (. x))) |
import Base: x, y | (import (: (. Base) (. x) (. y))) |
export a, b | (export a b) |
using имеет такое же представление, как import, но с заголовком выражения :using вместо :import.
Numbers
Julia поддерживает больше типов чисел, чем многие реализации Scheme, поэтому не все числа представлены напрямую как числа Scheme в AST.
| Input | AST |
|---|---|
11111111111111111111 | (macrocall @int128_str nothing "11111111111111111111") |
0xfffffffffffffffff | (macrocall @uint128_str nothing "0xfffffffffffffffff") |
1111...many digits... | (macrocall @big_str nothing "1111....") |
Block forms
Блок операторов разбирается как (block stmt1 stmt2 ...).
Если оператор:
if a
b
elseif c
d
else
e
endразбирается как:
(if a (block (line 2) b)
(elseif (block (line 3) c) (block (line 4) d)
(block (line 6 e))))Цикл while разбирается как (while условие тело).
Цикл for разбирается как (for (= var iter) body). Если есть более одной спецификации итерации, они разбираются как блок: (for (block (= v1 iter1) (= v2 iter2)) body).
break и continue интерпретируются как выражения без аргументов (break) и (continue).
let разбирается как (let (= var val) body) или (let (block (= var1 val1) (= var2 val2) ...) body), как и циклы for.
Базовое определение функции разбирается как (function (call f x) body). Более сложный пример:
function f(x::T; k = 1) where T
return x+1
endразбирается как:
(function (where (call f (parameters (kw k 1))
(:: x T))
T)
(block (line 2) (return (call + x 1))))Определение типа:
mutable struct Foo{T<:S}
x::T
endразбирается как:
(struct true (curly Foo (<: T S))
(block (line 2) (:: x T)))Первый аргумент - это булево значение, указывающее, является ли тип изменяемым.
try блоки разбираются как (try try_block var catch_block finally_block). Если после catch нет переменной, то var равно #f. Если нет finally клаузулы, то последний аргумент отсутствует.
Quote expressions
Синтаксические формы исходного кода Julia для цитирования кода (quote и :( )) поддерживают интерполяцию с помощью $. В терминологии Lisp это означает, что они на самом деле являются формами "обратной кавычки" или "квазицитаты". Внутренне также существует необходимость в цитировании кода без интерполяции. В схеме кода Julia неинтерполирующая цитата представлена с помощью заголовка выражения inert.
inert выражения преобразуются в объекты QuoteNode Julia. Эти объекты оборачивают одно значение любого типа, и при оценке просто возвращают это значение.
Выражение quote, аргументом которого является атом, также преобразуется в QuoteNode.
Line numbers
Информация о местоположении источника представлена в виде (line line_num file_name), где третий компонент является необязательным (и опускается, когда изменяется только номер текущей строки, но не имя файла).
Эти выражения представлены как LineNumberNode в Julia.
Macros
Макро-гигиена представлена через выражение head pair escape и hygienic-scope. Результат расширения макроса автоматически оборачивается в (hygienic-scope block module), чтобы представить результат новой области видимости. Пользователь может вставить (escape block) внутрь, чтобы интерполировать код из вызывающего контекста.
Lowered form
Сниженная форма (IR) более важна для компилятора, так как она используется для вывода типов, оптимизаций, таких как инлайнинг, и генерации кода. Она также менее очевидна для человека, так как является результатом значительной переработки входного синтаксиса.
В дополнение к Symbol и некоторым числовым типам, в пониженном виде существуют следующие типы данных:
ExprИмеет тип узла, указанный полем
head, и полеargs, которое являетсяVector{Any}подвыражений. Хотя почти каждая часть поверхностного AST представленаExpr, IR использует только ограниченное количествоExpr, в основном для вызовов и некоторых форм, доступных только на верхнем уровне.НомерСлотаИдентифицирует аргументы и локальные переменные последовательной нумерацией. У него есть целочисленное поле
id, указывающее индекс слота. Типы этих слотов можно найти в полеslottypesих объектаCodeInfo.АргументТо же самое, что и
SlotNumber, но появляется только после оптимизации. Указывает на то, что ссылающийся слот является аргументом окружающей функции.CodeInfoОборачивает IR группы операторов. Его поле
codeявляется массивом выражений для выполнения.GotoNodeБезусловный переход. Аргументом является цель перехода, представленная в виде индекса в массиве кода, на который нужно перейти.
GotoIfNotУсловная ветвь. Если поле
condоценивается как ложное, переходит к индексу, указанному в полеdest.ReturnNodeВозвращает свой аргумент (поле
val) в качестве значения окружающей функции. Если полеvalнеопределено, то это представляет собой недостижимое выражение.QuoteNodeОборачивает произвольное значение для ссылки в качестве данных. Например, функция
f() = :aсодержитQuoteNode, полеvalueкоторого является символомa, чтобы вернуть сам символ вместо его вычисления.GlobalRefСсылается на глобальную переменную
nameв модулеmod.SSAValueОтносится к последовательно пронумерованной (начиная с 1) статической переменной единственного присваивания (SSA), вставленной компилятором. Номер (
id)SSAValue— это индекс массива кода выражения, значение которого он представляет.NewvarNodeОтмечает точку, где создается переменная (слот). Это приводит к сбросу переменной в неопределенное состояние.
Expr types
Эти символы появляются в поле head Expr в нижнем регистре.
callВызов функции (динамическая диспетчеризация).
args[1]— это функция для вызова,args[2:end]— это аргументы.invokeВызов функции (статическая диспетчеризация).
args[1]— это экземпляр метода, который нужно вызвать,args[2:end]— это аргументы (включая вызываемую функцию, которая находится вargs[2]).статический_параметрСсылайтесь на статический параметр по индексу.
=Задание. В IR первый аргумент всегда является
SlotNumberилиGlobalRef.методДобавляет метод к обобщенной функции и при необходимости присваивает результат.
Имеет форму с 1 аргументом и форму с 3 аргументами. Форма с 1 аргументом возникает из синтаксиса
function foo end. В форме с 1 аргументом аргумент является символом. Если этот символ уже называет функцию в текущей области видимости, ничего не происходит. Если символ не определен, создается новая функция и присваивается идентификатору, указанному символом. Если символ определен, но называет не функцию, возникает ошибка. Определение "называет функцию" заключается в том, что связывание является постоянным и ссылается на объект с уникальным типом. Обоснование этого заключается в том, что экземпляр уникального типа однозначно идентифицирует тип, к которому нужно добавить метод. Когда тип имеет поля, не было бы ясно, добавляется ли метод к экземпляру или его типу.Форма с 3 аргументами имеет следующие аргументы:
args[1]Имя функции или
nothing, если неизвестно или не нужно. Если это символ, то выражение сначала ведет себя как форма с 1 аргументом выше. Этот аргумент игнорируется с этого момента. Он может бытьnothing, когда методы добавляются строго по типу,(::T)(x) = x, или когда метод добавляется к существующей функции,MyModule.f(x) = x.args[2]SimpleVectorаргумента типа данных.args[2][1]являетсяSimpleVectorтипов аргументов, аargs[2][2]являетсяSimpleVectorпеременных типа, соответствующих статическим параметрам метода.args[3]CodeInfoметода самого себя. Для определений методов "вне области видимости" (добавление метода к функции, которая также имеет методы, определенные в разных областях видимости) это выражение, которое вычисляется в:lambdaвыражение.
struct_typeВыражение с 7 аргументами, которое определяет новую
struct:args[1]Имя
structargs[2]Выражение
call, которое создаетSimpleVector, указывая его параметрыargs[3]callвыражение, которое создаетSimpleVector, указывая его имена полейargs[4]Symbol,GlobalRefилиExpr, указывающий на суперкласс (например,:Integer,GlobalRef(Core, :Any)или:(Core.apply_type(AbstractArray, T, N)))args[5]callвыражение, которое создаетSimpleVector, указывая его типы полейargs[6]Булев, истинный, если
mutableargs[7]Количество аргументов для инициализации. Это будет количество полей или минимальное количество полей, вызываемых оператором
newвнутреннего конструктора.
abstract_typeВыражение с 3 аргументами, которое определяет новый абстрактный тип. Аргументы такие же, как аргументы 1, 2 и 4 выражений
struct_type.примитивный_тип4-аргументное выражение, которое определяет новый примитивный тип. Аргументы 1, 2 и 4 такие же, как
struct_type. Аргумент 3 - это количество бит.Julia 1.5 struct_type,abstract_typeиprimitive_typeбыли удалены в Julia 1.5 и заменены вызовами новых встроенных функций.глобальныйОбъявляет глобальную привязку.
constОбъявляет (глобальную) переменную как константу.
новыйВыделяет новый объект, похожий на структуру. Первый аргумент - это тип. Псевдо-функция
newпреобразуется в это, и тип всегда вставляется компилятором. Это в значительной степени является внутренней функцией и не выполняет проверок. Оценка произвольных выраженийnewможет легко привести к ошибке сегментации.splatnewПохоже на
new, за исключением того, что значения полей передаются в виде одного кортежа. Работает аналогичноsplat(new), если быnewбыл функцией первого класса, отсюда и название.isdefinedExpr(:isdefined, :x)возвращает Bool, указывающий, было лиxуже определено в текущей области видимости.the_exceptionВозвращает пойманное исключение внутри блока
catch, как возвращаетсяjl_current_exception(ct).вводВходит в обработчик исключений (
setjmp).args[1]— это метка блока catch, к которому нужно перейти в случае ошибки. Возвращает токен, который будет использованpop_exception.leaveИзвлеките обработчики исключений.
args[1]— это количество обрабатывателей, которые нужно извлечь.pop_exceptionВосстановите стек текущих исключений до состояния, соответствующего
enter, при выходе из блока catch.args[1]содержит токен из связанногоenter.Julia 1.1 pop_exceptionновый в Julia 1.1.inboundsУправляет включением или отключением проверок границ. Поддерживается стек; если первый аргумент этого выражения истинный или ложный (
trueозначает, что проверки границ отключены), он помещается в стек. Если первый аргумент равен:pop, стек извлекается.boundscheckИмеет значение
false, если встроен в раздел кода, помеченный@inbounds, в противном случае имеет значениеtrue.loopinfoОтмечает конец цикла. Содержит метаданные, которые передаются в
LowerSimdLoop, чтобы либо отметить внутренний цикл выражения@simd, либо передать информацию в проходы циклов LLVM.copyastЧасть реализации квази-цитаты. Аргумент — это AST синтаксиса поверхности, который просто копируется рекурсивно и возвращается во время выполнения.
метаМетаданные.
args[1]обычно является символом, указывающим на вид метаданных, а остальные аргументы являются произвольными. Следующие виды метаданных обычно используются::inlineи:noinline: Подсказки для инлайнинга.
foreigncallСтатически вычисляемый контейнер для информации о
ccall. Поля:args[1]: имяВыражение, которое будет разобрано для внешней функции.
args[2]::Type: RTТип возвращаемого значения (в буквальном смысле), вычисляемый статически, когда был определен содержащий метод.
args[3]::SimpleVector(типы) : ATВектор (литеральный) типов аргументов, вычисляемый статически, когда был определен содержащий метод.
args[4]::Int: nreqКоличество необходимых аргументов для определения функции с переменным числом аргументов.
args[5]::QuoteNode{Symbol}: соглашение о вызовеКонвенция вызова для вызова.
args[6:5+length(args[3])]: аргументыЗначения для всех аргументов (с типами каждого, указанными в args[3]).
args[6+length(args[3])+1:end]: gc-корниДополнительные объекты, которые могут потребоваться для gc-rooted на время вызова. См. Working with LLVM для информации о том, откуда они происходят и как с ними работают.
new_opaque_closureСоздает новое непрозрачное замыкание. Поля:
args[1]: подписьПодпись функции непрозрачного замыкания. Непрозрачные замыкания не участвуют в диспетчеризации, но типы входных данных могут быть ограничены.
args[2]: isvaУказывает, принимает ли замыкание varargs.
args[3]: lbНижняя граница на тип вывода. (По умолчанию
Union{})args[4]: ubВерхняя граница на тип вывода. (По умолчанию
Any)args[5]: методФактический метод в виде выражения
opaque_closure_method.args[6:end]: захватываетЗначения, захваченные непрозрачным замыканием.
Julia 1.7 Непрозрачные замыкания были добавлены в Julia 1.7
Method
Уникальный контейнер, описывающий общие метаданные для одного метода.
имя,модуль,файл,строка,подписьМетаданные для уникальной идентификации метода как для компьютера, так и для человека.
амбигКэш других методов, которые могут быть неоднозначными с этим.
специализацииКэш всех созданных экземпляров MethodInstance для этого метода, используемый для обеспечения уникальности. Уникальность необходима для эффективности, особенно для инкрементальной предварительной компиляции и отслеживания недействительности метода.
sourceИсходный код (если доступен, обычно сжатый).
генераторВызываемый объект, который можно выполнить для получения специализированного исходного кода для конкретной сигнатуры метода.
корниУказатели на не-AST вещи, которые были интерполированы в AST, необходимые для сжатия AST, вывода типов или генерации нативного кода.
nargs,isva,called,is_for_opaque_closure,Описательные битовые поля для исходного кода этого метода.
primary_worldВозраст мира, который "владеет" этим методом.
MethodInstance
Уникальный контейнер, описывающий единую вызываемую сигнатуру для метода. Обратите внимание на Proper maintenance and care of multi-threading locks для получения важной информации о том, как безопасно изменять эти поля.
specTypesОсновной ключ для этого MethodInstance. Уникальность гарантируется через поиск
def.specializations.defМетод, который описывает эта функция, является специализацией. ИлиМодуль, если это верхнеуровневый Лямбда, расширенный в Модуле, и который не является частью Метода.sparam_valsЗначения статических параметров в
specTypes. ДляMethodInstanceвMethod.unspecializedэто пустойSimpleVector. Но для экземпляраMethodInstanceво время выполнения из кэшаMethodTableэто всегда будет определено и индексируемо.невыведенныйИсходный код верхнего уровня для thunk без сжатия. Кроме того, для сгенерированной функции это одно из многих мест, где может быть найден исходный код.
обратные ребраМы храним обратный список зависимостей кэша для эффективного отслеживания инкрементальной переанализации/перекомпиляции, которая может потребоваться после определения нового метода. Это работает за счет ведения списка других
MethodInstance, которые были выведены или оптимизированы для включения возможного вызова этогоMethodInstance. Эти результаты оптимизации могут храниться где-то вcache, или это мог быть результат чего-то, что мы не хотели кэшировать, например, распространения констант. Таким образом, мы объединяем все эти обратные связи с различными записями кэша здесь (почти всегда есть только одна применимая запись кэша с отправным значением для max_world).кэшКэш объектов
CodeInstance, которые используют эту инстанциацию шаблона.
CodeInstance
defMethodInstance, от которого происходит эта запись кэша.владелецТокен, который представляет владельца этого
CodeInstance. Будет использоватьсяjl_egalдля сопоставления.rettype/rettype_constВыведенный тип возвращаемого значения для поля
specFunctionObject, который (в большинстве случаев) также является вычисленным типом возвращаемого значения для функции в целом.выведенныйМожет содержать кэш выведенного источника для этой функции, или может быть установлен в
nothing, чтобы просто указать, чтоrettypeвыведен.ftprУниверсальная точка входа jlcall.
jlcall_apiABI, который следует использовать при вызове
fptr. Некоторые значимые из них включают:- 0 - Еще не скомпилировано
- 1 -
JL_CALLABLEjl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs) - 2 - Константа (значение, хранящееся в
rettype_const) - 3 - С переданными статическими параметрами
jl_value_t *(*)(jl_svec_t *sparams, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs) - 4 - Запустите в интерпретаторе
jl_value_t *(*)(jl_method_instance_t *meth, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)
min_world/max_worldДиапазон мировых возрастов, для которых этот экземпляр метода может быть вызван. Если max_world - это специальное значение токена
-1, значение еще не известно. Он может продолжать использоваться, пока мы не столкнемся с обратной связью, которая требует от нас пересмотра.
CodeInfo
Временный контейнер для хранения пониженного исходного кода.
кодМассив утверждений
AnyslotnamesМассив символов, дающий имена для каждого слота (аргумента или локальной переменной).
slotflagsМассив
UInt8свойств слотов, представленный в виде битовых флагов:- 0x02 - назначен (только ложь, если нет операторов присваивания с этой переменной слева)
- 0x08 - использован (если есть чтение или запись слота)
- 0x10 - статически назначено один раз
- 0x20 - может быть использован до присвоения. Этот флаг действителен только после вывода типа.
ssavaluetypesЛибо массив, либо
Int.Если это
Int, он указывает количество временных мест, вставленных компилятором, в функции (длина массиваcode). Если это массив, он указывает тип для каждого места.ssaflagsФлаги на уровне операторов 32 бита для каждого выражения в функции. См. определение
jl_code_info_tв julia.h для получения дополнительной информации.linetableМассив объектов местоположения источника
codelocsМассив целочисленных индексов в
linetable, указывающий местоположение, связанное с каждым утверждением.
Дополнительные поля:
slottypesМассив типов для слотов.
rettypeВыведенный тип возвращаемого значения пониженнной формы (IR). Значение по умолчанию —
Any.method_for_inference_limit_heuristicsМетод
method_for_inference_heuristicsрасширит генератор данного метода, если это необходимо во время вывода.родительMethodInstance, который "владеет" этим объектом (если применимо).edgesПеренаправьте ссылки на экземпляры методов, которые необходимо аннулировать.
min_world/max_worldДиапазон мировых возрастов, для которых этот код был действителен в то время, когда он был выведен.
Булевы свойства:
выведенныйБудет ли это произведено с помощью вывода типов.
inlineableДолжно ли это быть допустимо для инлайнинга.
propagate_inboundsСледует ли это распространять
@inbounds, когда оно встроено с целью исключения блоков@boundscheck.
UInt8 настройки:
constprop- 0 = использовать эвристику
- 1 = агрессивный
- 2 = none
purityСостоит из 5 битовых флагов:- 0x01 << 0 = этот метод гарантированно вернет или завершится последовательно (
:consistent) - 0x01 << 1 = этот метод свободен от внешне семантически видимых побочных эффектов (
:effect_free) - 0x01 << 2 = этот метод гарантированно не вызовет исключение (
:nothrow) - 0x01 << 3 = этот метод гарантированно завершится (
:terminates_globally) - 0x01 << 4 = синтаксический контроль потока внутри этого метода гарантированно завершится (
:terminates_locally)
Смотрите документацию
Base.@assume_effectsдля получения дополнительных сведений.- 0x01 << 0 = этот метод гарантированно вернет или завершится последовательно (