cgbsu
cgbsu

Reputation: 55

llvm - Access And Call Function Pointer In A Global Array Without Horrible Pointer Hacking

I am having quite some trouble programmatically accessing a function pointer in a global array programmatically. I have a global array of function pointers, my "lookup table" which I basically I am using for "overloads". Every time I try to GetElementPointer (GEP)/getelementptr an element in this array with the desired type, I get a runtime assertion:

warp_compiler: /root/.conan/data/llvm-core/13.0.0/_/_/package    /6efbb14f313e71b5e1dbf77c1c011f47614b7c7c/include/llvm/IR/
Instructions.h:960: static llvm::GetElementPtrInst* llvm::GetElementPtrInst::Create(
llvm::Type*, llvm::Value*, llvm::ArrayRef<llvm::Value*>, const llvm::Twine&, llvm::Instruction*): 
Assertion `cast<PointerType>(Ptr->getType()->getScalarType()) ->isOpaqueOrPointeeTypeMatches(PointeeType)' 
failed.
Aborted (core dumped)

Now the type of the array when compiled is [3 x i32 (i32)*] by default it tries to do a a GEP on [3 x i32 (i32)*]* with element type [3 x i32 (i32)*] which does not work.

If I manaually edit the code to be:

%option_address = getelementptr i32 (i32)*, [3 x i32 (i32)*]* @my_function_1_table, i32 %7

Or too:

%option_address = getelementptr i32 (i32)*, [3 x i32 (i32)*] @my_function_1_table, i32 %7

it works just dandy, the ladder is really what I am looking to do. But I cant seem to do it probrammatically because of this exception.

I have tried casting the array to i32 (i32)* with:

    auto first_element = context->builder.CreatePointerBitCastOrAddrSpaceCast(
            (llvm::Value*) lookup_table_global, 
            (llvm::Type*) function->getType(), 
            "cast"
        );

Then trying to access the elements with something like:

auto element = context->builder.CreateGEP(
         (llvm::Type*) function->getType(), 
         first_element,                                             
         index_array,
         "option_address"
     );

But I get that exception again, and it does work if I type it manually into the IR

%option_address = getelementptr i32 (i32)*, i32 (i32)* @my_function_1_table, i32 %7

Seems like a pretty regular way to access an array, right?

But I cant seem to do it programmatically, because if the assertion, I even tried to make a work around by tryng to inherit from GetElementPtrInst directly and omitting the assertion, but couldn't (because its constructor is private).

Currently, my solution is to cast the array to a i32 (i32)* then to a [1 x i32 (i32)*] then do the GEP on a [1 x i32(i32)*]* with a [1 x i32(i32)]

%option_address = getelementptr [1 x i32 (i32)*], [1 x i32 (i32)*]* bitcast ([3 x i32 (i32)*]* @my_function_1_table to [1 x i32 (i32)*]*), i32 %7

This is horrible.

Does anyone know how I can simply access the function pointers I need from a global (constant) array so they can be called? Also is my current solution portable?

Thank you!

Upvotes: 1

Views: 671

Answers (1)

Chandler Carruth
Chandler Carruth

Reputation: 3341

Sorry you've run into this challenging aspect of LLVM. It definitely causes confusion.

There is an entire webpage dedicated to trying to help folks understand the counter-intuitive design of this instruction. While the design is well motivated from within LLVM, it causes lots of folks confusion and frustration when they first encounter it.

The challenge you're hitting is because a GEP instruction in LLVM always operates on a pointer, and with global variables, that pointer is to the variable. When the global variable is an array as in your case, this is extra confusing -- GEP has to go through an extra layer of pointer before it gets to the array you're trying to index with it.

The first section of the GEP site I mentioned above specifically explains how the first index to a GEP works -- it indexes the base pointer directly.

The second section then specifically clarifies why global variables end up surprising here. The global variable, @my_function_1_table in your case, is a pointer to itself. You'll have to index that with a simple i32 0 index first. Then you can add an additional index into the array that global variable points to.

So for a global variable with type [3 x i32 (i32)*], if you want to extract the second element of the array, you need:

  %fptr = getelementptr [3 x i32 (i32)*], [3 x i32 (i32)*]* @my_function_1_table, i32 0, i32 2

The first i32 0 here indexes the global itself. The second index of i32 2 indexes into the array.

You can also use Clang to get example LLVM IR that can help explain how to do things. For example, here is some C++ that does something similar to what you're trying to do:

using FPtrT = int (*)(int);

extern FPtrT function_ptrs[3];

int test(int i) {
  FPtrT fptr = function_ptrs[i];
  return (*fptr)(42);
}

And this turns into the following LLVM IR after some basic optimizations (-O1):

@function_ptrs = external dso_local local_unnamed_addr global [3 x i32 (i32)*], align 16

define dso_local i32 @_Z4testi(i32 %0) local_unnamed_addr #0 {
  %2 = sext i32 %0 to i64
  %3 = getelementptr inbounds [3 x i32 (i32)*], [3 x i32 (i32)*]* @function_ptrs, i64 0, i64 %2
  %4 = load i32 (i32)*, i32 (i32)** %3, align 8, !tbaa !4
  %5 = call i32 %4(i32 42)
  ret i32 %5
}

attributes #0 = { mustprogress uwtable "frame-pointer"="none" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

Here you can see %3 is doing a dynamic (and inbounds, but that's orthogonal) version of this indexing.

You can play with this kind of IR generation using Compiler Explorer: https://cpp.compiler-explorer.com/z/ETa8nvTvh

Once you're using the API to create this two index GEP it should start working for you.

Also, just so you (or others) reading this don't get confused: the LLVM IR syntax changed here recently, so the latest versions of LLVM don't look quite the same. You can switch from Clang v13 to a more recent one to see what it looks like, for example here: https://cpp.compiler-explorer.com/z/Kc4er413G

Upvotes: 2

Related Questions