[LLVMbugs] [Bug 20653] New: MS ABI: Mangling collision for virtual member functions of multiple inheritance classes

bugzilla-daemon at llvm.org bugzilla-daemon at llvm.org
Wed Aug 13 13:35:46 PDT 2014


http://llvm.org/bugs/show_bug.cgi?id=20653

            Bug ID: 20653
           Summary: MS ABI: Mangling collision for virtual member
                    functions of multiple inheritance classes
           Product: clang
           Version: unspecified
          Hardware: PC
                OS: Windows NT
            Status: NEW
          Severity: normal
          Priority: P
         Component: LLVM Codegen
          Assignee: unassignedclangbugs at nondot.org
          Reporter: rnk at google.com
                CC: david.majnemer at gmail.com, hans at chromium.org,
                    llvmbugs at cs.uiuc.edu, nicolasweber at gmx.de
            Blocks: 12477, 18887
    Classification: Unclassified

Consider:

struct A { virtual void a(int); };
struct B { virtual void b(int, int); };
struct C : A, B {
  virtual void a(int);
  virtual void b(int, int);
};

Pointers to virtual member functions are implemented using thunks. The only
information encoded in the thunk is the vftable offset of the method, so MSVC's
thunks are typically something like 'mov eax, [ecx] ; jmp [ecx + 0xNN]'. Since
they are functionally identical to all other thunks to vftable offset 0xNN,
MSVC aggressively reuses memptr thunks for different types.  In this function,
only one member pointer thunk called "A::`vcall'{0}'" is emitted:

void f(A *a, B *b) {
  (a->*(&A::a))(0);
  (b->*(&B::b))(0, 0);
}

B's thunk is identical to A's, so they save some code size by reusing it. 
Because the types are unrelated, member pointer comparison still works.  If a
derived class inherits from both such types, the member pointer will grow an
extra this-adjustment field which discriminates between the two vftables.

Going further, if we replace f with this version that uses C's member pointers,
we can see that only one thunk called "C::`vcall'{0}'" is emitted.  Note that
the mangling *lacks* type information, which MSVC normally sprinkles liberally
through their manglings.

void f(C *c) {
  (c->*(&C::a))(0);
  (c->*(&C::b))(0, 0);
}

In Clang, we emit a delegating call, which encodes extra information about the
virtual function prototype into the thunk, but that information is not
reflected in the mangling, so we have a collision.  We end up with this IR,
which is clearly broken:

define void @"\01?f@@YAXPAUC@@@Z"(%struct.C* %c) #0 {
entry:
  %c.addr = alloca %struct.C*, align 4
  store %struct.C* %c, %struct.C** %c.addr, align 4
  %0 = load %struct.C** %c.addr, align 4
  %1 = bitcast %struct.C* %0 to i8*
  %2 = getelementptr inbounds i8* %1, i32 0
  %this.adjusted = bitcast i8* %2 to %struct.C*
  call x86_thiscallcc void @"\01??_9C@@$BA at AE"(%struct.C* %this.adjusted, i32
0)
  %3 = load %struct.C** %c.addr, align 4
  %4 = bitcast %struct.C* %3 to i8*
  %5 = getelementptr inbounds i8* %4, i32 4
  %this.adjusted1 = bitcast i8* %5 to %struct.C*
  call x86_thiscallcc void bitcast (void (%struct.C*, i32)* @"\01??_9C@@$BA at AE"
to void (%struct.C*, i32, i32)*)(%struct.C* %this.adjusted1, i32 0, i32 0)
  ret void
}

define linkonce_odr x86_thiscallcc void @"\01??_9C@@$BA at AE"(%struct.C* %this,
i32) unnamed_addr #0 align 2 {
entry:
  %.addr = alloca i32, align 4
  %this.addr = alloca %struct.C*, align 4
  store i32 %0, i32* %.addr, align 4
  store %struct.C* %this, %struct.C** %this.addr, align 4
  %this1 = load %struct.C** %this.addr
  %1 = bitcast %struct.C* %this1 to void (%struct.C*, i32)***
  %vtable = load void (%struct.C*, i32)*** %1
  %vfn = getelementptr inbounds void (%struct.C*, i32)** %vtable, i64 0
  %2 = load void (%struct.C*, i32)** %vfn
  %3 = load i32* %.addr, align 4
  call x86_thiscallcc void %2(%struct.C* %this1, i32 %3)
  ret void
}

The second call to the thunk is either UB, or is supplying a dead argument
which gets removed by DAE.  Either way, the program is broken.

There are two ways we can proceed, each with tradeoffs:

1. Give up on MSVC's mangling and mangle in the function prototype. Clang will
continue working with minimal changes, but now you will be unable to compare
pointers to virtual member functions from TUs compiled by clang with TUs
compiled by MSVC. This may be a good temporary solution.

2. Continue implementing perfect forwarding in an LLVM-typesafe way. We already
want to be able to forward an ellipsis for Itanium variadic vtable thunks. In
MSVC, the 'this' pointer always comes first, even if a hidden sret parameter is
added, so we could give all vmemptr thunks the type "i8* (i8*, ...)", and slap
musttail on the delegating call. Hypothetically, we could learn to inline
through this. This requires a bit of finesse with the return type, but my
understanding is that methods always return using sret, and can never return an
i64 in eax:edx, for example. We will have to investigate x86_64, though, which
might have different method return type rules.

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20140813/b775e5d4/attachment.html>


More information about the llvm-bugs mailing list