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:

  • value_type: get the type of the value.
  • context: get the context in which the value was created.
  • name!/name: get or set the name of the value.

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:

  • operands: get the operands of the user.

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 0xFFFFFFFFE0000000

Constant 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()))
i1* null

julia> UndefValue(LLVM.Int1Type())
i1 undef

julia> PoisonValue(LLVM.Int1Type())
i1 poison

Constant 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+00

It 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)

Constant 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]
Note

ConstantDataVector is currently not implemented.

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 = value_type(val)
{ i32 }

julia> ConstantArray(ty, [val])
[1 x { i32 }] [{ i32 } { i32 42 }]

Both ConstantDataArray and ConstantArray can, to some extent, be manipulated with plain Julia array operations:

julia> arr = ConstantArray([1, 2])
[2 x i64] [i64 1, i64 2]

julia> length(arr)
2

julia> arr[1]
i64 1

Constant 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()))
i1* inttoptr (i64 42 to i1*)

For the exact list of supported constant expressions, refer to the LLVM documentation.

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)
void ()* asm "nop", ""

For more details on inline assembly, particularly the format of the constraints string, refer to the LLVM documentation.

Global values

Global values are values that are encoded at the top level of a module. They support a couple of additional APIs:

  • global_value_type: get the type of the global value.
  • isdeclaration: whether the global value is a declaration, i.e., it does not have a body.
  • linkage/linkage!: get or set the linkage of the global value.
  • visibility/visibility!: get or set the visibility of the global value.
  • section/section!: get or set the section of the global value.
  • alignment/alignment!: get or set the alignment of the global value.
  • dllstorage/dllstorage!: get or set the DLL storage class of the global value.
  • unnamed_addr/unnamed_addr!: get or set whether the global value has an unnamed address.

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 i32

Global variables support additional APIs:

  • initializer/initializer!: get or set the initializer of the global variable to a constant value (pass nothing to remove the initializer).
  • isthreadlocal/isthreadlocal!: get or set whether the global variable is thread-local.
  • isconstant/isconstant!: get or set whether the global variable is constant.
  • isextinit/isextinit!: get or set whether the global variable is externally initialized.
  • erase!: delete the global variable from its parent module, and delete the object.

Uses

It is possible to inspect the uses of a value using the iterator returned by the uses function. This iterator returns Use objects which contain both the user and the original value:

julia> c1 = ConstantInt(42);

julia> c2 = const_inttoptr(c1, LLVM.PointerType(LLVM.Int1Type()));

julia> use = only(uses(c1));

julia> user(use)
i1* inttoptr (i64 42 to i1*)

julia> value(use)
i64 42

It 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 42