The use of static makes this somewhat redundant currently, though if we later have multiple compilation units it will be needed.
This also sets the dllexport needed for shared libraries on win32.
The use of static makes this somewhat redundant currently, though if we later have multiple compilation units it will be needed.
This also sets the dllexport needed for shared libraries on win32.
Ah, I guess the benchmarks linking the library is not considered to be across the library boundary. objdump -x on the .so file shows that doing so makes the API call go from ‘g’ to ’l’ (global to local?).
This raises the question: how can we test this? Nothing will catch a missing SECP256K1_EXPORT line.
Other than that, Tested ACK.
@gmaxwell LGTM after rebase.
@sipa nm -D file.so
should show the symbols as “T” if they’re properly exported. The tests work regardless of the exports because they’re linked statically.
Off the top of my head, we could test by:
-Wl,--no-undefined
. It could run as well, but we’d have to use LD_LIBRARY_PATH or some libtool tricks.I’m for renaming SECP256K1_EXPORT
to SECP256K1_API
too.
And also SECP256K1_BUILD
to SECP256K1_EXPORTS
as this name is a convention cmake is using for shared library builds (I mentioned about that in the other PR).
No support for static build (other than predefining empty SECP256K1_EXPORT
)?
@sipa Sorry, I got confused between the names used in the two pull requests. My preference is SECP256K1_API
, which was unchanged in https://github.com/bitcoin/secp256k1/pull/223. The change was from SECP256K1_DLL
to SECP256K1_EXPORTS
, because of @chfast comment….
CMake has a convention to add macro MyLibrary_EXPORTS when building a DLL. Is it possible to rename SECP256K1_DLL to SECP256K1_EXPORTS?
Also, I agree with @chfast that there should be a SECP256K1_STATIC
, which amounts to what is currently in https://github.com/bitcoin/secp256k1/pull/223…
0# if defined _MSC_VER || defined __CYGWIN__
1# define SECP256K1_HELPER_LOCAL
2# define SECP256K1_HELPER_DLL_IMPORT __declspec(dllimport)
3# define SECP256K1_HELPER_DLL_EXPORT __declspec(dllexport)
4# else
5# if __GNUC__ >= 4
6# define SECP256K1_HELPER_LOCAL __attribute__ ((visibility ("hidden")))
7# define SECP256K1_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
8# define SECP256K1_HELPER_DLL_EXPORT __attribute__ ((visibility ("default")))
9# else
10# define SECP256K1_HELPER_LOCAL
11# define SECP256K1_HELPER_DLL_IMPORT
12# define SECP256K1_HELPER_DLL_EXPORT
13# endif
14# endif
15
16# if defined SECP256K1_STATIC
17# define SECP256K1_API SECP256K1_HELPER_LOCAL
18# elif defined SECP256K1_EXPORTS
19# define SECP256K1_API SECP256K1_HELPER_DLL_EXPORT
20# else
21# define SECP256K1_API SECP256K1_HELPER_DLL_IMPORT
22# endif
No support for static build (other than predefining empty SECP256K1_EXPORT)?
Static builds are supported just fine (unless dllimport breaks them on windows, in which case I think we should drop dllimport). In fact they are currently the built by default and used by the benchmarks by default in the current configuration.
I currently disagree with the separate static define. It is messy, increases the already nearly untestably large ifdef combinitoric complexity, and unnecessary as far as I can tell.
Also fix the nullness requirements for schnorr nonce-pair generation.
There are three sceanrios (actually four, but two can be collapsed), which requires more than one flag.
SECP256K1_EXPORTS
(initially this was defined as SECP256K1_DLL
) and gets dllexport
.dllimport
)The ifdef
does get more complex, which is why the above pattern is used to keep it visually verifiable.
evoskuil: When building the library the visibility annotations are set on the prototypes. This is mediated by the singular SECP256K1_BUILD macro which we also need for other reasons.
Then using the library they are not. On *nix with libtool the library isn’t even built twice for static vs dynamic: both outputs are created from the same intermediate result.
You haven’t explained why you are treating static specially. It is not special on unix (beyond the visibility policy being a no-op), is it actually special on win32 in some other way?
@gmaxwell I don’t see where SECP256K1_BUILD
is defined, but I assume its definition implies that the library is being built (as DLL or static) as opposed to being linked (as DLL or static).
Based on that assumption and this construction…
0# if defined(_WIN32)
1# ifdef SECP256K1_BUILD
2# define SECP256K1_API __declspec(dllexport)
3# else
4# define SECP256K1_API __declspec(dllimport)
5# endif
6...
7# endif
… how does _WIN32
static use of the library avoid __declspec(import|export)
? I believe this was the point of @sipa ’s question.
SECP256K1_BUILD is being set by the build system.
Having a special case for static building is incompatible with the fact that the shared and static library are being built from the same .o.file.
_WIN32
as it stands.
dllimport
will as well. I’ll run a test in a bit just to make absolutely sure.
Can you explain how (e.g. what error results?)
The original macro set I used (which doesn’t set dllimport) is the same as I’ve used in other projects running on hundreds of millions of windows devices (and most built with the microsoft toolchain of some vintage or another). I’m unclear on the mechanism that would cause it to fail, as the microsoft documentation is vague. It works in mingw32 for me at least.
From what I read, not setting dllimport is always safe, just slightly less efficient. The inefficiency is a single extra jump to a stub. Given that our fastest runtime APIs already take several microseconds, I think the effect is negligable.
If dllimport is not causing any problems… sure, but if setting it is a problem in static builds, it means the caller code needs to be aware of whether it’s linking libsecp256k1 statically or dynamically (not only the linrary code), which seems pretty ugly.
The problem is with __declspec(dllimport)
. If this is defined on the API then static linking will produce
0warning C4273: '[function name]' : inconsistent dll linkage
for all functions so defined. More info here: https://msdn.microsoft.com/en-us/library/35bhkfb6.aspx
Given your objectives I’d recommend just not setting dllimport
.
So is the avoidance of a jump when calling the library worth:
@sipa It is sort of ugly that the caller has to make this distinction, but it’s also extremely common in Windows builds. I try to minimize it to having to only define a symbol when linking statically, and doing nothing when linking dynamically. Most of the work I do is in C++, where this distinction is more significant.
I don’t think the first two issues you list are significant, but achieving your objective of a single compile seems to outweigh the small runtime hit.
The use of static makes this somewhat redundant currently, though if
we later have multiple compilation units it will be needed.
This also sets the dllexport needed for shared libraries on win32.
118@@ -119,6 +119,20 @@ typedef int (*secp256k1_nonce_function)(
119 # define SECP256K1_INLINE inline
120 # endif
121
122+#ifndef SECP256K1_API
123+# if defined(_WIN32)
124+# ifdef SECP256K1_BUILD
SECP256K1_BUILD
to SECP256K1_EXPORTS
? It is weird that SECP256K1_BUILD
is defined for dynamic builds but not for static builds.
118@@ -119,6 +119,20 @@ typedef int (*secp256k1_nonce_function)(
119 # define SECP256K1_INLINE inline
120 # endif
121
122+#ifndef SECP256K1_API
@gmaxwell I get that it’s an objective is to avoid the conditional compilation of a static and dynamic build, and to avoid exposing that distinction to the library consumer. But what I don’t understand is how this is actually accomplished.
When intending to load a DLL a project is initially linked against a static (stub) lib. Since the stub forwards calls to the DLL it is necessarily a different build than the (full) static lib. In VC the stub and full static lib have the same name and define the same symbols, but I don’t see how the full static lib can possibly be used as a stub and therefore how distinct builds can be avoided.
It makes sense that one can link statically against the stub lib, built with dllexport
, but this doesn’t constitute a static link of the main lib, as the DLL would still be required. It’s not just a question of visibility. This is why the conditionality is always exposed to the library consumer in Windows. If you’ve got a way around this I’d like to understand so I can simplify some repos.
@evoskuil The static and dynamic library are not the same. But they are both constructed from the same object file. The C compiler doesn’t need to know how the compiled code will be used, only the linker that turns it either into a static or dynamic library.
Given that @gmaxwell’s header preamble works in Opus on Windows, here is my assumption:
dllimport
, as it is necessary to select the appropriate library.
@evoskuil Well obviously something in the build system needs to know what it is linking to. But the compiler doesn’t need to know. Not on the library side, not on user side.
Even better, the one building the library can choose whether to build the static version, the dynamic version, or both. And the user doesn’t even need to care what was produced - only find the library.
.obj
files is not an issue as long as there are no compiler conditions that differ between static and dynamic builds, and avoiding dllimport
seems like a reasonable trade-off in a C project.