Metadata
Metadata is a way to attach additional information to various entities in LLVM IR. Various metadata object types exist, each prefixed with MD
, and with a variety of APIs to query and manipulate them. The abstract supertype of all metadata objects is Metadata
.
Metadata strings can be constructed with the MDString
constructor, and converted back to Julia strings with a convert
call:
julia> md = MDString("hello")
!"hello"
julia> convert(String, md)
"hello"
Then there are metadata nodes, which can have other metadata as operands. For example, a tuple of metadata nodes can be created with the following MDNode
constructor:
julia> md = MDNode([MDString("hello"), MDString("world")])
<0x6000022c7f20> = !{!"hello", !"world"}
These operands can be extracted again using the operands
function:
julia> operands(md)
2-element Vector{MDString}:
!"hello"
!"world"
Converting between Metadata and Value
It happens often that you want to put values in metadata, or use metadata with APIs that expect a value. In LLVM.jl, this is possible by using the Value
and Metadata
constructors with respectively Metadata
and Value
inputs, automatically wrapping them in the correct type:
julia> val = ConstantInt(42);
julia> md = Metadata(val)
i64 42
julia> typeof(md)
LLVM.ConstantAsMetadata
julia> md = MDString("test");
julia> val = Value(md)
!"test"
julia> typeof(val)
LLVM.MetadataAsValue
Inspecting and attaching
Any global value and instruction can have metadata attached to it. In LLVM.jl, it's possible to inspect and mutate that metadata using the metadata
function:
julia> inst
%2 = add i64 %1, %0
julia> isempty(metadata(inst))
true
julia> metadata(inst)["dbg"] = MDNode([MDString("hello")])
<0x5ff68c3f2f28> = !{!"hello"}
julia> inst
%2 = add i64 %1, %0, !dbg !0
Metadata can also be attached to a module, in which case it needs to be grouped in a named metadata node (which can only contain other metadata nodes, and not e.g. strings directly). With LLVM's C API, module-level named metadata is append-only, which is done using the push!
function:
julia> md = metadata(mod);
julia> isempty(md)
true
julia> push!(md["hello"], MDNode([MDString("world")]));
julia> md
ModuleMetadataIterator for module :
!hello = !{<0x60000394d3f8> = !{!"world"}}
Debug information
When generating IR, it is possible to add debug information metadata to the generated code. This information can be used by debuggers to provide a better debugging experience.
LLVM.jl currently does not offer extensive wrappers for generating debug info, and is mostly focussed on the ability to inspect or copy existing information.
LLVM represents debug information as a variety of DI
-prefixed structures, which are subtypes of the above metadata types. In LLVM.jl, various functions are provided to inspect properties of these structures:
DILocation
:line
,column
,scope
,inlined_at
DIVariable
:file
,scope
,line
DIScope
:file
,name
DIFile
:directory
,filename
,source
DIType
:name
,sizeof
,offset
,line
,flags
DISubProgram
:line
(and methods inherited fromDIScope
)
To query the debug info attached to an instruction, one queries the !dbg
metadata using metadata(inst)["dbg"]
.
julia> inst
%2 = add i64 %1, %0, !dbg !9
julia> dbg = metadata(inst)["dbg"]
!DILocation(line: 87, scope: <0x6000056c5dd0>) = !DILocation(line: 87, scope: <0x6000056c5dd0>)
julia> line(dbg)
87
julia> file(scope(dbg))
<0x6000000beb80> = !DIFile(filename: "int.jl", directory: ".")
Debug info can also be attached to functions, which can be queried and modified using respectively subprogram
and subprogram!
:
julia> sp = subprogram(add)
<0x600003edfad0> = distinct !DISubprogram(name: "+", linkageName: "julia_+", scope: null, file: <0x600003ba6fe0>, line: 87, type: <0x600003494c90>, scopeLine: 87, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: <0x6000021d8428>, retainedNodes: <0x6000010f09d0>)