Skip to content

build: Refactor visibility logic and add override #1696

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

real-or-random
Copy link
Contributor

@real-or-random real-or-random commented Jul 2, 2025

This is less invasive than #1695. The latter might be the right thing in a new library (and then we'd probably not support autotools in the first place), but any semantic change to this code has the potential to create news bug, or at least breakages for downstream users.

This is different from #1677 in that it does not set hidden explicitly. I agree with the comment in #1677 that setting hidden violates the principle of least surprise.

So this similar in spirit to #1674. So I wonder if this should also include
3eef736. I'd say no, fvisibility should then set by the user. But can you, in CMake, set CMAKE_C_VISIBILITY_PRESET from a parent project?

@hebasto
Copy link
Member

hebasto commented Jul 2, 2025

But can you, in CMake, set CMAKE_C_VISIBILITY_PRESET from a parent project?

Yes, we can.

@real-or-random
Copy link
Contributor Author

cc @theuni @purpleKarrot

@theuni
Copy link
Contributor

theuni commented Jul 2, 2025

LGTM.

A few thoughts:

  • Suggest renaming to SECP256K1_NO_FUNCTION_VISIBILITY_ATTRIBUTES because this does not affect SECP256K1_LOCAL_VAR.
  • To address @hebasto's comment, a CMake option could be added to define SECP256K1_NO_FUNCTION_VISIBILITY_ATTRIBUTES during build, so that parent projects can use it rather than having to mess with cflags.
  • If you don't want to go as far as reverting 3eef736, symbols could be hidden by default but override-able.

Those two would add up to:

option(SECP256K1_ENABLE_VISIBILITY_ATTRIBUTES "Enable visibility attributes when compiling the library" ON)
if (NOT DEFINED CMAKE_C_VISIBILITY_PRESET)
  set(CMAKE_C_VISIBILITY_PRESET hidden)
endif()
...
if(NOT SECP256K1_ENABLE_FUNCTION_VISIBILITY_ATTRIBUTES)
  target_compile_definitions(secp256k1 PRIVATE SECP256K1_NO_FUNCTON_VISIBILITY_ATTRIBUTES)
endif() 

But I noticed while testing combinations of the above that it leads to broken outcomes for test binaries when simply disabling the SECP256K1_ENABLE_FUNCTION_VISIBILITY_ATTRIBUTES option, since then by default we'd be building shared libs with hidden visibility and no default attributes for the abi.

To be robust against that, we'd need something like:

option(SECP256K1_ENABLE_VISIBILITY_ATTRIBUTES "Enable visibility attributes when compiling the library" ON)
if (SECP256K1_ENABLE_FUNCTION_VISIBILITY_ATTRIBUTES AND NOT DEFINED CMAKE_C_VISIBILITY_PRESET)
  set(CMAKE_C_VISIBILITY_PRESET hidden)
endif()

Which of course would defeat the purpose. So I propose that we just skip trying to set CMAKE_C_VISIBILITY_PRESET altogether. Parent projects can mess with that when they know what they're doing.

tl;dr: I think this is the best solution so far, but propose adding a helper option to make life easier for parent projects: theuni@e22ed8c

Core would then set CMAKE_C_VISIBILITY_PRESET=hiddden and SECP256K1_ENABLE_FUNCTION_VISIBILITY_ATTRIBUTES=false and be all set.

@real-or-random
Copy link
Contributor Author

Suggest renaming to SECP256K1_NO_FUNCTION_VISIBILITY_ATTRIBUTES because this does not affect SECP256K1_LOCAL_VAR.

True, but we also apply it to variables, e.g., secp256k1_context_static. And the LOCAL_VAR are really internal.

Perhaps SECP256K1_NO_API_VISIBILITY_ATTRIBUTES. Well, if someone has a shorter name that is still clear, please let me know. :)

@real-or-random
Copy link
Contributor Author

real-or-random commented Jul 2, 2025

(edit: nevermind, got it now)

@real-or-random
Copy link
Contributor Author

But I noticed while testing combinations of the above that it leads to broken outcomes for test binaries when simply disabling the SECP256K1_ENABLE_FUNCTION_VISIBILITY_ATTRIBUTES option, since then by default we'd be building shared libs with hidden visibility and no default attributes for the abi.

Right, that's the just same problem as with your #1674.

Which of course would defeat the purpose. So I propose that we just skip trying to set CMAKE_C_VISIBILITY_PRESET altogether. Parent projects can mess with that when they know what they're doing.

tl;dr: I think this is the best solution so far, but propose adding a helper option to make life easier for parent projects: theuni@e22ed8c

Sounds good to me.

@fanquake
Copy link
Member

fanquake commented Jul 3, 2025

cc @purpleKarrot

@purpleKarrot
Copy link
Contributor

I don't get why additional #defines or cmake-options are necessary. As I pointed out in #1677 (comment), there are three different modes the header file needs to be interpreted as. To distinguish between these three cases, two defines are needed: SECP256K1_STATIC and SECP256K1_BUILD.

SECP256K1_BUILD needs to be defined when building the library, no matter whether the library is static or dynamic. This is defined in src/secp256k1.c before including secp256k1.h. The current approach is build system agnostic. No further change needed.

SECP256K1_STATIC needs to be defined when a static library is used or consumed. Both Makefile.am and CMakeLists.txt already mention this name. The only necessary change is to drop the "if (windows)" condition.

(Both cmake and libtool set a define symbol when building a shared library. This symbol alone is insufficient to distinguish between three different cases. When using custom defines, the symbols set by those tools is redundant. The approach of using SECP256K1_STATIC and SECP256K1_BUILD is portable and build system independent.)

@hebasto
Copy link
Member

hebasto commented Jul 3, 2025

I've spent some time re-reading all the other competing PRs, including #1674, #1677, #1695), and discussing the topic offline with @purpleKarrot.

Concept ACK on this one.

tl;dr: I think this is the best solution so far, but propose adding a helper option to make life easier for parent projects: theuni@e22ed8c

Sounds good to me.

+1.

@purpleKarrot
Copy link
Contributor

I am failing to reason about all the different paths through that #ifdef-maze and whether they are truly all covered by actual requirements. Exactly what is the use case this new code wants to support without breaking what other requirements? I am sure such a list exits, even if not explicitly written down, otherwise we would not have this discussion. Extracting a platform abstraction as suggested in #1677 (comment) and https://gcc.gnu.org/wiki/Visibility (see the Step-by-step guide) could help comparing the code with those requirements.

The latter might be the right thing in a new library ...

I am a bit concerned that new libraries might follow the example set here. There should be a big disclaimer that points people to that GCC wiki page.

/* Consuming libsecp256k1 as a DLL. */
# define SECP256K1_API extern __declspec (dllimport)
# endif
#if !defined(SECP256K1_API) && defined(SECP256K1_NO_VISIBILITY_ATTRIBUTES)
Copy link
Member

Choose a reason for hiding this comment

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

My apologies if this was discussed earlier and I missed it, but it seems that SECP256K1_NO_VISIBILITY_ATTRIBUTES should only be considered only when building the library, not when consuming it:

Suggested change
#if !defined(SECP256K1_API) && defined(SECP256K1_NO_VISIBILITY_ATTRIBUTES)
#if !defined(SECP256K1_API) && defined(SECP256K1_BUILD) && defined(SECP256K1_NO_VISIBILITY_ATTRIBUTES)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that it hurts to apply it when consuming the library. Or does it have any undesirable effects?

@jonasnick jonasnick added this to the 0.7.0 milestone Jul 14, 2025
@real-or-random
Copy link
Contributor Author

SECP256K1_STATIC needs to be defined when a static library is used or consumed. Both Makefile.am and CMakeLists.txt already mention this name. The only necessary change is to drop the "if (windows)" condition.

This changes the meaning of SECP256K1_STATIC. So far, it's a consume-only macro (as explained in the code comments here).
I'd like to avoid the need to set SECP256K1_STATIC when building a static library, and only in Windows. (In earlier versions, we didn't even require the user to set SECP256K1_STATIC when consuming the library, but this was not possible without accepting that MSVC emits warnings, so it was changed.)

Why avoid the need to set SECP256K1_STATIC when building? First, it's more convenient for anyone who runs a build. Second, that's how it always was, so it's in particular more convenient for people who upgrade from earlier versions. Third, the existing logic is well-tested and has worked so far -- we're not fighting a bug, we're responding to a (somewhat unusual) feature request from Bitcoin Core -- so I'd like to avoid semantic changes that could introduce bugs. (But yes, refactoring seems a good idea.) Fourth, I don't know what the implications for autotools builds are: The ./configure script by default creates a build system that builds both the shared and the static library. If you ask me, that's a bad design choice in autoconf, but there's not much we can do about it except force the user to select one of the builds, which again, is not convenient and also unexpected to users familiar with autotools.

(Both cmake and libtool set a define symbol when building a shared library.)

I doubt that's true. As explained by @theuni in #1674, there's no such macro. There's just EXPORT_DLL, but this is set only on Windows.

The approach of using SECP256K1_STATIC and SECP256K1_BUILD is portable and build system independent.

Sure, writing it from scratch would lead to simpler code but my point is that the aforementioned arguments are stronger.

@real-or-random
Copy link
Contributor Author

Suggest renaming to SECP256K1_NO_FUNCTION_VISIBILITY_ATTRIBUTES because this does not affect SECP256K1_LOCAL_VAR.

True, but we also apply it to variables, e.g., secp256k1_context_static. And the LOCAL_VAR are really internal.

Renamed to SECP256K1_API_FUNCTION_VISIBILITY_ATTRIBUTES

tl;dr: I think this is the best solution so far, but propose adding a helper option to make life easier for parent projects: theuni@e22ed8c

Cherry-pick (and renamed the option accordingly).

It would be nice to get reviews by the end of the week, then we may get this into 0.7.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants