Function Definitions and Declarations¶
The translation of function definitions depends on a range of factors, ranging from the calling convention in use, whether the function is exception-aware or not, and if the function is to be publicly available outside the module.
Simple Public Functions¶
The most basic model is:
int Bar(void)
{
return 17;
}
Becomes:
define i32 @Bar() nounwind {
ret i32 17
}
Simple Private Functions¶
A static function is a function private to a module that cannot be referenced from outside of the defining module:
define private i32 @Foo() nounwind {
ret i32 17
}
Note that this does not directly map to public/private in the context of
C++
. Two C++
classes inside one LLVM module can call each other
private methods, because they’re simply module-level private functions for
LLVM.
Function Prototypes¶
A function prototype, aka a profile, is translated into an equivalent
declare
declaration in LLVM IR:
int Bar(int value);
Becomes:
declare i32 @Bar(i32 %value)
Or you can leave out the descriptive parameter name:
declare i32 @Bar(i32)
Functions with a Variable Number of Parameters¶
To call a so-called vararg function, you first need to define or declare it using the elipsis (…) and then you need to make use of a special syntax for function calls that allows you to explicitly list the types of the parameters of the function that is being called. This “hack” exists to allow overriding a call to a function such as a function with variable parameters. Please notice that you only need to specify the return type once, not twice as you’d have to do if it was a true cast:
declare i32 @printf(i8*, ...) nounwind
@.textstr = internal constant [20 x i8] c"Argument count: %d\0A\00"
define i32 @main(i32 %argc, i8** %argv) nounwind {
; printf("Argument count: %d\n", argc)
%1 = call i32 (i8*, ...) @printf(i8* getelementptr([20 x i8], [20 x i8]* @.textstr, i32 0, i32 0), i32 %argc)
ret i32 0
}
Function Overloading¶
Function overloading is actually not dealt with on the level of LLVM IR, but on the source language. Function names are mangled, so that they encode the types they take as parameter and return in their function name. For a C++ example:
int function(int a, int b) {
return a + b;
}
double function(double a, double b, double x) {
return a*b + x;
}
For LLVM these two are completely different functions, with different names etc.
define i32 @_Z8functionii(i32 %a, i32 %b) #0 {
; [...]
ret i32 %5
}
define double @_Z8functionddd(double %a, double %b, double %x) #0 {
; [...]
ret double %8
}
Struct by Value as Parameter or Return Value¶
Classes or structs are often passed around by value, implicitly cloning the objects when they are passed. But they are not
struct Point {
double x;
double y;
double z;
};
Point add_points(Point a, Point b) {
Point p;
p.x = a.x + b.x;
p.y = a.y + b.y;
p.z = a.z + b.z;
return p;
}
This simple example is in turn compiled to
%struct.Point = type { double, double, double }
define void @add_points(%struct.Point* noalias sret %agg.result,
%struct.Point* byval align 8 %a,
%struct.Point* byval align 8 %b) #0 {
; there is no alloca here for Point p;
; p.x = a.x + b.x;
%1 = getelementptr inbounds %struct.Point, %struct.Point* %a, i32 0, i32 0
%2 = load double, double* %1, align 8
%3 = getelementptr inbounds %struct.Point, %struct.Point* %b, i32 0, i32 0
%4 = load double, double* %3, align 8
%5 = fadd double %2, %4
%6 = getelementptr inbounds %struct.Point, %struct.Point* %agg.result, i32 0, i32 0
store double %5, double* %6, align 8
; p.y = a.y + b.y;
%7 = getelementptr inbounds %struct.Point, %struct.Point* %a, i32 0, i32 1
%8 = load double, double* %7, align 8
%9 = getelementptr inbounds %struct.Point, %struct.Point* %b, i32 0, i32 1
%10 = load double, double* %9, align 8
%11 = fadd double %8, %10
%12 = getelementptr inbounds %struct.Point, %struct.Point* %agg.result, i32 0, i32 1
store double %11, double* %12, align 8
; p.z = a.z + b.z;
%13 = getelementptr inbounds %struct.Point, %struct.Point* %a, i32 0, i32 2
%14 = load double, double* %13, align 8
%15 = getelementptr inbounds %struct.Point, %struct.Point* %b, i32 0, i32 2
%16 = load double, double* %15, align 8
%17 = fadd double %14, %16
%18 = getelementptr inbounds %struct.Point, %struct.Point* %agg.result, i32 0, i32 2
store double %17, double* %18, align 8
; there is no real returned value, because the previous stores directly wrote
; to the caller allocated value via %agg.result
ret void
}
We can see that the function now actually returns void
and another
parameter was added. The first parameter is a pointer to the result,
which is allocated by the caller. The pointer has the attribute
noalias
because there is no way that one of the parameters might
point to the same location. The sret
attribute indicates that this
is the return value.
The parameters have the byval
attribute, which indicates that they
are structs that are passed by value.
Let’s see how this function would be called.
int main() {
Point a = {1.0, 3.0, 4.0};
Point b = {2.0, 8.0, 5.0};
Point c = add_points(a, b);
return 0;
}
is compiled to:
define i32 @main() #1 {
; these are the a, b, c in the scope of main
%a = alloca %struct.Point, align 8
%b = alloca %struct.Point, align 8
%c = alloca %struct.Point, align 8
; these are copies, which are passed as arguments
%1 = alloca %struct.Point, align 8
%2 = alloca %struct.Point, align 8
; copy the global initializer main::a to %a
%3 = bitcast %struct.Point* %a to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %3, i8* bitcast (%struct.Point* @main.a to i8*), i64 24, i32 8, i1 false)
; copy the global initializer main::b to %b
%4 = bitcast %struct.Point* %b to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %4, i8* bitcast (%struct.Point* @main.b to i8*), i64 24, i32 8, i1 false)
; clone a to %1
%5 = bitcast %struct.Point* %1 to i8*
%6 = bitcast %struct.Point* %a to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %5, i8* %6, i64 24, i32 8, i1 false)
; clone b to %2
%7 = bitcast %struct.Point* %2 to i8*
%8 = bitcast %struct.Point* %b to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %7, i8* %8, i64 24, i32 8, i1 false)
; call add_points with the cloned values
call void @add_points(%struct.Point* sret %c, %struct.Point* byval align 8 %1, %struct.Point* byval align 8 %2)
; [...]
}
We can see that the caller, in our case main
, allocates space for
the return value %c
and also makes sure to clone the parameters
a
and b
before actually passing them by reference.
Exception-Aware Functions¶
A function that is aware of being part of a larger scheme of
exception-handling is called an exception-aware function. Depending upon
the type of exception handling being employed, the function may either
return a pointer to an exception instance, create a
setjmp
/longjmp
frame, or simply specify the uwtable
(for
UnWind Table) attribute. These cases will all be covered in great detail
in the chapter on Exception Handling below.
Function Pointers¶
Function pointers are expressed almost like in C and C++:
int (*Function)(char *buffer);
Becomes:
@Function = global i32(i8*)* null