Linker Question

Say I have the following code:

main.c:

  void foo(char *, ...);
  void bar(int, ...);

  int main(void)
  {
    foo("foo", 1);
    bar(1, 2, 3, "bar");

    return 0;
  }

foobar.c:

  int printf(const char *, ...);

  void foo(const char *str, int i)
  {
    printf("%s: %d\n", str, i);
  }

  void bar(int i, int j, int k, const char *str)
  {
    printf("%s sum: %d\n", str, i+j+k);
  }

When I compile these separately and run llvm-ld -disable-opt I get this
unfortunate sequence:

define i32 @main() nounwind {
entry:
  %retval = alloca i32 ; <i32*> [#uses=2]
  %0 = alloca i32 ; <i32*> [#uses=2]
  %"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
  call void (i8*, ...)* bitcast (void (i8*, i32)* @foo to void (i8*, ...)*)
(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 1) nounwind
  call void (i32, ...)* bitcast (void (i32, i32, i32, i8*)* @bar to void
(i32, ...)*)(i32 1, i32 2, i32 3, i8* getelementptr inbounds ([4 x i8]*
@.str1, i64 0, i64 0)) nounwind

It's unfortunate because the bitcast gets in the way. For example, looking
at the ValueMap in the linker one would expect a Function to map to a
Function. That's not true here. A Function maps to a bitcast. It also
means the generated code will be suboptimal on targets like x86-64 that
require extra processing for vararg calls.

This happens because RecursiveResolveTypes in LinkModules.cpp doesn't
understand that a "more fully specified" function argument list is compatible
with a "less fully specified" one and that it should be perfectly fine to
resolve the type to the more specified one.

Would it break things horribly if I went in and taught RecursiveResolveTypes
how to handle this or would that violate some deep-level assumption?

                                 -Dave

When I compile these separately and run llvm-ld -disable-opt I get this
unfortunate sequence:

define i32 @main() nounwind {
entry:
%retval = alloca i32 ; <i32*> [#uses=2]
%0 = alloca i32 ; <i32*> [#uses=2]
%"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
call void (i8*, ...)* bitcast (void (i8*, i32)* @foo to void (i8*, ...)*)
(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 1) nounwind
call void (i32, ...)* bitcast (void (i32, i32, i32, i8*)* @bar to void
(i32, ...)*)(i32 1, i32 2, i32 3, i8* getelementptr inbounds ([4 x i8]*
@.str1, i64 0, i64 0)) nounwind

It's unfortunate because the bitcast gets in the way. For example, looking
at the ValueMap in the linker one would expect a Function to map to a
Function. That's not true here. A Function maps to a bitcast. It also
means the generated code will be suboptimal on targets like x86-64 that
require extra processing for vararg calls.

Yep, it's pretty ugly.

This happens because RecursiveResolveTypes in LinkModules.cpp doesn't
understand that a "more fully specified" function argument list is compatible
with a "less fully specified" one and that it should be perfectly fine to
resolve the type to the more specified one.

Would it break things horribly if I went in and taught RecursiveResolveTypes
how to handle this or would that violate some deep-level assumption?

This is intentional, but instcombine should clean it up. Are you not seeing this? If not, instcombine should be improved.

-Chris

The problem is I need to examine this before instcombine and do various
nefarious things like spitting out IR and/or generating code without any
modifications to improve debuggability of our compiler. Why is this
intentional?

I've coded up the necessary linker changes but the build is waiting on NFS and
a tape drive. Grr...

                              -Dave

The type system fundamentally depends on identity; you can't just
screw around with types like that. If I had to guess, things will
explode if something uses a pointer to such a function in some way
other than calling it.

If you really need this, you can always copy the relevant code out of
instcombine and create your own pass to which just performs this fix.

-Eli

It is intentional because there are two things going on here: the linker needs to unify the prototypes, then something needs to massage the call to pass the arguments to the new prototype. Just violating the type system won't work. Doing this in two steps (the linker does something simple and always correct + instcombine improving performance/analyzability) is much better than the old and buggy thing we had before where the linker tried to do both.

-Chris