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

Make the default symbol visibility hidden for unixen builds #868

Merged

Conversation

kdt3rd
Copy link
Contributor

@kdt3rd kdt3rd commented Nov 22, 2020

This only applies when building shared libraries, but switches to a
symbol management strategy which is more in line with Windows, where
symbols must be explicitly exported (marked as default visibilty). This
allows us to be much more explicit and careful about what symbols are
being exposed, making our .so files tidier

Signed-off-by: Kimball Thurston kdt3rd@gmail.com

@kdt3rd kdt3rd force-pushed the build_exr_with_hidden_symbols branch from 60e8e0e to 795df2c Compare November 22, 2020 09:50
@kdt3rd kdt3rd changed the title This commit makes the default symbol visibility hidden for unixen builds Makes the default symbol visibility hidden for unixen builds Nov 22, 2020
@kdt3rd kdt3rd changed the title Makes the default symbol visibility hidden for unixen builds Make the default symbol visibility hidden for unixen builds Nov 22, 2020
@@ -246,14 +237,14 @@ struct StreamIO

struct CharPtrIO
{
static void
static inline void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think any method fully defined inside a struct or class declaration is implicitly inline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@lgritz
Copy link
Contributor

lgritz commented Nov 22, 2020

There are some failures on the Mac CI test. Not sure if it's related to this PR.

@@ -57,60 +57,60 @@ IEX_INTERNAL_NAMESPACE_HEADER_ENTER
// Our most basic exception class
//-------------------------------

class BaseExc: public std::exception
class IEX_EXPORT BaseExc: public std::exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few years ago we painstakingly went from this form to the individual export form, because classes contained things, such as std::string that are not adorned for export. This led to subtle corruption style bugs when using OpenEXR as a DLL. One of the root causes of the nightmare was that BaseExc was sublcassed from std::string. I'm extremely nervous about undoing this change! Are any warnings about "some members are not decorated for export" emitted during compilation? One of the many symptoms was that exception handling never worked on Windows; OpenEXR exceptions segfaulted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue that resulted was that symbol tables become ungodly huge, base exporting the entire class exports way more than you would expect, including symbols for any std objects within the class. Perhaps Microsoft has refined things and it's no longer the can of worms that it was? ... nervous,,,, it took a long time to figure out why Windows was so unstable and correct it....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you should never believe me about Windows things, but I'm quite certain that both OIIO and OSL use this method of tagging the whole class as exportable, and not the individual methods therein, and I have not had any reported problems. Maybe this was an issue that was fixed in MSVS long ago?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no warnings I've seen on any of the platform compiles about things not adorned for export on any of the CI builds, but I think the failure on the mac is due to me missing something about that for that platform - exceptions are notoriously difficult in this realm, so I think one / some of the Imf exceptions are not being exported correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We recently did a pass on USD to do it the pain-staking member-by-member way, rather than class by class, because we ran into, in particular, that since we have vector, vector was being re-exported as somehow being owned by USD libs. The root problem boils down to STL classes being re-exported by client libraries, and the fact that historically the Microsoft STL relied on static member initialization which breaks down fiendishly in a DLL based world. A quick scan of https://github.com/microsoft/STL suggests that they no longer rely on static initializers. Still, there may be a symbol table explosion due to undesired STL exports that the painstaking method avoids. At ILM, the historic motivation to do it the nasty way was that between boost and the STL the Zeno symbol table became too huge and we had to do fairly frequent refactorings to get templated symbols below the MSVC crash threshold.

All this is to say that I'm inclined to the simpler suggestion you're proposing, if the symbol table sizes don't increase by integer multiples of the current size, and if exceptions thrown under Windows don't go back to segfaulting.

@@ -55,6 +55,8 @@ function(OPENEXR_DEFINE_LIBRARY libname)
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
C_VISIBILITY_PRESET hidden
Copy link
Contributor

@meshula meshula Nov 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the visibility preset to hidden is necessary for the export attributes to mean anything under gcc/clang, but I want to point out that the existing set up of exporting individual members as we did is compatible with that approach, and should work in and of itself without changing the export mechanism on all the classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, my original goal was to just change to turn on the hidden flag (and change the export defines, similar to what USD does) to avoid symbol leakage - we've had some issues with this recently in other libraries, but seemed an appropriate opportunity to become tidier about what symbols we expose to the world.

However, once I set the default visibility to hidden, I was having issues with vtables not being exported consistently, even though the destructor was exported - I think something is/was off with default provided functions (i.e. rule of 3/5) and visibility. I do see what you mean by it exporting contained types as weak dynamic symbols, I did not expect that.

I will explore more why I was having issues with the vtables, to be able to clean that up, and maybe take a different stab at this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that gcc doesn't export the vtable. There's a proposal in the USD Issues which contains a lot more analysis of the horrible state of shared libraries in general. It also has me relating some of the OpenEXR experience...

PixarAnimationStudios/OpenUSD#665

#if HAVE_GCC_OR_CLANG
#define FOO_EXPORT_API the visibility thing
#else
#define FOO_EXPORT_API
#endif

class FOO_EXPORT_API Foo
{
     int foo(); // not exported
     FOO_EXPORT int bar(); // exported
};

In general, this stuff is a world of hurt. More "fascinating" investigation here, wrt pybind11: pybind/pybind11#2132

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading the USD issue and

https://gcc.gnu.org/onlinedocs/gcc/Vague-Linkage.html

I think it makes the most sense to follow the basic guidelines outlined in the USD issue, and have implemented similar constructs here. I also put a brief doxygen description of the issue in IexExports.h, and put all the other export headers into the same export macros group

@kdt3rd
Copy link
Contributor Author

kdt3rd commented Feb 15, 2021

hrm, I am not sure what github has done in running the CI, it seems to have merged in master because it detected a conflict. Warning to be careful during merge...

@meshula
Copy link
Contributor

meshula commented Feb 15, 2021

@kdt3rd This is looking good. Seeing your solution withe "vague" linkage, reminds me of a pattern I used in some code at ILM where something similar came up ago in aeons past --- I used, equivalently to IEX_EXPORT_VAGUELINKAGE, IEX_EXPORT_CLASS, to capture that it was an export macro, and where it was intended to be used. I wonder if using CLASS might help down the line in years to come, if we are thinking about where VAGUELINKAGE might come into play, and if it's safe to use it in other contexts. EXPORT_CLASS would preemptively answer that hypothetical question.

@kdt3rd
Copy link
Contributor Author

kdt3rd commented Feb 16, 2021

@meshula - so you'll notice the CI is failing under Mac. It is unfortunately at runtime, and basically, the types of the "known" attribute types are being hidden inside the dylibs, and the dynamic RTTI comparison is failing when attributes are being set into the exr header because they are created in a separate module, and so result in a separate type. Where g++ does something different. This is the best explanation of what is happening I've been able to find:

https://stackoverflow.com/questions/19496643/using-clang-fvisibility-hidden-and-typeinfo-and-type-erasure

So I think there is going to have to be a multi-stage response to this in order to get to the full solution. The first I'll put in will be to use something similar to the dynamic cast work around suggested in the above. This can be encapsulated in the (existing) cast function of TypedAttribute. This ends up comparing the names after a few other checks (which is apparently what libstdc++ does, which I agree w/ the llvm/libc++ guys, is a bit dodgy, but hey, represents some definition of truth). Then, once this change is propagated to Imath as well, where we can tag the template classes with an export properly, then we can expose the other types inside OpenEXR that are put into TypedAttribute, but passed in from external (i.e. TypedAttributeImath::vec2i and you have to even expose the enums such as enum EnvMap). Then it will all work under libc++ (it's not really an issue w/ clang, clang under linux works just fine without libc++) - I've been able to make incremental changes, and move the problem. Which is making me re-examine what's really wrong.

