Values
Values are the basic building blocks of a program. They are the simplest form of data that can be manipulated by a program. Many things in LLVM are considered a value: not only constants, but also instructions, functions, etc.
General APIs
The Value type is the abstract type that represents all values in LLVM. It supports a range of general APIs that are common to all values:
val.value_type: the type of the value.val.name: the name of the value, which can also be assigned to.context(val): the context in which the value was created.take_name!(val, from): givevalthe name offrom, which becomes unnamed. Assigning the name instead would make LLVM add a suffix, asfromstill uses it.strip_pointer_casts(val): the value behind any bitcasts, address space casts andgetelementptrs with all-zero indices, like C++'sValue::stripPointerCasts.strip_pointer_casts_and_aliasesalso looks through global aliases.
User values
A User is a value that can have other values as operands. It is the base type for instructions, functions, and other values that are composed of other values. It supports a few additional APIs:
user.operands: the operands of the user, as a mutable view: assigning to an element,user.operands[i] = val, replaces that operand.
Constant values
Many values are actually constant, i.e., they are known to be immutable at run time. Constant numbers are examples of constants, but also functions and global variables, because their address is immutable.
It is possible to quickly create all-zeros and all-ones constants using the null and all_ones functions:
julia> null(LLVM.Int1Type())
i1 false
julia> all_ones(LLVM.FloatType())
float 0xFFFFFFFFE0000000Constant data
There are several kinds of constant data that can be represented in LLVM. Singleton constants, which include null, undef, and poison values, can be created using constructors that take a single type as argument:
julia> PointerNull(LLVM.PointerType(LLVM.Int1Type()))
ptr null
julia> UndefValue(LLVM.Int1Type())
i1 undef
julia> PoisonValue(LLVM.Int1Type())
i1 poisonConstant numbers can be created by passing a type and a value, or simply a value in which case the Julia type will be mapped to the corresponding LLVM type:
julia> ConstantInt(LLVM.Int1Type(), 1)
i1 true
julia> ConstantInt(true)
i1 true
julia> ConstantFP(LLVM.FloatType(), 1.0)
float 1.000000e+00
julia> ConstantFP(1.0f0)
float 1.000000e+00It is possible to extract the value of a constant using the convert function:
julia> c = ConstantFP(Float16(1))
half 0xH3C00
julia> convert(Float16, c)
Float16(1.0)Floating-point constants pass through a Float64, which cannot represent every value of wider types like fp128 or x86_fp80. To create such constants exactly, pass their bit pattern instead, and use the bitpattern property to get it back:
julia> ConstantFP(LLVM.FP128Type(), 0.1) # rounded to Float64 precision
fp128 0xLA0000000000000003FFB999999999999
julia> c = ConstantFP(LLVM.FP128Type(); bits=0x3ffb999999999999999999999999999a)
fp128 0xL999999999999999A3FFB999999999999
julia> c.bitpattern
0x3ffb999999999999999999999999999aConstant structures can be created using the ConstantStruct constructor:
julia> ty = LLVM.StructType([LLVM.Int32Type()])
{ i32 }
julia> ConstantStruct(ty, [LLVM.ConstantInt(Int32(42))])
{ i32 } { i32 42 }
julia> # short-hand where the LLVM type is inferred
ConstantStruct([LLVM.ConstantInt(Int32(42))])
{ i32 } { i32 42 }Sequential constants, i.e., arrays and vectors, can be created using the ConstantDataArray and ConstantDataVector constructors, which again supports the shorthand of only passing Julia values:
julia> ConstantDataArray(LLVM.Int32Type(), Int32[1, 2])
[2 x i32] [i32 1, i32 2]
julia> # short-hand where the LLVM type is inferred
ConstantDataArray(Int32[1, 2])
[2 x i32] [i32 1, i32 2]While ConstantDataArray only supports simple element types, ConstantArray supports arbitrary aggregates as elements:
julia> val = ConstantStruct([LLVM.ConstantInt(Int32(42))])
{ i32 } { i32 42 }
julia> ty = val.value_type
{ i32 }
julia> ConstantArray(ty, [val])
[1 x { i32 }] [{ i32 } { i32 42 }]The elements of aggregate constants, including ConstantDataArrays and zeroinitializers (which LLVM uses to represent simple and zero data), are available as the elements property, a read-only vector of constants:
julia> arr = ConstantArray([1, 2])
[2 x i64] [i64 1, i64 2]
julia> length(arr.elements)
2
julia> arr.elements[1]
i64 1
julia> ConstantArray([0, 0]).elements
2-element LLVM.ConstantAggregateElementSet:
i64 0
i64 0Code that has to handle any constant, like a port of LLVM's constant folding, can use aggregate_element instead, which returns the element at a 0-based index or nothing if LLVM doesn't know it, e.g., for constant expressions:
julia> aggregate_element(ConstantArray([1, 2]), 1)
i64 2
julia> aggregate_element(ConstantArray([1, 2]), 2) === nothing
trueConstant expressions
Constant expressions are a way to represent computations that are known at compile time. Their support in LLVM is diminishing, and their results are often constant-folded to other constants, but the ones that remain can be constructed with const_-prefixed functions in LLVM.jl:
julia> const_neg(ConstantInt(1))
i64 -1
julia> const_inttoptr(ConstantInt(42), LLVM.PointerType(LLVM.Int1Type()))
ptr inttoptr (i64 42 to ptr)For the exact list of supported constant expressions, refer to the LLVM documentation.
A single constant expression can also be turned into an equivalent instruction with Instruction(ce), which is not part of any basic block until it is inserted with move!, or directly at a given position with Instruction(LLVM.before(inst), ce). In both cases the uses of the constant expression are left to the caller to replace.
Constant expressions (and constant aggregates) that wrap a given set of constants can be rewritten into equivalent instructions at each point of use, using convert_users_to_instructions!. This is useful when a constant needs to be replaced with a function-local value, which is not a valid operand of a constant expression. For example, given a global variable that is used through a getelementptr constant expression:
define i32 @getelem() {
entry:
%0 = load i32, ptr getelementptr inbounds ([4 x i32], ptr @myglobal, i32 0, i32 2), align 4
ret i32 %0
}calling convert_users_to_instructions!([myglobal]) materializes the constant expression as a regular instruction:
define i32 @getelem() {
entry:
%0 = getelementptr inbounds [4 x i32], ptr @myglobal, i32 0, i32 2
%1 = load i32, ptr %0, align 4
ret i32 %1
}Operands of phi nodes are materialized in their respective incoming blocks. The function returns whether anything was changed, and by default removes the constants that became dead as a result of the rewrite.
Inline assembly
Inline assembly is a way to include raw assembly code in a program. It is often used to access features that are not directly supported by LLVM, or to optimize specific parts of a program.
The InlineAsm constructor takes a function type, assembly string, constraints string, and a boolean indicating whether the assembly has side effects:
julia> InlineAsm(LLVM.FunctionType(LLVM.VoidType()), "nop", "", false)
ptr asm "nop", ""For more details on inline assembly, particularly the format of the constraints string, refer to the LLVM documentation.
Block addresses
The address of a basic block, as used by indirectbr instructions, is a BlockAddress constant. It is created from a block that is part of a function, other than its entry block, and refers back to the function and the block:
julia> mod = parse(LLVM.Module, """
define void @f() {
entry:
br label %target
target:
ret void
}""");
julia> f = mod.functions["f"]; bb = f.blocks[2];
julia> ba = BlockAddress(bb)
ptr blockaddress(@f, %target)
julia> ba.function == f, ba.block == bb
(true, true)A few other constants are mostly encountered when inspecting IR: the none token (ConstantTokenNone, the null value of the token type), the zero value of a target extension type (ConstantTargetNone), and signed pointers (ConstantPtrAuth, on LLVM 19 and later).
Global values
Global values are values that are encoded at the top level of a module. They support a couple of additional APIs:
gv.global_value_type: the type of the global value.gv.parent: the module that contains the global value.gv.linkage,gv.visibility,gv.section,gv.dllstorage: the linkage, visibility, section and DLL storage class of the global value.gv.unnamed_addr: whether the address of the global value is significant, e.g.,LLVM.UnnamedAddr.Globalfor anunnamed_addrglobal.gv.dso_local: whether the global value resolves to a definition in the same linkage unit (dso_local).isdeclaration(gv): whether the global value is a declaration, i.e., it does not have a body.
All of these properties, except for global_value_type, can also be assigned to (the section only on global objects, i.e., not on aliases).
The most common type of global value is the global variable, which can be created using the GlobalVariable constructor:
julia> mod = LLVM.Module("SomeModule");
julia> ty = LLVM.Int32Type();
julia> gv = GlobalVariable(mod, ty, "SomeGV")
@SomeGV = external global i32Global variables support additional APIs:
gv.initializer: the initializer of the global variable, a constant value (assignnothingto remove the initializer).gv.alignment: the alignment of the global variable.gv.threadlocal: whether the global variable is thread-local.gv.threadlocal_mode: the thread-local storage model of the global variable.gv.constant: whether the global variable is constant.gv.externally_initialized: whether the global variable is externally initialized.erase!: delete the global variable from its parent module, and delete the object.
All of these properties can be assigned to. The threadlocal flag is a view of the thread-local mode: making a variable thread-local selects the general dynamic model, which can be refined by assigning to threadlocal_mode:
julia> mod = LLVM.Module("SomeModule");
julia> gv = GlobalVariable(mod, LLVM.Int32Type(), "SomeGV");
julia> gv.threadlocal = true;
julia> gv.threadlocal_mode
LLVM.ThreadLocalMode.GeneralDynamic
julia> gv.threadlocal_mode = LLVM.ThreadLocalMode.LocalExec;
julia> gv
@SomeGV = external thread_local(localexec) global i32A global alias introduces a new symbol for an existing global value, or for a constant expression involving one. It can be created with the GlobalAlias constructor, which takes the value type and address space from the global value it refers to:
julia> mod = LLVM.Module("SomeModule");
julia> gv = GlobalVariable(mod, LLVM.Int32Type(), "SomeGV");
julia> gv.initializer = ConstantInt(Int32(42));
julia> ga = GlobalAlias(mod, gv, "SomeAlias")
@SomeAlias = alias i32, ptr @SomeGV
julia> ga.aliasee
@SomeGV = global i32 42For constant expressions, pass the value type explicitly, as in GlobalAlias(mod, typ, aliasee, name). The aliasee can be changed by assigning to the aliasee property.
Similarly, an indirect function or ifunc is a symbol whose address is determined at load time by calling a resolver function. It is created with the GlobalIFunc constructor, which takes the function type of the ifunc (not that of the resolver), and the resolver itself. The resolver is available as the resolver property, which can also be assigned to, and the ifunc can be removed with erase!.
Uses
It is possible to inspect the uses of a value using its uses property, a read-only view of Use objects, whose user and value properties refer to respectively the user and the original value:
julia> c1 = ConstantInt(42);
julia> c2 = const_inttoptr(c1, LLVM.PointerType(LLVM.Int1Type()));
julia> use = only(c1.uses);
julia> use.user
ptr inttoptr (i64 42 to ptr)
julia> use.value
i64 42It is also possible to replace uses of a value using the replace_uses! function (commonly referred to as "RAUW" in LLVM):
julia> inst1
%2 = add i64 %1, %0
julia> inst2
ret i64 %2
julia> replace_uses!(inst1, ConstantInt(Int64(42)))
julia> inst2
ret i64 42To only replace the uses of a value in a specific instruction, replace the matching operands of the instruction using replace!(inst.operands, old => new).
When only the users of a value are needed, use its users property, which returns the user of each use (so a user that uses the value multiple times occurs multiple times). After replacing or erasing the instructions that use a constant, constant expressions that used it may linger without being used themselves. These can be removed with remove_dead_constant_users!(c), e.g., before checking whether a global variable is still used.