Understanding tail calls

I have some code generated with llvm-g++ and llvm-link that includes a tail call that is confusing me for two reasons:

  1. I am not sure why it is a tail call (i.e. it does not look like it is in the tail position)

  2. When I instrument the code using my opt pass, none of the instrumentation functions in the callee get called, leading me to believe that some funny business is going on.

Below I have pasted the tail call in question and surrounding code. My instrumentation pass instruments every llvm instruction. When I run it and just print out debug info, I see all the instructions up to the tail call, the tail call instruction itself, and then the icmp following the tail call. But none of the debug info from within the callee gets printed.

I do not use “-tailcallopt” as an option for llc when I generate the code.

It seems like I am misunderstanding something about tail calls and tail call optimization. Any insight might help me debug this issue.

Thanks,
Scott

define linkonce_odr i64 @_ZN16FCEmailInputFile4openERKSsR11FCIdManager(%struct.FCEmailInputFile* %this, %“struct.std::basic_string<char,std::char_traits,std::allocator >”* nocapture %fn, %struct.FCIdManager* nocapture %idm) align 2 {
entry:
%0 = getelementptr inbounds %“struct.std::basic_string<char,std::char_traits,std::allocator >”* %fn, i64 0, i32 0, i32 0 ; <i8**> [#uses=1]
%1 = load i8** %0, align 8 ; <i8*> [#uses=1]
%2 = getelementptr inbounds %struct.FCEmailInputFile* %this, i64 0, i32 1 ; <%“struct.std::ifstream”> [#uses=1]
%3 = getelementptr inbounds %struct.FCEmailInputFile
%this, i64 0, i32 1, i32 1 ; <%“struct.std::basic_filebuf<char,std::char_traits >”> [#uses=1]
%4 = tail call %“struct.std::basic_filebuf<char,std::char_traits >”
@_ZNSt13basic_filebufIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode(%“struct.std::basic_filebuf<char,std::char_traits >”* %3, i8* %1, i32 8) ; <%“struct.std::basic_filebuf<char,std::char_traits >”> [#uses=1]
%5 = icmp eq %“struct.std::basic_filebuf<char,std::char_traits >”
%4, null ; [#uses=1]
%6 = getelementptr inbounds %struct.FCEmailInputFile* %this, i64 0, i32 1, i32 0, i32 0 ; <i32 (…)> [#uses=1]
%7 = load i32 (…)
%6, align 8 ; <i32 (…)> [#uses=1]
%8 = getelementptr inbounds i32 (…)
%7, i64 -3 ; <i32 (…)> [#uses=1]
%9 = bitcast i32 (…)
%8 to i64* ; <i64*> [#uses=1]
%10 = load i64* %9, align 8 ; [#uses=1]
%11 = ptrtoint %“struct.std::ifstream”* %2 to i64 ; [#uses=1]
%12 = add i64 %10, %11 ; [#uses=1]
%13 = inttoptr i64 %12 to %“struct.std::basic_ios<char,std::char_traits >”* ; <%“struct.std::basic_ios<char,std::char_traits >”*> [#uses=6]
br i1 %5, label %bb.i, label %bb1.i

I have some code generated with llvm-g++ and llvm-link that includes a tail call that is confusing me for two reasons:

1) I am not sure why it is a tail call (i.e. it does not look like it is in the tail position)

The "tail" marker has a very specific description in LLVM IR: it says that the caller does not access the callee's stack:
http://llvm.org/docs/LangRef.html#i_call

2) When I instrument the code using my opt pass, none of the instrumentation functions in the callee get called, leading me to believe that some funny business is going on.

I don't know about this.

-Chris

The "tail" marker has a very specific description in LLVM IR: it says that the caller does not access the callee's stack:
LLVM Language Reference Manual — LLVM 16.0.0git documentation

Ah that makes more sense. Thanks for the pointer.

> 2) When I instrument the code using my opt pass, none of the instrumentation functions in the callee get called, leading me to believe that some funny business is going on.
I don't know about this.

That is because I did not give you enough information :). I realized
that the callee was linkonce_odr, and so the function body that I was
instrumenting was not making its way into the final executable. This
is why I was not seeing my debug info printed.

So if I am looking at a CallInst, and want to know if the callee is
going to be instrumented by my pass, I can check F->isWeakForLinker(),
where F is the called function. But this just tells me when the
function definition *may* be replaced. I want to know precisely when
the CallInst is a call to a function that I am going to instrument as
part of my pass (ignore indirect invocations for the moment).
Obviously I could do an initial pass and build an explicit list of
such functions. But is there an easier way by just looking at the
Function object?

I guess I am looking for something like
F->IsGoingToGetReplacedAtLinkTime(). Actually, that seems like it is
probably impossible to resolve during the pass.

Hi Scott,

So if I am looking at a CallInst, and want to know if the callee is
going to be instrumented by my pass, I can check F->isWeakForLinker(),
where F is the called function. But this just tells me when the
function definition *may* be replaced. I want to know precisely when
the CallInst is a call to a function that I am going to instrument as
part of my pass (ignore indirect invocations for the moment).
Obviously I could do an initial pass and build an explicit list of
such functions. But is there an easier way by just looking at the
Function object?

I guess I am looking for something like
F->IsGoingToGetReplacedAtLinkTime(). Actually, that seems like it is
probably impossible to resolve during the pass.

I don't really understand what you are asking. In general a function with
weak linkage may or may not be replaced by another at link time, but you
can't tell before link time. For example, suppose a function called F (I
will refer to this as F1) has weak linkage. If at link time you link with
another object file that also defines a function called F (I will refer to
this as F2) but not with weak linkage, then F1 will be discarded and F2 is
what will end up in the executable. On the other hand, suppose F2 also has
weak linkage, then I think one of F1/F2 is chosen essentially randomly.
Finally, if at link time no other object file defines a function called F
then F1 will be used. So what happens is determined by what the user passes
to the linker, and as such is not known at compile time.

A philosophical remark: if you could tell at compile time which function
bodies will be in the final executable, then there would be no point in
having weak linkage: if you can tell that the function body is not going
to be used then you might as well throw it away (i.e. replace with a
declaration) and not bother optimizing it; on the other hand, if you are
sure that it is going to be used then you might as well give it strong
linkage.

Ciao,

Duncan.

I don't really understand what you are asking. In general a function with
weak linkage may or may not be replaced by another at link time, but you
can't tell before link time. For example, suppose a function called F (I
will refer to this as F1) has weak linkage. If at link time you link with
another object file that also defines a function called F (I will refer to
this as F2) but not with weak linkage, then F1 will be discarded and F2 is
what will end up in the executable. On the other hand, suppose F2 also has
weak linkage, then I think one of F1/F2 is chosen essentially randomly.
Finally, if at link time no other object file defines a function called F
then F1 will be used. So what happens is determined by what the user passes
to the linker, and as such is not known at compile time.

A philosophical remark: if you could tell at compile time which function
bodies will be in the final executable, then there would be no point in
having weak linkage: if you can tell that the function body is not going
to be used then you might as well throw it away (i.e. replace with a
declaration) and not bother optimizing it; on the other hand, if you are
sure that it is going to be used then you might as well give it strong
linkage.

OK this makes sense, thanks for the example and the general comment.

Let me be a bit more clear about my problem. I am instrumenting code
with an opt pass. If I come across a CallInst, the way I instrument it
depends on whether the body of the callee will also be instrumented.
If the callee is some function in, say, a dynamically loaded share
library, it won't be instrumented (because I am not running the
library through my pass). Previously, I assumed that as long as I
could see the function definition in the module, it was safe to assume
that I was going to instrument it. But I recently came across examples
where the function definition from a dynamically loaded library was
brought in and assigned linkonce_odr. I would instrument that
definition, but then that definition was not used at link time, so my
instrumented code was broken.

So I actually don't care which version of the function gets finally
linked, but I do care whether or not it is a version that actually
runs through my opt pass. Does that make more sense?

However, from your philosophical comment, it seems like in general
this is not going to be possible. It also sounds like I need to
educate myself a bit more about linkage, beyond just the LLVM docs.

Thanks,
Scott