-
Notifications
You must be signed in to change notification settings - Fork 1.1k
build: Add SECP256K1_FORCE_HIDDEN_VISIBILITY #1677
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
base: master
Are you sure you want to change the base?
build: Add SECP256K1_FORCE_HIDDEN_VISIBILITY #1677
Conversation
aa2448a
to
5f8dbfe
Compare
include/secp256k1.h
Outdated
* | ||
* While visibility is a concept that applies only to shared libraries, setting | ||
* visibility will still make a difference when building a static library: the | ||
* visibility settings will be stored in the static library, solely for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind sharing more details regarding "the visibility settings will be stored in the static library" please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://stackoverflow.com/a/67473340/2725281. I don't think I have more to share, but I assume @theuni can confirm the behavior.
Like #1674, this defaults to "default" (haha) visibility. While not perfect for static builds -- we'd probably prefer a default of "hidden" there -- this avoids any attempt to detection in the preprocessor whether a static or a shared library is built. Some kind of detection is possible (see my comment in #1674), but it will most likely never be perfect, and some manual override will be necessary anyway. Defaulting to "default" avoids the need to detect and is thus much simpler. |
Hmm. There's a pretty common assumption that when using So I think many packagers would run into problems with this, as that flag (which is usually considered safe for visibility-aware libs) would essentially produce broken shared libs (except on Windows). |
Hm, that assumption sounds misguided to me. If a packager expects a visibility-aware lib, they should let the lib set the flags... Whatever, I think it's easy to address this concern by setting "default" in an else branch: #ifndef SECP256K1_API
# if defined(__GNUC__) && (__GNUC__ >= 4) && defined(SECP256K1_BUILD)
# if defined(SECP256K1_FORCE_HIDDEN_VISIBILITY)
# define SECP256K1_API extern __attribute__ ((visibility ("hidden")))
# else
# define SECP256K1_API extern __attribute__ ((visibility ("default")))
# endif
# endif
#endif What do you think? |
libsecp's in a pretty unique position here because it has no private non-static functions and only a handful of private vars. Most libs have a huge pile of functions and marking them all as hidden would be infeasible. Hence the compiler flag/default override combo. Edit:
Sure, it's not conventional, but it gets the job done :) |
Ok, I can update the PR tomorrow. :)
Yep. So for visibility-aware libs, it's a noop, and for non-aware libs, it breaks the build. Sounds like an option you don't wanna have in your toolchain file. ;) Okay, just kidding, I guess there may be aware libs which leave setting the compiler detault to the user. |
Not sure yet what to think of the two approaches, but it does strike me as very unfortunate that we are introducing more bespoke behaviour yet again. Can we at least only allow this being set when |
I am not sure how bespoke this is, in the end. I think the ability to configure visibility for static builds is a valid use case in general, not specific to Bitcoin Core's planned use. It's just pretty niche... (That's different from #1678, which is merely a hack that works around a lack of functionality in CMake.) edit: But if the goal is to avoid the need for any of these, then I believe the proper way is to abandon the idea of building a monolithic kernel. IIUC, then you won't need to the CMake workaround, and you can use
In both PRs, the override is currently just an |
5f8dbfe
to
42d3133
Compare
Force-pushed this update. Here's another variant of that approach: real-or-random@2d92ac3 Instead of an override to force "hidden", this has an override to do nothing. Then the user can use |
* solely for the potential case that the static library will be linked into | ||
* a shared library. In that case, the stored visibility settings will | ||
* resurface and be honored for the shared library. */ | ||
# define SECP256K1_API extern __attribute__ ((visibility("hidden"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Win32 needs this branch too, no? Otherwise it'll still require -fvisibility=hidden
for static libs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, maybe #if defined(SECP256K1_FORCE_HIDDEN_VISIBILITY)
could just go at the top, and keep with the #ifndef(SECP256K1_API)
pattern below it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Win32 needs this branch too, no? Otherwise it'll still require
-fvisibility=hidden
for static libs?
I don't think so (but I haven't tested this so far). As far as I understand, gcc defaults to hidden on Windows as soon as it sees an explicit __declspec(dllexport)
annotation (and MSVC anyway exports only annotated symbols). At least this is what the libtool manual claims:
It should be noted that the GNU auto-export feature is turned off when an explicit
__declspec(dllexport)
is seen. The GNU tools do this to not make more symbols visible for projects that have already taken the trouble to decorate symbols.
https://www.gnu.org/software/libtool/manual/html_node/Windows-DLLs.html
Actually, maybe
#if defined(SECP256K1_FORCE_HIDDEN_VISIBILITY)
could just go at the top, and keep with the#ifndef(SECP256K1_API)
pattern below it?
Not sure if I can follow. If you still think we'll need this for Windows, can you suggest/sketch a patch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 42d3133
@TheCharlatan @hebasto If this is still needed for kernel, can you review this approach? |
NACK I find the deeply nested #if defined(_WIN32) || defined(__CYGWIN__)
# define SECP256K1_EXPORT __declspec(dllexport)
# define SECP256K1_IMPORT __declspec(dllimport)
#else
# define SECP256K1_EXPORT __attribute__ ((visibility ("default")))
# define SECP256K1_IMPORT __attribute__ ((visibility ("default")))
#endif And then use that abstraction for defining the API: #if defined(SECP256K1_STATIC)
# define SECP256K1_API
#elif defined(SECP256K1_BUILD)
# define SECP256K1_API SECP256K1_EXPORT
#else
# define SECP256K1_API SECP256K1_IMPORT
#endif In CMake the two preprocessor definitions are set like this: target_compile_definitions(libsecp256k1 PUBLIC
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,STATIC_LIBRARY>:SECP256K1_STATIC>
)
set_target_properties(libsecp256k1 PROPERTIES
DEFINE_SYMBOL SECP256K1_BUILD # if this is not set, it defaults to libsecp256k1_EXPORTS
) Note that
The result is that there are three modes that the header file can be interpreted in:
The advantage of that approach is that the information of how clients need to consume the library is tied to the library interface. There are no extra flags that clients need to set. It is impossible for them to get to a situation where the I don't see a reason why this approach shouldn't works for autotools just as well. All that needs to be done is add |
To avoid name collisions when libraries form diamond of death dependencies, it might be good to provide a way for clients to define their own "namespace" (prefix) for public symbols. An example library that supports not one, but two approaches for this use case is https://gitlab.kitware.com/utils/kwsys. To understand how that is used, look at https://github.com/Kitware/CMake/blob/master/Source/kwsys/String.h.in for example. |
This comment was marked as outdated.
This comment was marked as outdated.
While testing this PR as part of an integration with Bitcoin Core, I've noticed some inconvenience in having to set the extra |
This is exactly what I meant. Downstream users need to match the preprocessor define and the linked library. There are four possible combinations, and two of them cause problems. Usage requirements should be tied to the library. |
Well, the goal of this PR and of #1674 is precisely to enable the user to control visibility for static builds. |
The fact that the packager uses autotools does not imply that the consumer will use pkgconfig. Our current logic attempts to make it possible to consume the library (at least on Unix-like) as a static library or as a dynamic library without the need to set any defines. This is broadly useful for library consumers, independent of their build system and of the build system of the library. The current code is based on https://www.gnu.org/software/libtool/manual/html_node/Windows-DLLs.html and https://gcc.gnu.org/wiki/Visibility, but has been refined since. I agree that the logic is convoluted, and it may be possible to improve this. But I think that's partly due to the fact that the build toolchains are convoluted, in particular if you want to support many platforms. |
The name should make it clear that this is intended for ELF platforms only.
42d3133
to
2a5cf8c
Compare
So this PR is based on the assumption that static library packagers/builders typically don't want hidden visibility. Moreover, we didn't set hidden in the past for static builds, so keeping this behavior is a bit more conservative because it won't break existing downstream builds that rely on this (if there are any, I don't know.) If Core wants different. It needs to set a flag, I think that's okay. But perhaps the assumption is wrong? I'm not sure, and I think either way is somewhat sane. Consider some libxyz that uses libsecp256k1. Then it's probably okay for it not to export our public API. But also, there's nothing really wrong with re-exporting our public API. The code is there anyway, so why not make it available to the consumer of libxyz? The only problem that may occur is that libxyz also wants to use libsecp256k1, and it wants to use a different version. Then you'll get name collisions, and this is what @purpleKarrot mentioned above... But if you really run into this problem, then you still get a flag |
There is no valid use case of On Windows, the situation is clear: When building a On other platforms that support visibility, there are three options:
But when you use the above define and keep the compiler's default visibility preset, it could lead to a situation where you export all the private functions and none of the public ones. Further, if portability to Windows is important, then option 1 is the only possible approach anyway. |
I see. On Windows this is impossible, because clients need to know whether they need to import or not. On Unix-like, this is not possible either, when clients want to change the visibility preset (aka opt into Windows behavior), which is exactly what is experienced in core, that a Maybe that goal should be dropped in favor of a both simpler and more portable approach? |
Let me rephrase: The API is neither dll-imported nor dll-exported. The libtool manual states:
The use case that we are talking about is to statically link secp256k1 into a library without exposing any of secp256k1's symbols. We don't want to dll-import libsecp's symbols (we want to use the static library) and we don't want to dll-export the symbols (we only want to define our own API). |
This part I agree with. secp defining symbols as hidden is convenient in many cases, but violates the principle of least surprise and makes this all harder to reason about. Agree with @hebasto about this being annoying for downstreams. To be clear, I acked this PR because it solves the problem we (Core static kernel builders) are facing. I don't think it's the most elegant or comprehensive solution, but it does work. |
I think this has two parts. I agree that setting
Yep, both this PR and your #1674 will need some manual See #1696 for yet another approach that we can hopefully all agree on. |
Alternative to #1674.