-
Notifications
You must be signed in to change notification settings - Fork 1k
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
build: Improvements to symbol visibility logic on Windows #1362
build: Improvements to symbol visibility logic on Windows #1362
Conversation
a61ca80
to
f31c12e
Compare
Ok, ready for review (if CI is happy). This PR also addresses this item in #1235 now:
I've chosen to use the |
cc @theuni |
Outdated? |
Out of scope of this PR, but shouldn't CI has a Cygwin build as well? |
Addresses one item in bitcoin-core#1235.
This makes uses of the freshly introduced `SECP256K1_STATICLIB` macro instead of ignoring MSVC linker warnings LNK4217 and LNK4286. Co-authored-by: Tim Ruffing <[email protected]>
f31c12e
to
5267c8b
Compare
Yes, I removed it from the initial comment.
In general, I guess more testing never hurts. Is Cygwin still in wide use? |
I don't have any particular numbers, but it seems WSL undermined Cygwin's purpose :) Anyway, if code written for a platform that platform should be tested. If it not feasible/reasonable to test a platform, no need to maintain platform-specific code, no? |
Ok, yeah, the reason I included the Cygwin commit is that checking
I think that's a good rule of thumb, but I also think that in this case maintaining half a line of code is okay as a best-effort thing, even if we don't test it. In particular, because it's just build system code. (If we used different instructions on Cygwin, then we would need a test for sure.) |
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.
Concept ACK.
[ ] Consider changing
DLL_EXPORT
TOSECP256k1_DLL_EXPORT
(#1113 (comment))
Does it make sense to replace s/DLL_EXPORT
/SECP256k1_DLL
/ in the src/CMakeLists.txt
?
# define SECP256K1_API | ||
# define SECP256K1_API_VAR extern __declspec (dllimport) | ||
# elif defined DLL_EXPORT | ||
# elif defined(DLL_EXPORT) |
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.
Shoudn't it be OR'ed with defined(SECP256k1_DLL)
?
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, if SECP256K1
is defined, then we would have hit the #elif defined(SECP256K1_DLL)
above.
An alternative option is to drop this "guess" based on EXPORT_DLL
entirely. We took this from the libtool manual which justifies it as follows:
For older GNU tools and other proprietary tools there is no generic way to make it possible to consume either of the DLL or the static library without user intervention, the tools need to be told what is intended. One common assumption is that if a DLL is being built (‘DLL_EXPORT’ is defined) then that DLL is going to consume any dependent libraries as DLLs. If that assumption is made everywhere, it is possible to select how an end-user application is consuming libraries by adding a single flag ‘-DDLL_EXPORT’ when a DLL build is required. This is of course an all or nothing deal, either everything as DLLs or everything as static libraries.
(https://www.gnu.org/software/libtool/manual/html_node/Windows-DLLs.html#Windows-DLLs)
But with this PR, we get better methods for telling the tools what is intended. And I believe that "Explicit is better than implicit" and "In the face of ambiguity, refuse the temptation to guess.".
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.
What do you think? Should we remove this branch entirely?
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.
Remove it. As a Libtool-only convention, using the DLL_EXPORT
macro when consuming the library looks weird.
But with this PR, we get better methods for telling the tools what is intended.
Indeed.
* the following is that it may provoke linker warnings LNK4217 and LNK4286. | ||
* See "Windows DLLs" in the libtool manual. */ |
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.
Add a recommendation to use the SECP256K1_STATICLIB
macro?
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.
I had a sentence in an earlier version, but I removed it because it's probably clear by "No method requested explicitly." Let me bring it back.
Good point, will do. |
* imported (i.e., not only variables are imported), which should be the case | ||
* for any meaningful program that uses the libsecp256k1 API. The drawback of | ||
* the following is that it may provoke linker warnings LNK4217 and LNK4286. | ||
* See "Windows DLLs" in the libtool manual. */ | ||
# define SECP256K1_API |
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.
While touching this code, we can exploit one more optimization.
From Microsoft docs:
Using
__declspec(dllimport)
is optional on function declarations, but the compiler produces more efficient code if you use this keyword.
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.
Indeed, but we shouldn't touch it here in the # elif defined(_MSC_VER)
branch.
The specific combination
# define SECP256K1_API
# define SECP256K1_API_VAR extern __declspec (dllimport)
is precisely what always works for MSVC (if one is willing to accept the warning):
With Microsoft tools you typically get away with always compiling the code such that variables are expected to be imported from a DLL and functions are expected to be found in a static library. The tools will then automatically import the function from a DLL if that is where they are found. If the variables are not imported from a DLL as expected, but are found in a static library that is otherwise pulled in by some function, the linker will issue a warning (LNK4217) that a locally defined symbol is imported, but it still works.
https://www.gnu.org/software/libtool/manual/html_node/Windows-DLLs.html#Windows-DLLs
But in general, yes, the introduction of SECP256K1_DLL
here will make it possible for the user to force __declspec(dllimport)
on functions, which is faster according to the docs. We could add this to our linking jobs in autotools/CMake, though I doubt that it's measurable, given that API calls for us typically involve some expensive cryptographic operation anyway -- saving a few CPU instructions is probably not a big deal.
Approach ACK 5267c8b. While testing a static library using MSVC, the following warning has been noticed:
which is a new one after merging #1129. To tackle with it, suggesting the following diff (+ #1362 (comment)): diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index a314c17..e2ea473 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -6,9 +6,6 @@ target_link_libraries(example INTERFACE
secp256k1
$<$<PLATFORM_ID:Windows>:bcrypt>
)
-if(NOT BUILD_SHARED_LIBS AND MSVC)
- target_compile_definitions(example INTERFACE SECP256K1_STATICLIB)
-endif()
add_executable(ecdsa_example ecdsa.c)
target_link_libraries(ecdsa_example example)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0bba199..4e62f61 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -20,10 +20,9 @@ if(SECP256K1_ASM STREQUAL "arm32")
target_link_libraries(secp256k1_asm INTERFACE secp256k1_asm_arm)
endif()
-# Define our export symbol only for Win32 and only for shared libs.
-# This matches libtool's usage of DLL_EXPORT
if(WIN32)
- set_target_properties(secp256k1 PROPERTIES DEFINE_SYMBOL "DLL_EXPORT")
+ set_target_properties(secp256k1 PROPERTIES DEFINE_SYMBOL SECP256K1_DLL)
+ target_compile_definitions(secp256k1 INTERFACE $<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:SECP256K1_STATICLIB>)
endif()
# Object libs don't know if they're being built for a shared or static lib. |
I'm still thinking about user's definitions during the consuming of the library. Now, at the master branch, none of them are required or expected from the user. The This PR (5267c8b) introduces two optional macros: Another approach is to make the What drawbacks did I miss here? |
Doesn't the following set SECP256K1_DLL even if we're building a static lib? if(WIN32)
set_target_properties(secp256k1 PROPERTIES DEFINE_SYMBOL SECP256K1_DLL)
[...]
endif() And the current code on master does the same despite the comment saying "only for shared libs". I assume having dllexport just doesn't hurt in this case? Or am I getting this wrong? |
From CMake docs:
|
That's indeed simpler. And I don't think you missed any drawbacks. The only drawback is that MSVC users are required to define I wonder how other libraries are doing this? |
CMake-based projects might exploit the |
Here are two branches to prove this concept:
"Simple is better than complex." :) |
By the way, Also, I'll rework the PR based on these observations, but probably not this week. I'd also be happy to pass it back to you @hebasto if you're willing to work on this. |
I'll take it :) |
…s (attempt 3) c6cd2b1 ci: Add task for static library on Windows + CMake (Hennadii Stepanov) 020bf69 build: Add extensive docs on visibility issues (Tim Ruffing) 0196e8a build: Introduce `SECP256k1_DLL_EXPORT` macro (Hennadii Stepanov) 9f1b190 refactor: Replace `SECP256K1_API_VAR` with `SECP256K1_API` (Hennadii Stepanov) ae9db95 build: Introduce `SECP256K1_STATIC` macro for Windows users (Hennadii Stepanov) Pull request description: Previous attempts: - #1346 - #1362 The result is as follows: 1. Simple, concise and extensively documented code. 2. Explicitly documented use cases with no ambiguities. 3. No workarounds for linker warnings. 4. Solves one item in #1235. ACKs for top commit: real-or-random: utACK c6cd2b1 Tree-SHA512: d58694452d630aefbd047916033249891bc726b7475433aaaa7c3ea2a07ded8f185a598385b67c2ee3440ec5904ff9d9452c97b0961d84dcb2eb2cf46caa171e
This is a somewhat more elaborate alternative to #1346.
As I said there: