-
Notifications
You must be signed in to change notification settings - Fork 0
Auto register metafunctions (POC) #2
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: program-defined_metafunctions_v2
Are you sure you want to change the base?
Auto register metafunctions (POC) #2
Conversation
A metafunction is normal Cpp2 code compiled as part of a library. When parsing a declaration that `@`-uses the metafunction, the library is loaded and the metafunction invoked on the declaration. The reflection API is available by default to Cpp2 code (via `cpp2util.h`). The implementation of the API is provided by the `cppfront` executable. For this to work, compiling `cppfront` should export its symbols (for an explanation, see <https://cmake.org/cmake/help/latest/prop_tgt/ENABLE_EXPORTS.html>). The default build of `cppfront` doesn't support loading program-defined metafunctions. In order to support loading program-defined metafunctions, `cppfront` should be built with `CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER` defined to a header which implements the loading functionality. This commit includes an implementation using [Boost.DLL][]. [Boost.DLL]: https://www.boost.org/doc/libs/release/doc/html/boost_dll.html Because cppfront doesn't perform name lookup, metafunction names should be `@`-used unqualified and follow C "namespacing" conventions (e.g., `@mylib_mymetafunction`). Here is an example of program-defined metafunctions using the Boost.DLL implementation. The commands were cleaned up from the CMake buildsystem in hsutter#797. `metafunctions.cpp2`: ```Cpp2 greeter: (inout t: cpp2::meta::type_declaration) = { t.add_member($R"(say_hi: () = std::cout << "Hello, world!\nFrom (t.name())$\n";)"); } ``` `main.cpp2`: ```Cpp2 my_class: @greeter type = { } main: () = my_class().say_hi(); ``` Build `cppfront`: ```bash g++ -DBOOST_ATOMIC_DYN_LINK -DBOOST_ATOMIC_NO_LIB -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_FILESYSTEM_NO_LIB -DBOOST_SYSTEM_DYN_LINK -DBOOST_SYSTEM_NO_LIB -DCPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER=\"reflect_load_metafunction_boost_dll.h\" -std=c++20 -o cppfront.cpp.o -c cppfront.cpp g++ -Wl,--export-dynamic -rdynamic cppfront.cpp.o -o cppfront /usr/lib/libboost_system.so.1.83.0 /usr/lib/libboost_filesystem.so.1.83.0 /usr/lib/libboost_atomic.so.1.83.0 ``` Build `metafunctions`: ```bash ./cppfront metafunctions.cpp2 g++ -std=c++20 -fPIC -o metafunctions.cpp.o -c metafunctions.cpp g++ -fPIC -shared -Wl,-soname,libmetafunctions.so -o libmetafunctions.so metafunctions.cpp.o ``` Build and run `main`: ```bash CPPFRONT_METAFUNCTION_LIBRARIES=libmetafunctions.so ./cppfront main.cpp2 g++ -std=c++20 -o main.cpp.o -c main.cpp g++ main.cpp.o -o main ./main ``` Output: ```output metafunctions.cpp2... ok (all Cpp2, passes safety checks) main.cpp2... ok (all Cpp2, passes safety checks) Hello, world! From my_class ```
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.
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".
At some point those strings will need to be reported anyways, and its unbearable to test without zero error logging.
…st-dll refactor(reflect): remove Boost.DLL dependency by doing DLL handling ourselves
Regenerate `reflect.h2` with the following commands. ``` cppfront -p reflect.h2 -o cpp2reflect.h mv cpp2reflect.h ../include/ ```
On reading <https://stackoverflow.com/questions/11189662/warning-c4251-needs-to-have-dll-interface-to-be-used-by-clients-of-class> and its 3 linked questions, this seems to be mostly safe.
This reverts commit 1817e9c.
This reverts commit 2583d41.
I tested your implementation and it works. But
The loading order of static symbols is not defined in Cpp1 and I had problems with this in the past. I thought about it a little bit more and for this kind of application it should not matter. So option 2 should be fine. |
Oh, I thought about that too, but my concern with it is that it introduces extra global state to cppfront, in order to keep track where symbols belong per library (constraint 3 point c in the constraints post), as you'd need to setup global context that the
Yeah, I too know how problematic order of static constructors can be, but here we defer all of that to cppfront, it can decide how exactly to build up the look-up tree from the registry, and even detect duplicate entries (so we can deal with subtle ODR violations). |
With your current implementation, every library needs to setup a local state. The global state in cppfront is not really required. The register_functions could just call a function handle that is set by cppfront prior to the loading of the dll. I do not really see the problem with constraint 3 point c. You know which dll you are loading and can use this information to tag each meta function. If a second metafunction with the same name is loaded, then you can throw a nice error. This would be nearly the same as it currently is. |
yes, the "set by cppfront prior to loading the dll" part is what I mean by "global state", you'd need a global variable to setup that function handle, which needs to be seen by |
6f2c71b
to
613f352
Compare
This is an proof-of-concept implementation (works, though I won't call it mergeable) to illustrate the idea I was having around in regards to auto-registration of metafunctions and possibly adopt it for the main PR. It also includes the patch to remove
CPPFRONT_METAFUNCTION_LIBRARY
usage by @/MaxSagebaum (thanks btw!). In summary, it does the following:cpp2::meta::register_function
for each metafunction, for which the fully-qualified name is passed, as well as a pointer to the metafunction as arguments to the constructor. (this works similarly to howregister_flag
in cppfront works today)cpp2::meta::register_function
is then provided separately, and its mean to be used when building/linking the final library, along a unambiguousextern "C"
function which becomes the only entry-point of the DLL (all inmeta_lib_impl.cpp
)Usage then becomes (writing down example with multiple TUs/files to better show how this scales):
metafunctions.cpp2
:metafunctions2.cpp2
:main.cpp2
:Build
cppfront
:Build
metafunctions
lib:Build and run
main
:Output:
Generated Cpp1 code for completeness:
metafunctions.cpp
:metafunctions2.cpp
:main.cpp
:Follow up
@MaxSagebaum you mentioned here hsutter#907 (comment) that you'd prefer Option 1, over 2 (what I did here), could you elaborate on why?
After drafting this code, I feel like with this approach, most of the code currently in the main PR becomes a bit redundant:
dll_symbol
unnecessarymeta_lib_impl.cpp
. It also acts as a customization point if needed.What do you think? Would this be a direct improvement? I am not sure which caveats it might have over the existing solution, lets discuss here!