We are a group of 3 master students and are currently doing a project where we are making a prototype compiler for a language with support for calling external C functions and we are currently having an issue with passing structs by value between functions.
To pinpoint our issue on the IR level we created a simple C++ program that is roughly the same as the input program of our compiler and noticed the main difference between the IR emitted by Clang++ and the IR our compiler produces was the inclusion of a byval
parameter attribute of in the function call.
We tried modifying the IR by hand to verify if this parameter attribute was indeed what we were missing as illustrated below.
Our IR:
%Struct.Number = type { i64, i64, i32, i8, i8 }
Define void @main() {
Root_block:
***
call void (ptr, ...) @simpleFunction(ptr %callVar1)
ret void
}
Desired IR:
%Struct.Number = type { i64, i64, i32, i8, i8 }
Define void @main() {
Root_block:
***
call void (ptr, ...) @simpleFunction(ptr byval(%Struct.Number) %callVar1)
ret void
}
This change indeed seems to be what we were missing as compiling the program with this version of the IR yields in an executable which displayed the desired behaviour.
We generated the original IR by doing the following:
CallVar1 is a copy of var1. This copy is created by calling auto alloc = CreateAlloca(structType, nullptr, newVarName)
and then using CreateGEP
and CreateLoad
for all fields of the struct like this:
llvm::Value *value_ptr = builder->CreateGEP(bcModule->getNumberStructType(), field.getLlvmValue(), {indices[0], indices[0]}, "valuePtrtmp");
llvm::Value *value_ptr_tmp = builder->CreateGEP(bcModule->getNumberStructType(), alloc, {indices[0], indices[0]}, "valuePtrtmptmp");
llvm::Value *loaded_value = builder->CreateLoad(llvm::Type::getInt64Ty(bcModule->getContext()), value_ptr, "loadtmp");
builder->CreateStore(loaded_value, value_ptr_tmp);
// Then the alloc gets added to the parameters vector:
parameters.push_back(alloc);
// then we create a type vector for the parameters:
std::vector<llvm::Type *> param_types;
param_types.reserve(parameters.size());
transform(parameters.begin(), parameters.end(), back_inserter(param_types), Visitor::getType);
// then we create the functionType using the param_types
llvm::Type *void_t = llvm::Type::getVoidTy(bcModule->getContext());
llvm::FunctionType *new_function_types = llvm::FunctionType::get(void_t, param_types, true);
auto *new_function = new llvm::FunctionCallee();
// and then create the function call using the parameters
*(new_function) = bcModule->getOrInsertFunction(functionName, new_function_types);
builder->CreateCall(*new_function, parameters);
We are wondering what we need to do differently to let our compiler generate the byval
parameter attribute.