I should note that this problem is contained and not unbounded, in that we are dynamic casting the attributes to the type we think we know (but are actually ending up as different typeid values, which is pedantically correct). A similar change will be needed in OpenEXRUtil for the image / channel sub types, but that is self-contained there as well.

I do agree that EXPORT_VAGUELINKAGE is a bit ... vague? but it gives an obvious search term to start the journey of discovery. However, I agree that something like an EXPORT_TYPE might be better (or EXPORT_CLASS, but given I'll need to add this eventually to the enums, thought TYPE was clearer). So I think I'll switch it to TYPE.

@kdt3rd kdt3rd force-pushed the build_exr_with_hidden_symbols branch from 1b2b519 to 07cf5bb Compare March 14, 2021 00:45
This only applies when building shared libraries, but switches to a
symbol management strategy which is more in line with Windows, where
symbols must be explicitly exported (marked as default visibilty). This
allows us to be much more explicit and careful about what symbols are
being exposed, making our .so files tidier

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
This removes the change to just export the classes for some classes
where typeinfo is needed, and adds two new macros EXPORT_VAGUELINKAGE
and EXPORT_LOCAL that expose the classes as needed for Unix builds which
have visibility hidden set

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
… for Windows

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
With symbol visibility=hidden, type erasure can happen much more
readily, such that when we have a routine compiled into the library, it
may have "different" types than the ones passed across the interface.
These are semantics in some sense, and there is some disagreement
between libstdc++ and libc++ how dynamic_cast works. To avoid issue,
provide custom dynamic_cast functions for converting attributes and
utility image channel types

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
the VAGUELINKAGE was deemed a bit too vague, and not particularly
indicative, where it is really the type information that needs to be
exported. Given that this macro will have to be used on enum and other
things eventually if we want full hiding and to still retain RTTI
information accurately, call the macro EXPORT_TYPE

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
…th other headers

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
This changes the headers such that all the ImfForward-declared things
have ImfForward.h included first, such that ImfForward is truly ahead of
all of them, which allows us to tag the symbol visibility correctly and
avoid warnings. This also reduces header bloat throughout the library.

Further, this change documents a more proper path for controlling symbol
visibility. It switches things to using extern template instantiation
for the attribute types, which should lower code bloat overall. The
OpenEXR libraries are now compiled fully with hidden symbols by default
as well as hidden inlines by default per the generally recommended
behavior (-fvisibility=hidden -fvisibility-inlines-hidden via cmake).

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
…ay we need

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Also add missing include guards, fix up documentation after findings

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
@kdt3rd kdt3rd force-pushed the build_exr_with_hidden_symbols branch from f0640f6 to 6cf6719 Compare March 14, 2021 04:14
…n as Deep

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
#define ILMTHREAD_EXPORT __declspec(dllimport)
#define ILMTHREAD_EXPORT_CONST extern __declspec(dllimport)
#endif
# if defined(ILMTHREAD_EXPORTS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These #defines don't exactly mirror the IEX ones, is that intentional? There's no OPENEXR_USE_DEFAULT_VISIBILITY here. Should the two libraries follow the same structure?

# define ILMTHREAD_EXPORT_TYPE __attribute__ ((__visibility__ ("default")))
# define ILMTHREAD_HIDDEN __attribute__ ((__visibility__ ("hidden")))
# else
# define ILMTHREAD_EXPORT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the old files somehow had tabs in the spaces (my editor is spaces only)


struct Data;
struct ILMTHREAD_HIDDEN Data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, if the default is hidden, why do we have to mark this explicitly as HIDDEN?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the outer class has been marked as public (default) such that it's typeinfo is exported, so I am forcing this one back to hidden. It may not be necessary on all platforms, but seemed safer.

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>

## Overview

Managing symbol visibility in a C++ library can reduce library sizes,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice explanation!


#ifndef COMPILING_IMF_BOX_ATTRIBUTE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This idiom occurs for every attribute type, it's worth explaining exactly what's going on here. Why do you need to avoid these declarations in the .cpp?



OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER

//#if defined(__MINGW32__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out lines.



OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER

using namespace OPENEXR_IMF_INTERNAL_NAMESPACE;

//#if defined(__MINGW32__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out lines.

#define IMFUTIL_EXPORT
#define IMFUTIL_EXPORT_CONST extern const

# if defined(OPENEXRUTIL_EXPORTS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per earlier comments, is there any reason to not just use OPENEXR_EXPORTS? Does OpenEXRUtil really need its own distinct macros?

Copy link
Member

@cary-ilm cary-ilm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked it over and it seems reasonable, although I hope there's a way of consolidating the various sets of export macros, for Imath, Iex, IlmThread, OpenEXR, since they're all doing the same basic thing but with slight variations. Are the variations intentional, or oversights, or misinterpretations on my part?

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
@kdt3rd kdt3rd merged commit 13a2df2 into AcademySoftwareFoundation:master Mar 15, 2021
@cary-ilm cary-ilm added Build A problem with building or installing the library. Documentation Developer guide, web site, project policies, etc. v3.0.0-beta labels Mar 21, 2021
@kdt3rd kdt3rd deleted the build_exr_with_hidden_symbols branch February 12, 2022 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Build A problem with building or installing the library. Documentation Developer guide, web site, project policies, etc. v3.0.0-beta
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants