Help with bitcast instruction

Hi,
I’m playing with the IR of pngpixel and I encountered this instruction:

call void bitcast (void (%struct.png_struct_def.68*, i8*, i8* (%struct.png_struct_def.68*, i64), void (%struct.png_struct_def.68, i8*)) @png_set_mem_fn to void (%struct.png_struct_def*, i8*, i8* (%struct.png_struct_def*, i64), void (%struct.png_struct_def, i8*)))(%struct.png_struct_def* %create_struct, i8* %mem_ptr, i8* (%struct.png_struct_def*, i64)* %malloc_fn, void (%struct.png_struct_def*, i8*)* %free_fn) #15

I would like to understand where bitcast is defined because it is not defined within libpng. I also noticed that the @ is not present, what does the missing @ mean? I guess it must be an LLVM function.

The bitcast operation seems to represent the following C code:

if (png_ptr != NULL)
{
png_ptr->mem_ptr = mem_ptr;
png_ptr->malloc_fn = malloc_fn;
png_ptr->free_fn = free_fn;
}

bitcast is an LLVM IR instruction. You can find its description here https://llvm.org/docs/LangRef.html#bitcast-to-instruction

Thanks I missed that

Alberto

Hi Alberto,

I would like to understand where bitcast is defined because it is not defined within libpng.

It's because the function (@png_set) has been defined to take one kind
of argument, but is being called with another. In this case the
difference seems to be whether it takes pointers to
%struct.png_struct_def or %struct.png_struct_def.68.

This kind of situation isn't terribly common, but can happen for
example in LTO where two modules are linked together, and one of them
only has an opaque handle for the type to hide the implementation
(e.g. from plain "struct png_struct_def;" in C). When that happens
LLVM can't merge the types, so it adds a numeric tag to one of them,
and then it has to insert bitcasts in the calls like this so that its
own type system works.

I also noticed that the @ is not present, what does the missing @ mean? I guess it must be an LLVM function.

The @ is only used directly before a global variable (including a
declared/defined function). In this case it's buried, but it is there
(@png_set). In other cases, like when you call a function pointer, it
will be missing entirely.

Cheers.

Tim.

Hi Tim,
Thanks again for your help. All clear now

Alberto

Hi Tim,
I’m still struggling on the instruction:

call void bitcast (void (%struct.png_struct_def.68*, i8*, i8* (%struct.png_struct_def.68*, i64), void (%struct.png_struct_def.68, i8*)) @png_set_mem_fn to void (%struct.png_struct_def*, i8*, i8* (%struct.png_struct_def*, i64), void (%struct.png_struct_def, i8*)))(%struct.png_struct_def* %create_struct, i8* %mem_ptr, i8* (%struct.png_struct_def*, i64)* %malloc_fn, void (%struct.png_struct_def*, i8*)* %free_fn) #15

I’m pretty sure that this is a CallInst but when I try to call getCalledFunction() I receive a null pointer and that really surprise me.

I would like to understand how to get the function that is called and its parameters. Can you tell me how please?

Am i right saying that:

(void (%struct.png_struct_def.68*, i8*, i8* (%struct.png_struct_def.68*, i64), void (%struct.png_struct_def.68, i8*)) → “old parameters type”
(%struct.png_struct_def*, i8*, i8* (%struct.png_struct_def*, i64), void (%struct.png_struct_def, i8*))) → “new parameters type”
(%struct.png_struct_def* %create_struct, i8* %mem_ptr, i8* (%struct.png_struct_def*, i64)* %malloc_fn, void (%struct.png_struct_def*, i8*)* %free_fn) “the parameters passed to the png_set_mem_fn”

As a clarification the function is declared like this:

void PNGAPI
png_set_mem_fn(png_structrp png_ptr, png_voidp mem_ptr, png_malloc_ptr
malloc_fn, png_free_ptr free_fn)

Thanks again

Hi,
sorry I forgot to mention, how can I extract the keyword bitcast? I would like to be able to distinguish the function name.

Thanks

Hi Alberto,

call void bitcast (void (%struct.png_struct_def.68*, i8*, i8* (%struct.png_struct_def.68*, i64)*, void (%struct.png_struct_def.68*, i8*)*)* @png_set_mem_fn to void (%struct.png_struct_def*, i8*, i8* (%struct.png_struct_def*, i64)*, void (%struct.png_struct_def*, i8*)*)*)(%struct.png_struct_def* %create_struct, i8* %mem_ptr, i8* (%struct.png_struct_def*, i64)* %malloc_fn, void (%struct.png_struct_def*, i8*)* %free_fn) #15

I'm pretty sure that this is a CallInst but when I try to call getCalledFunction() I receive a null pointer and that really surprise me.

I would like to understand how to get the function that is called and its parameters. Can you tell me how please?

The problem is that getCalledFunction is pretty naive, it just takes
the getCalleeValue and dyn_cast<Function>s it. Because you've got a
cast in the IR itself your situation is a bit more complicated.

In this case, you could write a "findCalledFunction" something like:

    Function *findCalledFunction(CallBase &CI) {
      Value *Callee = CI.getCalledValue()->stripPointerCasts();
      if (auto F = dyn_cast<Function>(Callee))
        return F;
      return nullptr;
    }

Where the "stripPointerCast" is the key extra ingredient that looks
through the bitcast. (Someone could certainly argue that's what
getCalledFunction should be doing anyway, but there are subtleties on
weird targets like GPUs so it's not 100%).

It won't catch all callsites (e.g. virtual function calls could still
be plain %X values), but pretty much any algorithm has to be robust
against an unknown callee anyway.

Am i right saying that:

(void (%struct.png_struct_def.68*, i8*, i8* (%struct.png_struct_def.68*, i64)*, void (%struct.png_struct_def.68*, i8*)*)* -> "old parameters type"
(%struct.png_struct_def*, i8*, i8* (%struct.png_struct_def*, i64)*, void (%struct.png_struct_def*, i8*)*)*) -> "new parameters type"
(%struct.png_struct_def* %create_struct, i8* %mem_ptr, i8* (%struct.png_struct_def*, i64)* %malloc_fn, void (%struct.png_struct_def*, i8*)* %free_fn) "the parameters passed to the png_set_mem_fn"

I think so, from what I understand. The essence of that cast is just
changing one pointer type to another, so it doesn't actually affect
how parameters are passed at all, and that is definitely the function
called.

As an aside, we're working towards a world where there's just one
"ptr" type in LLVM. I think this whole situation would disappear
completely there.

sorry I forgot to mention, how can I extract the keyword bitcast? I would like to be able to distinguish the function name.

I think the answer to this question is the "stripPointerCasts"
function I mentioned above, though I'm not 100% sure.

Cheers.

Tim.

Thanks you Tim,
that solved my problem!