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

remove Boost.DLL dependency by doing DLL handling ourselves #1

Conversation

DyXel
Copy link

@DyXel DyXel commented Dec 27, 2023

Follow-up to hsutter#907 (comment)

Implement minimal dll class that acts as "almost drop-in replacement" for Boost.DLL stuff. Tested only on Linux with g++.

Some notes:

  • Since this can always be provided, I removed the _never header, and renamed the _boost_dll to reflect_load_metafunction.h (simplifies the CLI, removes the need of CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER).
  • Library is ref-counted with shared_ptr rather than by the class implementation (dunno if Boost.DLL did that, but std::function was complaining about needing to copy so I am guessing some ref-counting was happening already behind the scenes).
  • I was getting a segfault when trying to call the aliased function, turns out you need extra incantations (that I dont know) to handle passing by reference, so instead I changed the cpp1 emission to do what I allured to in Design for program-defined metafunctions for cppfront hsutter/cppfront#909 (comment) (generate a C type-erased function).

@DyXel DyXel force-pushed the program-defined-metafunctions-no-boost-dll branch from 7ce5cd1 to fbdbaba Compare December 27, 2023 01:03
@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Another thing: Boost.DLL was probably resolving the absolute path of a DLL before opening, atm this doesn't do that, so in the CLI you now need to do CPPFRONT_METAFUNCTION_LIBRARIES=./libmetafunctions.so instead of CPPFRONT_METAFUNCTION_LIBRARIES=libmetafunctions.so.

This brings another point up: How to deal with the error logging? (I put some TODOs for now), I immediately identified the error because I had some debug statements to print whatever dlerror gave me, I think there is value in providing that to end-users but I wouldn't know how to integrate that with the rest of cppfront.

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Finally, before I forget, we should probably be adding something akin to:

  • __declspec(dllexport) on VS
  • __attribute__ ((visibility ("default"))) on gcc-like compilers

to mark these functions explicitly exported. A lot of programs define a macro that expands to either of those and they are usually called xAPI (e.g.: CPPFRONTAPI).

Copy link
Owner

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

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

Thank you for doing this.
I had forgotten to watch this repository, so I noticed 30 minutes late.

  • Since this can always be provided, I removed the _never header, and renamed the _boost_dll to reflect_load_metafunction.h (simplifies the CLI, removes the need of CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER).

This is great.
Although I'm not confident that Boost.DLL isn't ported to more than just Windows and Unix.
But this should help reduce the friction when considering for merging the feature upstream.

Another thing: Boost.DLL was probably resolving the absolute path of a DLL before opening, atm this doesn't do that, so in the CLI you now need to do CPPFRONT_METAFUNCTION_LIBRARIES=./libmetafunctions.so instead of CPPFRONT_METAFUNCTION_LIBRARIES=libmetafunctions.so.

Boost.DLL depends on Boost.Filesystem, so it was probably using that.

__declspec(dllexport) on VS

Yeah, this probably won't work at all on Windows without it, considering its default.
The same is true for the emitted symbol, right?
So this is a macro that we should add to cpp2util.h.

source/reflect_load_metafunction.h Outdated Show resolved Hide resolved
}
}

return {};
Copy link
Owner

Choose a reason for hiding this comment

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

This brings another point up: How to deal with the error logging? (I put some TODOs for now), I immediately identified the error because I had some debug statements to print whatever dlerror gave me, I think there is value in providing that to end-users but I wouldn't know how to integrate that with the rest of cppfront.

