Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MS ABI: Mangling collision for pointers to virtual member functions of multiple inheritance classes #21027

Closed
rnk opened this issue Aug 13, 2014 · 2 comments
Assignees
Labels
bugzilla Issues migrated from bugzilla clang:codegen

Comments

@rnk
Copy link
Collaborator

rnk commented Aug 13, 2014

Bugzilla Link 20653
Resolution FIXED
Resolved on Sep 07, 2015 01:05
Version unspecified
OS Windows NT
Blocks #12849 #19261
CC @majnemer,@zmodem,@nico

Extended Description

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.

@rnk
Copy link
Collaborator Author

rnk commented Aug 13, 2014

assigned to @rnk

@rnk
Copy link
Collaborator Author

rnk commented Sep 3, 2014

Fixed in r216782.

@llvmbot llvmbot transferred this issue from llvm/llvm-bugzilla-archive Dec 9, 2021
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugzilla Issues migrated from bugzilla clang:codegen
Projects
None yet
Development

No branches or pull requests

1 participant