Homework5 - LLVM
link for the implementation: llvm-pass-allocsize
Explanation of the code
For this homework, I tried to implement AllocSizePass
which is an LLVM pass that identifies alloca instructions within a function (representing stack allocations) and inserts a runtime printf statement after each allocation. This printf call outputs the size of each allocation in bytes, allowing users to observe the memory usage of each stack allocation during program execution. The pass traverses each function in the LLVM module, calculating the size of each allocation based on the data layout, then injects instrumentation code to print these sizes.
How It Is Tested
The pass was tested by:
- Compiling a C Program: A C program with multiple allocations was compiled with the pass plugin enabled, which allowed the pass to analyze and instrument the code.
- Runtime Execution: After compilation, the generated binary was executed. The output was inspected for runtime printf statements that reported the size of each allocation. This confirmed that the pass inserted the necessary instrumentation and altered the program’s runtime behavior as expected. The expected output included Allocation size: X bytes statements printed for each allocation, verifying that the pass correctly identified and instrumented allocations.
#include <stdio.h>
int calculate(int a, int b) {
int result;
if (a > b) {
= a - b;
result } else {
= a + b;
result }
return result;
}
int main() {
int x[10];
int sum = 0;
for (int i = 0; i < 10; i++) {
[i] = i * 2;
x+= x[i];
sum }
int result1 = calculate(sum, 15);
int result2 = calculate(sum, 25);
("Result 1: %d\n", result1);
printf("Result 2: %d\n", result2);
printf
return 0;
}
Output:
-fpass-plugin=build/alloc_size_pass.dylib test.c -o test_alloc_out
clang : calculate
Function: %2
Basic Block: 4 bytes
Allocation of type i32 with size: 4 bytes
Allocation of type i32 with size: 4 bytes
Allocation of type i32 with size: %9
Basic Block: %13
Basic Block: %17
Basic Block: main
Function: %0
Basic Block: 4 bytes
Allocation of type i32 with size[10 x i32] with size: 40 bytes
Allocation of type : 4 bytes
Allocation of type i32 with size: 4 bytes
Allocation of type i32 with size: 4 bytes
Allocation of type i32 with size: 4 bytes
Allocation of type i32 with size: %7
Basic Block: %10
Basic Block: %22
Basic Block: %25
Basic Block: printf Function
./test_alloc_out
: 4 bytes
Allocation size: 40 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size: 4 bytes
Allocation size1: 75
Result 2: 65 Result
What Was Challenging During Implementation
Adding a printf
call required defining printf
in the LLVM IR, creating or referencing a global format string, and ensuring correct argument types. Missteps in the function type, format string handling, or argument casting could lead to compilation or runtime errors. Solution: I used getOrInsertFunction
to declare printf
, which ensured that the function was only added if it didn’t already exist. To handle arguments, I used IRBuilder
for consistent type casting and format string handling, carefully matching printf’s arguments in LLVM IR.
This project specifically targets alloca
instructions, if we want the pass to also detect other types of allocations, we should probably identify calls to other available allocation instructions as well for this to become a comprehensive project.