LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 20653 - MS ABI: Mangling collision for pointers to virtual member functions of multiple inheritance classes
Summary: MS ABI: Mangling collision for pointers to virtual member functions of multip...
Status: RESOLVED FIXED
Alias: None
Product: clang
Classification: Unclassified
Component: LLVM Codegen (show other bugs)
Version: unspecified
Hardware: PC Windows NT
: P normal
Assignee: Reid Kleckner
URL:
Keywords:
Depends on:
Blocks: 12477 18887
  Show dependency tree
 
Reported: 2014-08-13 15:35 PDT by Reid Kleckner
Modified: 2015-09-07 01:05 PDT (History)
4 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Reid Kleckner 2014-08-13 15:35:46 PDT
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@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@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@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.
Comment 1 Reid Kleckner 2014-09-02 17:19:17 PDT
Fixed in r216782.