This is an important QoI issue that we should discuss with Herb.
I already have 3 different modules authoring metafunctions.
Just imagine how much worse this error message could get
if it listed all metafunctions authored in which libraries (https://cpp2.godbolt.org/z/vqMYYKv5e):

t: @ugh type = { }
main: () = { }
main.cpp2...
main.cpp2(2,1): error: unrecognized metafunction name: ugh
main.cpp2(2,1): error: (temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Yeah, this probably won't work at all on Windows without it, considering its default. The same is true for the emitted symbol, right? So this is a macro that we should add to cpp2util.h.

Yeah, it'll probably not work by default (will test tomorrow or so on a Windows VM), and yes, I was thinking about adding such macro to that file.

Edit: Special thanks to @edo9300 for fixing and testing windows' side of things (even mingw!)

Properly take a std::string in those functions to ensure that the passed strings are actually properly null terminated.
Check the ``_WIN32`` macro rather than ``_MSC_VER`` to detect if we're targeting Windows, rather than checking if we're compiling using Visual Studio.
Add ``function_cast`` to properly convert the function pointer retrieved from GetProcAddress when building under mingw.
@JohelEGP
Copy link
Owner

I switched to compiling my modules with hidden visibility.
Indeed, my program-defined metafunctions are no longer found.

With regards to commit 32063ad,
does this means cpp2util.h is wrong?
It uses defined(_MSC_VER) all over the place.

@edo9300
Copy link

edo9300 commented Dec 27, 2023

With regards to commit 32063ad, does this means cpp2util.h is wrong? It uses defined(_MSC_VER) all over the place.

The usage of that macro in that file is to check for compiler specific capabilities and features, so it's correct to check for Visual Studio. But for Windows targeting checking for _WIN32 is required as not only Visual Studio can build for the platform, and other compilers like gcc and clang will only define the _WINXX macro.

@edo9300
Copy link

edo9300 commented Dec 27, 2023

Another thing to note regarding the Windows side of the changes. At the moment I could only test its behavior while building with mingw, as I could use a similar approach as on linux to have gcc export every symbol present in cppfront.exe (via --export-all-symbols), but to have the same result on Visual Studio, symbols have to be properly marked as __declspec(dllexport) for them to be usable from outside at all.

@JohelEGP
Copy link
Owner

It should be OK to only emit the metafunction symbol with a CPP2_EXPORT macro.
The reflection API is written in Cpp2.
Cpp2 doesn't support a way to add CPP2_EXPORT to the lowered Cpp1 declaration.
So, for now, we will need to compile cppfront with all symbols exported.

@JohelEGP
Copy link
Owner

@DyXel Will you add an export macro to cpp2util.h?
Otherwise, I think this is ready to merge.
Then I would add it myself.

@JohelEGP
Copy link
Owner

but to have the same result on Visual Studio, symbols have to be properly marked as __declspec(dllexport) for them to be usable from outside at all.

I'm wondering.
Does this mean that Visual Studio has no option to export all symbols?
I.e., it's necessary to mark all symbols to be exported.
That would be a blocker for VS.

@edo9300
Copy link

edo9300 commented Dec 27, 2023

but to have the same result on Visual Studio, symbols have to be properly marked as __declspec(dllexport) for them to be usable from outside at all.

I'm wondering. Does this mean that Visual Studio has no option to export all symbols? I.e., it's necessary to mark all symbols to be exported. That would be a blocker for VS.

Unfortunately that seems to be the only way, from https://learn.microsoft.com/en-us/cpp/build/reference/export-exports-a-function?view=msvc-170

There are four methods for exporting a definition, listed in recommended order of use:

    __declspec(dllexport) in the source code

    An EXPORTS statement in a .def file

    An /EXPORT specification in a LINK command

    A comment directive in the source code, of the form #pragma comment(linker, "/export: definition ").

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

@DyXel Will you add an export macro to cpp2util.h? Otherwise, I think this is ready to merge. Then I would add it myself.

Yes, I'll be on it a bit later, currently running some errands.

@edo9300
Copy link

edo9300 commented Dec 27, 2023

Cpp2 doesn't support a way to add CPP2_EXPORT to the lowered Cpp1 declaration.

Yeah, that's a bit unfortunate, I just tried to confirm if in theory those things would work under visual studio, (and after fixing header inclusions, damn you NOMINMAX), by putting strategic __declspec(dllexport) to the 2 functions your sample program uses, I managed to get a working dll that could be then used by cppfront (and it required even less steps)

cl cppfront.cpp -std:c++20 -EHsc
cppfront.exe metafunctions.cpp2
cl /LD metafunctions.cpp cppfront.lib -std:c++20 -EHsc
set CPPFRONT_METAFUNCTION_LIBRARIES=metafunctions.dll
cppfront main.cpp2
cl main.cpp -std:c++20 -EHsc

@JohelEGP
Copy link
Owner

by putting strategic __declspec(dllexport) to the 2 functions your sample program uses

I suppose you did something like this:

__declspec(dllexport)
greeter: (inout t: cpp2::meta::type_declaration) = {
  t.add_member($R"(say_hi: () = std::cout << "Hello, world!\nFrom (t.name())$\n";)");
}

That works only in the global namespace.

CPPFRONTAPI should be used to expose c++ functions that would be used by
metafunctions. The question remains how to mark cpp2 code with this during
lowering, at the moment manually marking the generated files with it makes
Windows work.

CPP2_C_API is emitted when generating the "entrypoint" for each metafunction by
making it extern "C".
@edo9300
Copy link

edo9300 commented Dec 27, 2023

by putting strategic __declspec(dllexport) to the 2 functions your sample program uses

I suppose you did something like this:

__declspec(dllexport)
greeter: (inout t: cpp2::meta::type_declaration) = {
  t.add_member($R"(say_hi: () = std::cout << "Hello, world!\nFrom (t.name())$\n";)");
}

That works only in the global namespace.

I did it in a way more hacky way, direclty putting the export in the generated cpp files, specifically here and here, and in the generated entrypoint extern "C" __declspec(dllexport) void cpp2_metafunction_greeter, very hacky, but I really just wanted to confirm if the library loading code worked properly.

At some point those strings will need to be reported anyways, and its unbearable
to test without zero error logging.
@DyXel DyXel requested a review from JohelEGP December 27, 2023 18:31
Copy link
Owner

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

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

Sorry for the delay, I was trying to understand an error.
I have a corner case where a library that uses a metafunction in another library won't load.
Note that it worked with the implementation using Boost.DLL.
In this case, libmetafunctions.so depends on libutilities.so.
When failing to open libmetafunctions.so,
the error is that the symbol that libutilities.so defines is undefined.

[johel@sundown source]$ cat utilities.cpp2
printer: (inout t: cpp2::meta::type_declaration) = {
  t.add_member($R"(print: (s) = std::cout << s;)");
}
[johel@sundown source]$ cat metafunctions.cpp2
auto printer(cpp2::meta::type_declaration &t) -> void;
greeter: (inout t: cpp2::meta::type_declaration) = {
  t.printer();
  t.add_member($R"(say_hi: () = print("Hello, world!\nFrom (t.name())$\n");)");
}
[johel@sundown source]$ cat main.cpp2
my_class: @greeter type = {}
my_struct: @greeter type = {}
main: () = {
  my_class().say_hi();
  my_struct().say_hi();
}
[johel@sundown source]$ g++ -std=c++20 -o cppfront.cpp.o -c cppfront.cpp
[johel@sundown source]$ g++ -Wl,--export-dynamic -rdynamic cppfront.cpp.o -o cppfront
[johel@sundown source]$ ./cppfront utilities.cpp2
utilities.cpp2... ok (all Cpp2, passes safety checks)

[johel@sundown source]$ g++ -std=c++20 -fPIC -o utilities.cpp.o -c utilities.cpp
[johel@sundown source]$ g++ -fPIC -shared -Wl,-soname,libutilities.so -o libutilities.so utilities.cpp.o
[johel@sundown source]$ ./cppfront metafunctions.cpp2
metafunctions.cpp2... ok (mixed Cpp1/Cpp2, Cpp2 code passes safety checks)

[johel@sundown source]$ g++ -std=c++20 -fPIC -o metafunctions.cpp.o -c metafunctions.cpp
[johel@sundown source]$ g++ -fPIC -shared -Wl,-soname,libmetafunctions.so -o libmetafunctions.so metafunctions.cpp.o
[johel@sundown source]$ CPPFRONT_METAFUNCTION_LIBRARIES=utilities.so:libmetafunctions.so ./cppfront main.cpp2
main.cpp2...Error while looking up DLL symbol 'cpp2_metafunction_greeter': utilities.so: undefined symbol: _cpp2_metafunction_greeter
Error while loading DLL 'libmetafunctions.so': libmetafunctions.so: undefined symbol: _Z7printerRN4cpp24meta16type_declarationE

main.cpp2(2,1): error: unrecognized metafunction name: greeter
main.cpp2(2,1): error: (temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print

I can't seem to push to your branch.
Here's a patch for an issue I found:

commit 717f725132a9e54dc860105541f883808279fe7d
Author: Johel Ernesto Guerrero Peña <[email protected]>
Date:   Wed Dec 27 14:59:20 2023 -0400

    fix(reflect): guard clean up

diff --git a/source/reflect_load_metafunction.h b/source/reflect_load_metafunction.h
index a8b1b28..26a0930 100644
--- a/source/reflect_load_metafunction.h
+++ b/source/reflect_load_metafunction.h
@@ -32,13 +32,14 @@ public:
 
     ~dll() noexcept
     {
-        if(handle_ == nullptr);
-            return;
+        if(handle_ != nullptr)
+        {
 #ifdef _WIN32
-        FreeLibrary(static_cast<HMODULE>(handle_));
+            FreeLibrary(static_cast<HMODULE>(handle_));
 #else
-        dlclose(handle_);
+            dlclose(handle_);
 #endif // _WIN32
+        }
     }
 
     // Uncopyable

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Screenshot_20231227_212433
Weird since I have that check enabled. I'll push the patch myself.

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

I have a corner case where a library that uses a metafunction in another library won't load.

I am wondering if it would work if you loaded the DLLs in different order, also, I think the resolution has to do with RTLD_NOW (https://linux.die.net/man/3/dlopen):

RTLD_NOW
If this value is specified, or the environment variable LD_BIND_NOW is set to a nonempty string, all undefined symbols in the library are resolved before dlopen() returns. If this cannot be done, an error is returned.

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Looking closer at the documentation, I think using RTLD_LAZY|RTLD_GLOBAL should make your use-case work, but would probably make debugging harder if a symbol from cppfront is not found. using NOW|LOCAL forces cppfront to load .so s in the correct order, whether that is a feature or a problem is up to how liberal we want to be with metafunctions within metafunctions...

@JohelEGP
Copy link
Owner

I am wondering if it would work if you loaded the DLLs in different order

I think using RTLD_LAZY|RTLD_GLOBAL should make your use-case work

They don't.

@JohelEGP
Copy link
Owner

whether that is a feature or a problem is up to how liberal we want to be with metafunctions within metafunctions...

I suspect it's going to be a common use case for library writers.
In my case, I'm using an implementation of @rule_of_zero (hsutter#808).
Then I'm defining @sfml, which uses it (hsutter#789 (reply in thread)).

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

whether that is a feature or a problem is up to how liberal we want to be with metafunctions within metafunctions...

I suspect it's going to be a common use case for library writers. In my case, I'm using an implementation of @rule_of_zero (hsutter#808). Then I'm defining @sfml, which uses it (hsutter#789 (reply in thread)).

In that case in order to properly support these use-cases we would need to make the loading orthogonal (like modules!), it would require a bit of re-design, but basically I believe this is how it would work:

  • We do lazy and global resolution (need to check Windows' behavior but I think this is possible on every OS)
  • Rather than try to load the dll for each function look-up, we first load every single DLL without doing any kind of symbol look up.
  • Once all DLLs are loaded, the list is traversed and the look-up happens one by one on each DLL, at that point symbols should all hopefully resolve properly, letting the OS figure out the dependency between them.

@JohelEGP
Copy link
Owner

Note that it worked with the implementation using Boost.DLL.

Actually, the Boost.DLL implementation also fails
if I add the export macros and use the hidden visibility preset.

So this issue isn't something that should block this PR.

I do note that switching to the default visibility preset did make it work for me.
I must have messed up the commands of the reproducer
since they use the default visibility and fail anyways.

Why does visibility make a difference here?

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

To be honest, I am not that well-versed in how the symbol look-up is supposed to work, but according to this:
https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Code-Gen-Options.html#Code-Gen-Options (search -fvisibility)
doing -fvisibility=hidden + __attribute__ ((visibility("default"))) should provide what you want (all symbols hidden except for what we explicitly export, same as on Windows), the problem i'd argue is how symbol resolving happens, you might have gotten lucky before with RTLD_LAZY (boost.dll's default) and symbol look-up because, as I mentioned, since the boost dll class is copyable it was probably keeping the dll loaded at runtime somehow (this is all just guesswork, a more correct design would be what I suggested above).

@JohelEGP
Copy link
Owner

you might have gotten lucky before with RTLD_LAZY (boost.dll's default) and symbol look-up because, as I mentioned, since the boost dll class is copyable it was probably keeping the dll loaded at runtime somehow (this is all just guesswork, a more correct design would be what I suggested above).

Just like this one, the previous implementation loaded each library one at a time.
As I mentioned above, Boost.DLL actually has the same behavior if I update it.

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Then no idea why changing the global visibility would affect how symbols are resolved. And honestly not the most thrilling rabbit-hole to go down to 😅

Copy link
Owner

@JohelEGP JohelEGP left a comment

Choose a reason for hiding this comment

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

Thank you both for this!

Since I can't push to the branch, I'll merge this now and work on integrating the error reporting.

We also need to report #1 (comment) upstream, which blocks VS.
Do all declarations in cpp2reflect.h need to be manually prepended CPPFRONTAPI to work on VS (what about the definitions)?
It should be possible to write a script to post-process the generated header.
That would be a good place to document the problem and for Herb to notice the need to extend Cpp2.

After all that, I should update the upstream's opening comment
to show how this PR greatly simplified the requirements
by removing the dependency on Boost.DLL.

@JohelEGP JohelEGP merged commit c6217f9 into JohelEGP:program-defined_metafunctions_v2 Dec 27, 2023
@JohelEGP
Copy link
Owner

The windows CI failed with (https://github.com/JohelEGP/cppfront/actions/runs/7342334703/job/19991420610#step:5:68):

D:\a\cppfront\cppfront\source\reflect_load_metafunction.h(89): error C2664: 'DWORD FormatMessageA(DWORD,LPCVOID,DWORD,DWORD,LPSTR,DWORD,va_list *)': cannot convert argument 5 from 'LPSTR *' to 'LPSTR'

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

Do all declarations in cpp2reflect.h need to be manually prepended CPPFRONTAPI to work on VS (what about the definitions)?

I think yes, at least the thing you want to be accessible for metafunction usage, so I am guessing most of cpp2reflect.h... Ideally you'd want to mark the cpp2 code with something like @__cpp2_meta_library or something that is directly built-in in cppfront for internal usage that lowers to the properly marked code with the visibility macro. As for what to mark, I think marking just the declarations should be enough.

@DyXel
Copy link
Author

DyXel commented Dec 27, 2023

The windows CI failed with (https://github.com/JohelEGP/cppfront/actions/runs/7342334703/job/19991420610#step:5:68):

D:\a\cppfront\cppfront\source\reflect_load_metafunction.h(89): error C2664: 'DWORD FormatMessageA(DWORD,LPCVOID,DWORD,DWORD,LPSTR,DWORD,va_list *)': cannot convert argument 5 from 'LPSTR *' to 'LPSTR'

oops, my bad. reflect_load_metafunction.h:94 should be (LPSTR)&messageBuffer,

@DyXel DyXel deleted the program-defined-metafunctions-no-boost-dll branch December 27, 2023 22:31
@JohelEGP
Copy link
Owner

I have a corner case where a library that uses a metafunction in another library won't load.

It turns out that I didn't update my branch https://github.com/JohelEGP/cppfront/tree/cpp_modules
to export the emitted symbol when lowering a C++ module.

I also opened the related llvm/llvm-project#76526.
Here's the same without modules: https://godbolt.org/z/drhjhax9h.

@JohelEGP
Copy link
Owner

Actually, that's not exactly it.

It's that rule_of_zero doesn't lower with CPPFRONTAPI.
When loading libsfml_metafunction.so,
the use of rule_of_zero (and not of cpp2_metafunction_rule_of_zero)
in the body of sfml is a hidden symbol.
Manually adding CPPFRONTAPI to the lowered declaration of rule_of_zero makes it work.

It's the same issue as hsutter#907 (comment):

So we need Cpp2 support to export library symbols,

@JohelEGP
Copy link
Owner

  • was getting a segfault when trying to call the aliased function, turns out you need extra incantations (that I dont know) to handle passing by reference, so instead I changed the cpp1 emission to do what I allured to in

That's because you were trying to use a function, right?
The symbol used to be a function pointer object.
Now I need that again to fix hsutter@22496c9#diff-09d210b8aa3d8090c8877ce7afc954f7e8affd2de2e0546bf0edbb7a9f7ba98eR147-R148.
Is there any problem that one of the types that make up the pointer has a reference type?
In any case, I could lower a void* object symbol and take care of casting it in load_metafunction.

@DyXel
Copy link
Author

DyXel commented Dec 30, 2023

Is there any problem that one of the types that make up the pointer has a reference type?

From what I understand, at the ABI level there should be no difference between reference parameters and pointer parameters, yet, I was getting segfaults when trying to call the function with a reference, and the backtrace didn't tell me much of what was going on. It could have been a bad cast on my side but not sure.

The safest and most portable way is to type-erase when dealing with C APIs so I'd say you can (and should) go with:

I could lower a void* object symbol and take care of casting it in load_metafunction.

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

Successfully merging this pull request may close these issues.

3 participants