Passing structs by value to another function

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.

Attributes are a separate data structure from types. It should be enough to add the byval attrribute to the Function definition, then it’ll be inherited when you create the call. So something like this ought to work

Function *f = cast<Function>(new_function.getCallee());
f->addParamAttr(ArgNo, Attribute::getWithByValType(Ctx, StructTy));

You’d need to know what the ArgNo and StructTy are of course, but hopefully you already have that analysis handy.

1 Like