Akim Demaille
2014-Jun-02 07:07 UTC
[LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
[Was initially posted on cfe-users, sorry.]
Hi,
I'm sorry my message is quite long, the TL;DR version is "g++ and
clang++ seem to have different opinions on how RTTI, templates, and ELF
visibility should interact".
I can't tell whether this is a bug or not: I have found no relevant
documentation that could help me decide whether this behavior is meant, or not.
All I can say is that the current behavior is not the one I would expect, but
maybe you guys have a different opinion, which I'd be happy to hear about.
To my eyes it looks like a violation of the One Definition Rule, but since ELF
visibility issues are not covered by the standard, this is wishful thinking :)
I'm reproducing here basically what I had written there:
http://stackoverflow.com/questions/19496643/
-------------------------------------------------
This is a scaled down version of a problem I am facing with clang++ on Mac OS X.
The failure
==========
I have this big piece of software in C++ with a large set of symbols in the
object files, so I'm using `-fvisibility=hidden` to keep my symbol tables
small. It is well known that in such a case one must pay extra attention to the
vtables, and I suppose I face this problem. I don't know however, how to
address it elegantly in a way that pleases both gcc and clang.
Consider a `base` class which features a down-casting operator, `as`, and a
`derived` class template, that contains some payload. The pair
`base`/`derived<T>` is used to implement type-erasure:
// foo.hh
#define API __attribute__((visibility("default")))
struct API base
{
virtual ~base() {}
template <typename T>
const T& as() const
{
return dynamic_cast<const T&>(*this);
}
};
template <typename T>
struct API derived: base
{};
struct payload {}; // *not* flagged as "default visibility".
API void bar(const base& b);
API void baz(const base& b);
Then I have two different compilation units that provide a similar service,
which I can approximate as twice the same feature: down-casting from `base` to
`derive<payload>`:
// bar.cc
#include "foo.hh"
void bar(const base& b)
{
b.as<derived<payload>>();
}
and
// baz.cc
#include "foo.hh"
void baz(const base& b)
{
b.as<derived<payload>>();
}
>From these two files, I build a dylib. Here is the `main` function, calling
these functions from the dylib:
// main.cc
#include <stdexcept>
#include <iostream>
#include "foo.hh"
int main()
try
{
derived<payload> d;
bar(d);
baz(d);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
Finally, a Makefile to compile and link everybody. Nothing special here,
except, of course, `-fvisibility=hidden`.
CXX = clang++
CXXFLAGS = -std=c++11 -fvisibility=hidden
all: main
main: main.o bar.dylib baz.dylib
$(CXX) -o $@ $^
%.dylib: %.cc foo.hh
$(CXX) $(CXXFLAGS) -shared -o $@ $<
%.o: %.cc foo.hh
$(CXX) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
The run succeeds with gcc (4.8 and 4.9) on OS X:
$ make clean && make CXX=g++-mp-4.8 && ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c main.cc -o main.o
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
g++-mp-4.8 -o main main.o bar.dylib baz.dylib
However with clang (3.4 and 3.5), this fails (the typeids have different
addresses):
$ make clean && make CXX=clang++-mp-3.4 && ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c main.cc -o main.o
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
std::bad_cast
However it works if I tag my payload with a public visibility:
struct API payload {};
but I do not want to expose the payload type. So my questions are:
1. why are GCC and Clang different here?
2. is it _really_ working with GCC, or I was just "lucky" in my use of
undefined behavior?
3. do I have a means to avoid making `payload` go public with Clang++?
Thanks in advance.
Type equality of visible class templates with invisible type parameters (EDIT)
=============================================================================
I have now a better understanding of what is happening. It is appears that both
GCC _and_ clang require both the class template and its parameter to be visible
(in the ELF sense) to build a unique type. If you change the `bar.cc` and
`baz.cc` functions as follows:
// bar.cc
#include "foo.hh"
void bar(const base& b)
{
std::cerr
<< "bar value: " << &typeid(b) <<
std::endl
<< "bar type: " <<
&typeid(derived<payload>) << std::endl
<< "bar equal: " << (typeid(b) ==
typeid(derived<payload>)) << std::endl;
b.as<derived<payload>>();
}
and *if* you make `payload` visible too:
struct API payload {};
then both GCC and Clang succeed (same typeid address):
$ make clean && make CXX=g++-mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
./g++-mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x106785140
bar type: 0x106785140
bar equal: 1
baz value: 0x106785140
baz type: 0x106785140
baz equal: 1
$ make clean && make CXX=clang++-mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x10a6d5110
bar type: 0x10a6d5110
bar equal: 1
baz value: 0x10a6d5110
baz type: 0x10a6d5110
baz equal: 1
Type equality is easy to check, there is actually a single instantiation of the
type, as witnessed by its unique address.
However, if you remove the visible attribute from `payload`:
struct payload {};
then you get with GCC:
$ make clean && make CXX=g++-mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
g++-mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x10faea120
bar type: 0x10faf1090
bar equal: 1
baz value: 0x10faea120
baz type: 0x10fafb090
baz equal: 1
Now there are several instantiation of the type `derived<payload>` (as
witnessed by the three different addresses), but GCC sees these types are equal,
and (of course) the two `dynamic_cast` pass.
In the case of clang, it's different:
$ make clean && make CXX=clang++-mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
.clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x1012ae0f0
bar type: 0x1012b3090
bar equal: 0
std::bad_cast
There are also three instantiations of the type (removing the failing
`dynamic_cast` does show that there are three), but this time, they are not
equal, and the `dynamic_cast` (of course) fails.
Now the question turns into:
1. is this difference between both compilers wanted by their authors
2. if not, what is "expected" behavior between both
I prefer GCC's semantics, as it allows to really implement type-erasure
without any need to expose publicly the wrapped types.
Rafael Espíndola
2014-Jun-04 22:32 UTC
[LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
I think the difference is actually in the c++ library. It looks like libstdc++ changed to always use strcmp of the typeinfo names: https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964 Should we do the same with libc++? On 2 June 2014 03:07, Akim Demaille <akim.demaille at gmail.com> wrote:> [Was initially posted on cfe-users, sorry.] > > Hi, > > I'm sorry my message is quite long, the TL;DR version is "g++ and clang++ seem to have different opinions on how RTTI, templates, and ELF visibility should interact". > > > > I can't tell whether this is a bug or not: I have found no relevant documentation that could help me decide whether this behavior is meant, or not. All I can say is that the current behavior is not the one I would expect, but maybe you guys have a different opinion, which I'd be happy to hear about. To my eyes it looks like a violation of the One Definition Rule, but since ELF visibility issues are not covered by the standard, this is wishful thinking :) > > I'm reproducing here basically what I had written there: > > http://stackoverflow.com/questions/19496643/ > > ------------------------------------------------- > > This is a scaled down version of a problem I am facing with clang++ on Mac OS X. > > The failure > ==========> > I have this big piece of software in C++ with a large set of symbols in the object files, so I'm using `-fvisibility=hidden` to keep my symbol tables small. It is well known that in such a case one must pay extra attention to the vtables, and I suppose I face this problem. I don't know however, how to address it elegantly in a way that pleases both gcc and clang. > > Consider a `base` class which features a down-casting operator, `as`, and a `derived` class template, that contains some payload. The pair `base`/`derived<T>` is used to implement type-erasure: > > // foo.hh > > #define API __attribute__((visibility("default"))) > > struct API base > { > virtual ~base() {} > > template <typename T> > const T& as() const > { > return dynamic_cast<const T&>(*this); > } > }; > > template <typename T> > struct API derived: base > {}; > > struct payload {}; // *not* flagged as "default visibility". > > API void bar(const base& b); > API void baz(const base& b); > > > Then I have two different compilation units that provide a similar service, which I can approximate as twice the same feature: down-casting from `base` to `derive<payload>`: > > // bar.cc > #include "foo.hh" > void bar(const base& b) > { > b.as<derived<payload>>(); > } > > and > > // baz.cc > #include "foo.hh" > void baz(const base& b) > { > b.as<derived<payload>>(); > } > > From these two files, I build a dylib. Here is the `main` function, calling these functions from the dylib: > > // main.cc > #include <stdexcept> > #include <iostream> > #include "foo.hh" > > int main() > try > { > derived<payload> d; > bar(d); > baz(d); > } > catch (std::exception& e) > { > std::cerr << e.what() << std::endl; > } > > Finally, a Makefile to compile and link everybody. Nothing special here, except, of course, `-fvisibility=hidden`. > > CXX = clang++ > CXXFLAGS = -std=c++11 -fvisibility=hidden > > all: main > > main: main.o bar.dylib baz.dylib > $(CXX) -o $@ $^ > > %.dylib: %.cc foo.hh > $(CXX) $(CXXFLAGS) -shared -o $@ $< > > %.o: %.cc foo.hh > $(CXX) $(CXXFLAGS) -c -o $@ $< > > clean: > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > > The run succeeds with gcc (4.8 and 4.9) on OS X: > > $ make clean && make CXX=g++-mp-4.8 && ./main > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > g++-mp-4.8 -std=c++11 -fvisibility=hidden -c main.cc -o main.o > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > g++-mp-4.8 -o main main.o bar.dylib baz.dylib > > However with clang (3.4 and 3.5), this fails (the typeids have different addresses): > > $ make clean && make CXX=clang++-mp-3.4 && ./main > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c main.cc -o main.o > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > clang++-mp-3.4 -o main main.o bar.dylib baz.dylib > std::bad_cast > > However it works if I tag my payload with a public visibility: > > struct API payload {}; > > but I do not want to expose the payload type. So my questions are: > > 1. why are GCC and Clang different here? > 2. is it _really_ working with GCC, or I was just "lucky" in my use of undefined behavior? > 3. do I have a means to avoid making `payload` go public with Clang++? > > Thanks in advance. > > Type equality of visible class templates with invisible type parameters (EDIT) > =============================================================================> > I have now a better understanding of what is happening. It is appears that both GCC _and_ clang require both the class template and its parameter to be visible (in the ELF sense) to build a unique type. If you change the `bar.cc` and `baz.cc` functions as follows: > > // bar.cc > #include "foo.hh" > void bar(const base& b) > { > std::cerr > << "bar value: " << &typeid(b) << std::endl > << "bar type: " << &typeid(derived<payload>) << std::endl > << "bar equal: " << (typeid(b) == typeid(derived<payload>)) << std::endl; > b.as<derived<payload>>(); > } > > and *if* you make `payload` visible too: > > struct API payload {}; > > then both GCC and Clang succeed (same typeid address): > > $ make clean && make CXX=g++-mp-4.8 > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > ./g++-mp-4.8 -o main main.o bar.dylib baz.dylib > $ ./main > bar value: 0x106785140 > bar type: 0x106785140 > bar equal: 1 > baz value: 0x106785140 > baz type: 0x106785140 > baz equal: 1 > > $ make clean && make CXX=clang++-mp-3.4 > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > clang++-mp-3.4 -o main main.o bar.dylib baz.dylib > $ ./main > bar value: 0x10a6d5110 > bar type: 0x10a6d5110 > bar equal: 1 > baz value: 0x10a6d5110 > baz type: 0x10a6d5110 > baz equal: 1 > > Type equality is easy to check, there is actually a single instantiation of the type, as witnessed by its unique address. > > However, if you remove the visible attribute from `payload`: > > struct payload {}; > > then you get with GCC: > > $ make clean && make CXX=g++-mp-4.8 > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > g++-mp-4.8 -o main main.o bar.dylib baz.dylib > $ ./main > bar value: 0x10faea120 > bar type: 0x10faf1090 > bar equal: 1 > baz value: 0x10faea120 > baz type: 0x10fafb090 > baz equal: 1 > > Now there are several instantiation of the type `derived<payload>` (as witnessed by the three different addresses), but GCC sees these types are equal, and (of course) the two `dynamic_cast` pass. > > In the case of clang, it's different: > > $ make clean && make CXX=clang++-mp-3.4 > rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc > clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc > .clang++-mp-3.4 -o main main.o bar.dylib baz.dylib > $ ./main > bar value: 0x1012ae0f0 > bar type: 0x1012b3090 > bar equal: 0 > std::bad_cast > > There are also three instantiations of the type (removing the failing `dynamic_cast` does show that there are three), but this time, they are not equal, and the `dynamic_cast` (of course) fails. > > Now the question turns into: > 1. is this difference between both compilers wanted by their authors > 2. if not, what is "expected" behavior between both > > I prefer GCC's semantics, as it allows to really implement type-erasure without any need to expose publicly the wrapped types. > _______________________________________________ > LLVM Developers mailing list > LLVMdev at cs.uiuc.edu http://llvm.cs.uiuc.edu > http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
Joerg Sonnenberger
2014-Jun-04 23:08 UTC
[LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
On Wed, Jun 04, 2014 at 06:32:35PM -0400, Rafael Espíndola wrote:> I think the difference is actually in the c++ library. It looks like > libstdc++ changed to always use strcmp of the typeinfo names: > > https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964 > > Should we do the same with libc++?No. It pessimizes correctly behaving code to work around code that violates the ELF ABI. Consider an application that loads plugins with RTLD_LOCAL. Two plugins implement a (hidden) type called "Plugin". The GCC change now makes this completely separate types identical for all RTTI purposes. That makes no sense at all. Joerg
Akim Demaille
2014-Jun-13 18:34 UTC
[LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
Le 5 juin 2014 à 00:32, Rafael Espíndola <rafael.espindola at gmail.com> a écrit :> I think the difference is actually in the c++ library. It looks like > libstdc++ changed to always use strcmp of the typeinfo names: > > https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964 > > Should we do the same with libc++?What do people think about this issue?
Seemingly Similar Threads
- [LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
- [LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
- [LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
- [LLVMdev] -fvisibility=hidden, and typeinfo, and type-erasure
- [LLVMdev] -O4 -fvisibility=hidden