How to write C++ code for creating IR of a function returning a struct containing an array of real values?

Since some days already I’m attempting to write C++ code for creating IR of a function returning a struct containing an array of real values.
I looked at the kaleidoscope examples until including chapter 3.
I’m far away from trying to run the JIT-compiler or optimize.
The point is that I’m either

  1. getting an assertion below llvm::IRBuilderBase::CreateGEP() inside llvm::checkGEPType() because of the type-ptr being null (in debug mode only)
  2. The IR code created in Release mode cannot be compiled using clang telling me
tmp.ll:14:27: error: invalid getelementptr indices
  %8 = getelementptr ptr, ptr %7, i32 0, i32 0

From what I’m seeing there is more or less no documentation. Or am I missing The-Well-Known-Book everybody needs to read before even attempting this?

I’ve chatted with chatgpt are let him create code for me many times. Usually the code created by chatgpt is missing arguments to IRBuilder::CreateGEP() and I’ve asked it many questions about this function. Today chatgpt started using another function for the same problem (IRBuilder::CreateStructGEP()).
Below is shown what chatGPT created (I inserted the first rgument to CreateGEP(). I tried also passing a pointer using structType->getPointerTo() instead of just structType.

Sometimes I’m only getting an assertion firing, if the index for the array passed to IRBuilder::CreateGEP() is non-zero.

I’ve tried multiple versions on both, LINUX (various systems) and windows:

  1. llvm-8 on centos 7.9
  2. LLVM-15 on unbuntu 20 running on WSL
  3. LLVM-16 on Windows.
#include <iostream>
#include "llvm/ADT/ArrayRef.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"

using namespace llvm;

int main(int, char**)
{
	LLVMContext context;
	Module* module = new Module("my_module", context);

	// Create a struct type that contains an array of 2 double values
	std::vector<Type*> structElements;
	structElements.push_back(ArrayType::get(Type::getDoubleTy(context), 2));
	StructType* structType = StructType::create(context, structElements, "myStruct");

	// Create a function type that returns the struct
	FunctionType* funcType = FunctionType::get(structType, {}, false);

	// Create the function
	Function* func = Function::Create(funcType, Function::ExternalLinkage, "myFunction", module);

	// Create a basic block and an IRBuilder
	BasicBlock* entryBlock = BasicBlock::Create(context, "entry", func);
	IRBuilder<> builder(entryBlock);

	// Create an instance of the struct on the stack
	AllocaInst* structInstance = builder.CreateAlloca(structType);

	// Get a pointer to the array in the struct
	Value* zero = ConstantInt::get(Type::getInt32Ty(context), 0);
	Value* arrayPtr = builder.CreateStructGEP(structType, structInstance, 0);

	// Store values into the array
	for (unsigned i = 0; i < 2; ++i)
	{	// Compute the value to store (i^2)
		Value* index = ConstantInt::get(Type::getInt32Ty(context), i);
		Value* value = builder.CreateMul(index, index);

		// Convert the value to double
		Value* doubleValue = builder.CreateSIToFP(value, Type::getDoubleTy(context));

		// Get a pointer to the i-th element of the array
		Value* elementPtr = builder.CreateGEP(structType, arrayPtr, {zero, index});

		// Store the value into the i-th element of the array
		builder.CreateStore(doubleValue, elementPtr);
	}

	// Return the struct
	LoadInst* retVal = builder.CreateLoad(structType->getPointerTo(), structInstance);
	builder.CreateRet(retVal);
}

As you’ve learned, ChatGPT, whilst impressive in many respects, is no substitute for doing your own research and thinking, at least for now. Good ways to learn about IR are reading the Doxygen documentation, reading LangRef and seeing what Clang produces for C code that mirrors your intent.

Clearly arrayPtr does not have type structType*; you need to pass whatever the type of *arrayPtr is, i.e. [2 x double].

You may also find CreateConstGEP2_32 useful to avoid having to create the ConstantInts yourself.

3 Likes

CreateConstGEP2_32() expects a constant integer!

This would only be helpful, if I would want to unroll the code for every element of the array.

Constant in the sense of the generated IR, not the C code you’re writing to use the IRBuilder interfaces. You know it’s constant because you just created a ConstantInt for it. builder.CreateConstGEP2_32(..., 0, i); is the shorter way to write this.

No it’s not, not with your current code.

you’re right. Anyway – this problem is solved.

The detour of using a struct is not necessary, as one can anyway not return it.

Yes you can? Compiler Explorer

It may not be ABI-compliant if it’s expecting the frontend to have done some lowering, but it compiles just fine.

Maybe you discovered that this line is wrong? You don’t want to load a pointer to a struct, you want to load the struct itself, so the type is wrong.

This problem is solved!

Yes – there were coding mistakes.

Currently I’ve only successfully used CreateGEP() on pointer to structures.
I’ll change this code to use arrays instead.
The structure was only used in an attempt to cheat in order to be able to return it – and this is anyway not legal.

But I’m stuck on the other problem: Accessing elements of a global array variable(constant).