Абстрактные синтаксические деревья (AST) в Julia
В Julia есть два представления кода. Сначала идет дерево AST поверхностного синтаксиса, возвращаемое анализатором (например, функцией Meta.parse) и управляемое макросами. Это структурированное представление кода в том виде, в котором он написан, построенное с помощью julia-parser.scm из потока символов. Далее идет пониженная форма, или IR (промежуточное представление), которая используется при выводе типов и генерации кода. В этой форме меньше типов узлов, все макросы развернуты, а весь порядок управления преобразован в явные ветви и последовательности операторов. Она строится с помощью julia-syntax.scm.
Сначала мы рассмотрим дерево AST, поскольку оно необходимо для написания макросов.
AST поверхностного синтаксиса
Интерфейсные AST почти полностью состоят из выражений (Expr) и небольших единиц (например, символов, чисел). Как правило, для каждой визуально отличимой синтаксической формы существует своя вершина выражения. Примеры будут приведены в синтаксисе s-выражения. Каждый заключенный в скобки список соответствует выражению, где первый элемент является вершиной. Например, (call f x) соответствует Expr(:call, :f, :x) в Julia.
Вызовы
| Ввод | AST |
|---|---|
|
|
|
|
|
|
|
|
синтаксис do:
f(x) do a,b
body
end
анализируется как (do (call f x) (-> (tuple a b) (block body))).
Операторы
Чаще всего операторы используются просто для вызовов функций, поэтому они анализируются с помощью вызова (call) вершины. Однако некоторые операторы представляют собой специальные формы (необязательно вызовы функций), и в этих случаях сам оператор является вершиной выражения. В julia-parser.scm они называются «синтаксическими операторами». Некоторые операторы (+ и *) используют N-арный анализ. Цепочки вызовов анализируются как один N-аргументный вызов. Наконец, цепочки сравнений имеют свою особую структуру выражения.
| Ввод | AST |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Формы, заключенные в квадратные скобки
| Ввод | AST |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Макросы
| Ввод | AST |
|---|---|
|
|
|
|
|
|
Строки
| Ввод | AST |
|---|---|
|
|
|
|
|
|
|
|
$ |
|
Синтаксис строк документации:
"some docs"
f(x) = x
анализируется как (macrocall (|.| Core '@doc) (line) "some docs" (= (call f x) (block x))).
Импорт и прочее
| Ввод | AST |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using имеет такое же представление, как и import, но с вершиной выражения :using вместо :import.
Числа
Julia поддерживает больше числовых типов, чем многие реализации схем, поэтому не все числа представлены в AST непосредственно как числа схемы.
| Ввод | AST |
|---|---|
|
|
|
|
|
|
Формы блоков
Блок операторов анализируется как (block stmt1 stmt2 ...).
Оператор if:
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 condition body).
Цикл 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, значит последний аргумент отсутствует.
Выражения с кавычками
Формы синтаксиса исходного текста Julia для заключения кода в кавычки (quote и :( )) поддерживают интерполяцию с $. В терминологии Lisp это означает, что на самом деле они являются формами использования обратных кавычек. На внутреннем уровне также существует необходимость заключения кода в кавычки без интерполяции. В коде схемы Julia неинтерполирующая кавычка представлена вершиной выражения inert.
Выражения inert преобразуются в объекты Julia QuoteNode. Эти объекты инкапсулируют одно значение любого типа и при оценке просто возвращают его.
Выражение quote, аргументом которого является небольшой элемент, также преобразуется в QuoteNode.
Номера строк
Информация о расположении исходного кода представлена в виде (line line_num file_name), где третий компонент является необязательным (и опускается, когда меняется номер текущей строки, но не имя файла).
Эти выражения представлены в Julia как LineNumberNode.
Макросы
Гигиена макроса представлена через пару вершин выражения escape и hygienic-scope. Результат расширения макроса автоматически заключается в (hygienic-scope block module), чтобы представить результат новой области. Пользователь может вставить (escape block) внутрь, чтобы интерполировать код от вызывающей стороны.
Пониженная форма
Пониженная форма (IR) более важна для компилятора, поскольку она используется для вывода типов, оптимизации, такой как встраивание, и генерации кода. Она также менее очевидна для человека, так как является результатом значительного переупорядочения входного синтаксиса.
Помимо символов (Symbol) и некоторых числовых типов, в пониженной форме существуют следующие типы данных.
-
ExprИмеет тип узла, указанный полемhead, и полеargs, которое представляет собойVector{Any}подвыражений . Тогда как почти каждая часть поверхностного AST представлена типомExpr, IR использует только ограниченное количествоExprи в основном только для вызовов и некоторых форм верхнего уровня. -
SlotNumberИдентифицирует аргументы и локальные переменные с помощью последовательной нумерации. Имеет полеidс целочисленным значением, представляющим индекс слота. Типы этих слотов можно найти в полеslottypesих объектаCodeInfo. -
ArgumentТо же, что иSlotNumber, но появляется только после оптимизации. Указывает, что слот , на который дана ссылка, является аргументом включающей функции. -
CodeInfoОборачивает IR группы операторов. Его полеcodeпредставляет собой массив выражений для выполнения. -
GotoNodeБезусловная ветвь. Аргументом является целевой объект ветви, представленный в виде индекса в массиве кода , к которому нужно перейти. -
GotoIfNotУсловная ветвь. Если полеcondимеет значение false, переходит к индексу, определенному полемdest. -
ReturnNodeВозвращает свой аргумент (полеval) как значение включающей функции. Если полеvalне определено, представляет недоступный оператор. -
QuoteNodeОборачивает произвольное значение, на которое как на данные будет указывать ссылка. Например, функцияf() = :aсодержитQuoteNode, полемvalueкоторого является символa, чтобы возвращать сам символ, а не определять его. -
GlobalRefОтносится к глобальной переменнойnameв модулеmod. -
SSAValueОтносится к последовательно пронумерованной (начиная с 1) статической переменной одиночного присваивания (SSA), вставленной компилятором. Номер (id)SSAValue— это индекс массива кода выражения, значение которого он представляет. -
NewvarNodeОтмечает точку, в которой создается переменная (слот). Это приводит к сбросу переменной до неопределенной.
Типы Expr
Эти символы отображаются в поле head выражений (Expr) в пониженной форме.
-
callВызов функции (динамическая диспетчеризация).args[1]— это вызываемая функция,args[2:end]— это аргументы. -
invokeВызов функции (статическая диспетчеризация).args[1]— это вызываемый MethodInstance,args[2:end]— это аргументы (включая вызываемую функцию сargs[2]). -
static_parameterУказывает на статический параметр по индексу. -
=Присваивание. В IR первым аргументом всегда являетсяSlotNumberилиGlobalRef. -
methodДобавляет метод к универсальной функции и при необходимости присваивает результат. Имеет одноаргументную форму и трехаргументную форму. Источником формы с одним аргументом является синтаксисfunction foo end. В одноаргументной форме аргумент является символом. Если этот символ уже именует функцию в текущей области, ничего не происходит. Если символ не определен, создается новая функция и присваивается идентификатору, указанному символом. Если символ определен, но именует не функцию, возникает ошибка. Определение «именует функцию» заключается в том, что привязка является постоянной, и ссылается на объект одинарного типа. Это объясняется тем, что экземпляр одинарного типа идентифицирует тип, к которому нужно добавить метод. Когда у типа есть поля, будет неясно, добавляется ли метод к экземпляру или его типу. Трехаргументная форма имеет следующие аргументы:* `args[1]` Имя функции или `nothing`, если неизвестно или не требуется. Если символ, то выражение сначала ведет себя как одноаргументная форма выше. В дальнейшем этот аргумент игнорируется. Может быть `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Семиаргументное выражение, которое определяет новую структуру (struct).* `args[1]` Имя структуры `struct`. * `args[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]` Логический, имеет значение true, если является изменяемым (`mutable`). * `args[7]` Количество аргументов для инициализации. Это будет число полей или минимальное число полей, вызываемых оператором `new` внутреннего конструктора.
-
abstract_typeТрехаргументное выражение, которое определяет новый абстрактный тип. Аргументы совпадают с аргументами 1, 2 и 4 выраженийstruct_type. -
primitive_typeЧетырехаргументное выражение, которое определяет новый примитивный тип. Аргументы 1, 2 и 4 такие же, как вstruct_type. Аргумент 3 — это количество битов.
struct_type, abstract_type и primitive_type были удалены в Julia 1.5 и заменены вызовами новых встроенных объектов.
-
globalОбъявляет глобальную привязку. -
constОбъявляет (глобальную) переменную как константу. -
newВыделяет новый структуроподобный объект. Первый аргумент — это тип. Уровень псевдофункцииnewпонижается до этого, а тип всегда вставляется компилятором. Это в значительной степени только внутренняя функция, которая не выполняет проверку. Определение произвольных выраженийnewможет легко привести к сбою. -
splatnewАналогиченnew, за исключением того, что значения полей передаются в виде одного кортежа. Работает так же, какsplat(new), если быnewбыла функцией первого класса. Отсюда и имя. -
isdefinedExpr(:isdefined, :x)возвращает логическое значение, указывающее, был лиxуже определен в текущей области видимости. -
the_exceptionВыдает перехваченное исключение внутри блокаcatchкак возвращенноеjl_current_exception(ct). -
enterВводит обработчик событий (setjmp).args[1]— это метка блока catch, в который нужно перейти при ошибке . Выдает токен, используемыйpop_exception. -
leaveВозвращает обработчики исключений.args[1]— это количество обработчиков, которое нужно возвратить. -
pop_exceptionВозвращает состояние текущих исключений к состоянию в связанном аргументеenterпри выходе из блока catch.args[1]содержит токен из связанного аргументаenter.
Аргумент pop_exception является новым в Julia 1.1.
-
inboundsУправляет включением или отключением проверки границ. Ведется стек. Если первый аргумент этого выражения имеет значение true или false (trueозначает, что проверка границ отключена), он отправляется в стек. Если первый аргумент —:pop, стек извлекается. -
boundscheckИмеет значениеfalse, если вставлен в участок кода, помеченный с помощью макроса@inbounds, в противном случае имеет значениеtrue. -
loopinfoПомечает конец цикла. Содержит метаданные, которые передаются вLowerSimdLoop, чтобы либо пометить внутренний цикл выражения@simd, либо распространить информацию в передачи цикла LLVM. -
copyastЧасть реализации квазикавычки. Аргументом является поверхностный синтаксис AST, который просто рекурсивно копируется и возвращается во время выполнения. -
metaМетаданные.args[1]обычно представляет собой символ, указывающий тип метаданных, а остальные аргументы имеют свободную форму. Обычно используются следующие типы метаданных.* `:inline` and `:noinline`: Inlining hints.
-
foreigncallСтатически вычисляемый контейнер для информации по ключевому словуccall. Используются следующие поля.* `args[1]` : name Выражение, которое будет проанализировано для внешней функции. * `args[2]::Type` : RT (Литеральный) возвращаемый тип, вычисленный статически, когда был определен содержащий метод. * `args[3]::SimpleVector` (of Types) : AT (Литеральный) вектор типов аргументов, вычисленный статически, когда был определен содержащий метод. * `args[4]::Int` : nreq Количество необходимых аргументов для определения функции с переменным количеством аргументов. * `args[5]::QuoteNode{<:Union{Symbol,Tuple{Symbol,UInt16}, Tuple{Symbol,UInt16,Bool}}`: calling convention Соглашение о вызове функции, опционально с указанием эффектов и параметра `gc_safe` (безопасное выполнение одновременно со сборщиком мусора). * `args[6:5+length(args[3])]` : arguments Значения для всех аргументов (типы каждого из них указаны в args[3]). * `args[6+length(args[3])+1:end]` : gc-roots Дополнительные объекты, которым может потребоваться предоставить права суперпользователя при сборке мусора на время вызова. Сведения об источнике этих объектов и способе их обработки см. в главе [Работа с LLVM](@ref Working-with-LLVM). -
new_opaque_closureСоздает новое непрозрачное замыкание. Используются следующие поля.* `args[1]` : signature Сигнатура функции для непрозрачного замыкания. Непрозрачные замыкания не используются в диспетчеризации, но входные типы могут быть ограничены. * `args[2]` : lb Нижняя граница для типа вывода. (По умолчанию имеет значение `Union{}`) * `args[3]` : ub Верхняя граница для типа вывода. (По умолчанию имеет значение `Any`) * `args[4]` : constprop Указывает, можно ли использовать тождественность непрозрачного замыкания для распространения константы. Макрос `@opaque` включает это по умолчанию, но это вызовет дополнительный вывод, который может быть нежелательным и препятствует выполнению кода во время предварительной компиляции. Если `args[4]` является методом, аргумент считается пропущенным. * `args[5]` : method Фактический метод как выражение `opaque_closure_method`. * `args[6:end]` : captures Значения, записываемые непрозрачным замыканием.Совместимость: Julia 1.7Непрозрачные замыкания были добавлены в Julia 1.7.
Method
Уникальный контейнер, описывающий общие метаданные для одного метода.
-
name,module,file,line,sigМетаданные для уникального определения метода для компьютера и человека. -
ambigКэш других методов, которые могут быть неоднозначными с этим. -
specializationsКэш всех MethodInstance, когда-либо созданных для этого Method, используемых для обеспечения уникальности. Уникальность необходима для обеспечения эффективности, особенно для добавочной предварительной компиляции и отслеживания недействительности методов. -
sourceОригинальный исходный код (если доступен, обычно в сжатом виде). -
generatorВызываемый объект, который может быть выполнен для получения специализированного источника для определенной сигнатуры метода. -
rootsУказатели на объекты, не относящиеся к AST, которые были интерполированы в AST, требуемые при сжатии AST, выводе типов или генерации собственного кода. -
nargs,isva,called,is_for_opaque_closure, Описательные битовые поля для исходного кода данного Method. -
primary_world«Возраст мира» (иерархия определения методов) для этого Method.
MethodInstance
Уникальный контейнер, описывающий единичную вызываемую сигнатуру для Method. Важные сведения о безопасном изменении этих полей см. в разделе Надлежащее обслуживание многопоточных блокировок.
-
specTypesПервичный ключ для этого MethodInstance. Уникальность гарантируется с помощью поискаdef.specializations. -
defМетод (Method), специализацию которого описывает эта функция. Или модуль (Module), если это лямбда верхнего уровня, расширенная в Module и которая не является частью Method. -
sparam_valsЗначения статических параметров вspecTypes. Для контейнераMethodInstanceвMethod.unspecializedэто пустой векторSimpleVector. Но для контейнераMethodInstanceсреды выполнения из кэшаMethodTableэто значение всегда будет определенным и индексируемым. -
backedgesМы храним обратный список зависимостей кэша для эффективного отслеживания работы по добавочному повторному анализу или перекомпиляции, которые могут потребоваться после определений нового метода. Для этого мы храним список других контейнеровMethodInstance, которые были выведены или оптимизированы, чтобы содержать возможный вызов этого контейнераMethodInstance. Результаты оптимизации могут храниться где-то в кэше (cache), либо это может быть результат чего-то, что не требуется кэшировать, например распространение констант. Таким образом, мы объединяем все эти сведения в различные записи кэша (почти всегда есть только одна применимая запись кэша с контрольным значением для max_world). -
cacheКэш объектовCodeInstance, для которых этот экземпляр шаблона является общим.
CodeInstance
-
defКонтейнерMethodInstance, из которого получена эта запись кэша. -
ownerТокен, представляющий владельца этогоCodeInstance. Будет использоватьjl_egalдля соответствия. -
rettype/rettype_constВыводимый возвращаемый тип для поляspecFunctionObject, который (в большинстве случаев) является также вычисляемым возвращаемым типом для функции в целом. -
inferredМожет содержать кэш выводимого исходного кода для этой функции, или может иметь значениеnothing, чтобы просто указывать, что ничего не выводится (rettype). -
ftprУниверсальная точка входа jlcall. -
jlcall_apiИспользуемый ABI при вызовеfptr. Ниже приведены некоторые важные значения.* 0 - Not compiled yet * 1 - `JL_CALLABLE` `jl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` * 2 - Constant (value stored in `rettype_const`) * 3 - With Static-parameters forwarded `jl_value_t *(*)(jl_svec_t *sparams, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` * 4 - Run in interpreter `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, его значение еще не известно. Его можно продолжать использовать до тех пор, пока не появятся сведения, которые потребуют пересмотра. -
Поля времени
-
time_infer_total: Общая стоимость вычисленияinferred, изначально выраженная в виде времени выполнения от начала до конца. -
time_infer_cache_saved: Стоимость, сэкономленная по сравнению сtime_infer_totalза счет кэширования. Добавление этого значения кtime_infer_totalдолжно дать стабильную оценку для сравнения стоимости двух реализаций или одной реализации во времени. Как правило, это завышенная оценка времени, необходимого для вывода чего-либо, поскольку кэш часто эффективно справляется с повторяющейся работой. -
time_infer_self: Собственная стоимость вывода Julia дляinferred(частьtime_infer_total). Это просто дополнительная стоимость компиляции этого одного метода, если предоставлен полностью заполненный кэш всех целевых вызовов, включая постоянные результаты вывода и результаты LimitedAccuracy, которые обычно не находятся в кэше. -
time_compile: Собственная стоимость JIT-компиляции LLVM (например, вычислениеinvokeизinferred). Общая оценка стоимости может быть вычислена путем обхода всего содержимогоedgesи суммирования полученных значений с учетом циклов и дубликатов. (Это поле в настоящее время не включает измеренное время AOT-компиляции.)
-
CodeInfo
(Обычно временный) контейнер для хранения сведенного (и, возможно, подразумеваемого) исходного кода.
-
codeМассивAnyоператоров. -
slotnamesМассив символов, задающих имена для каждого слота (аргумента или локальной переменной). -
slotflagsМассивUInt8свойств слотов, представленных в виде битовых флагов:* 0x02 - assigned (only false if there are *no* assignment statements with this var on the left) * 0x08 - used (if there is any read or write of the slot) * 0x10 - statically assigned once * 0x20 - might be used before assigned. This flag is only valid after type inference.
-
ssavaluetypesЛюбой массив или целое число (Int). Если целое число (Int), задает количество вставленных компилятором временных расположений в функции (длина массиваcode). Если массив, задает тип для каждого расположения. -
ssaflags32-битные флаги уровня оператора для каждого выражения в функции. Дополнительные сведения см. в определенииjl_code_info_tв файле julia.h.
Эти данные заполняются только после вывода (или сгенерированными функциями в некоторых случаях):
-
debuginfoОбъект для получения информации об исходном коде для каждого оператора, см. Как интерпретировать номера строк в объектеCodeInfo. -
rettypeПредполагаемый тип возвращаемого значения в пониженной форме (IR). Значение по умолчанию —Any. Это в основном для удобства, поскольку (из-за особенностей работы OpaqueClosures) это не обязательно тот тип возвращаемого значения, который используется генератором кода. -
method_for_inference_limit_heuristicsmethod_for_inference_heuristicsрасширяет генератор данного метода, если это необходимо во время вывода. -
parentКонтейнерMethodInstance, который «владеет» этим объектом (если применимо). -
edgesПередача ребер в экземпляры метода, которые должны быть сделаны недействительными. -
min_world/max_worldДиапазон «возрастов мира» (иерархий определений методов), для которых этот код был допустим в момент его вывода.
Необязательные поля:
-
slottypesМассив типов для слотов. -
method_for_inference_limit_heuristicsmethod_for_inference_heuristicsрасширит генератор заданного метода, если это необходимо во время вывода.
Логические свойства:
-
propagate_inboundsСледует ли распространять@inboundsпри встраивании с целью пропуска блоков@boundscheck.
Параметры UInt8:
-
constprop,inlineable-
0 — использовать эвристику
-
1 — агрессивный режим
-
2 — нет
-
-
purityСостоит из 5-битовых флагов:-
0x01 << 0— этот метод гарантированно возвращает управление или согласованно завершает выполнение (:consistent) -
0x01 << 1— этот метод лишен внешне семантически видимых побочных эффектов (:effect_free) -
0x01 << 2— этот метод гарантированно не вызывает исключение (:nothrow) -
0x01 << 3— этот метод гарантированно завершает выполнение (:terminates_globally) -
0x01 << 4— синтаксический порядок выполнения внутри этого метода гарантированно завершает выполнение (:terminates_locally) Дополнительные сведения см. в документации поBase.@assume_effects.
-
Интерпретация номеров строк в объекте CodeInfo
Существует два распространенных формата этих данных: один используется внутри системы и немного сжимает данные, а другой используется компилятором. Они содержат одну и ту же основную информацию, но версия для компилятора является изменяемой, в то время как версия, используемая внутри системы, — нет.
Многие потребители могут вызывать Base.IRShow.buildLineInfoNode, Base.IRShow.append_scopes! или Stacktraces.lookup(::InterpreterIP), чтобы избежать необходимости (повторно) реализовывать эти объекты.
Вот определения каждой из этих структур:
struct Core.DebugInfo
@noinline
def::Union{Method,MethodInstance,Symbol}
linetable::Union{Nothing,DebugInfo}
edges::SimpleVector{DebugInfo}
codelocs::String # сжатые данные
end
mutable struct Core.Compiler.DebugInfoStream
def::Union{Method,MethodInstance,Symbol}
linetable::Union{Nothing,DebugInfo}
edges::Vector{DebugInfo}
firstline::Int32 # начальная строка блока (обозначается индексом 0)
codelocs::Vector{Int32} # для каждого оператора:
# индекс в таблице строк (если определен), в противном случае номер строки (в файле, представленном def)
# затем индекс в edges
# затем индекс в edges[linetable]
end
-
def: где был определен этот объектDebugInfo(например,Method,MethodInstanceилиSymbolв области файла) -
linetable
Другой объект `DebugInfo`, от которого произошел данный объект и который содержит фактические номера строк, так что в данном объекте DebugInfo содержатся только индексы. Позволяет избежать создания копий, а также отслеживать, как каждый отдельный оператор был преобразован из исходного кода в оптимизированную форму, а не только отдельные номера строк. Если `def` не является символом, то этот объект заменяет текущий объект функции для получения метаданных о том, какая функция выполняется в принципе (например, кассетные преобразования). Значения `codelocs`, описанные ниже, также интерпретируются как индекс в `codelocs` в этом объекте, а не как номер строки.
-
edges: вектор уникальных значений DebugInfo для каждой функции, встроенной в этот код (которые рекурсивно содержат ребра для всего, что в них встроено). -
firstline(при распаковке в DebugInfoStream)
Номер строки, связанный с оператором `begin` (или другим ключевым словом, таким как `function` или `quote`), который определяет, где «начинается» это определение кода.
-
codelocs(при распаковке вDebugInfoStream)
Вектор индексов с тремя значениями для каждого оператора в IR и еще одним для начальной точки блока, которые описывают трассировку стека от этой точки: 1. Целочисленный индекс в поле `linetable.codelocs`, указывающий на исходное расположение, связанное с каждым оператором (включая его синтаксические ребра), или ноль, что указывает на отсутствие изменений в номере строки по сравнению с ранее выполненным оператором (который не обязательно является синтаксически или лексически предшествующим), или сам номер строки, если поле `linetable` имеет значение `nothing`. 2. Целочисленный индекс в `edges`, указывающий на встроенный объект `DebugInfo`, или ноль, если нет ребер. 3. (Если элемент 2 не равен нулю) целочисленный индекс в `edges[].codelocs`, который следует интерпретировать рекурсивно для каждой функции в стеке встраивания, или ноль, означающий, что в качестве номера строки следует использовать `edges[].firstline`. Специальные коды:
-
(zero, zero, *): номер строки или ребра остаются неизменными по сравнению с предыдущим оператором (можно интерпретировать как синтаксически, так и лексически). Глубина встраивания также могла измениться, хотя большинству вызывающих объектов следует игнорировать это. -
(zero, non-zero, *): нет номера строки, только ребра (обычно из-за расширения макроса в код верхнего уровня).