| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
I opened #909 for the design write-up. |
Sorry, something went wrong.
|
Now reflect.h2 doesn't have a dependency on parse.h. To regenerate reflect.h2, use cppfront -p reflect.h2 -o cpp2reflect.h
mv cpp2reflect.h ../include/I use std::any values to build the compilation firewall. |
Sorry, something went wrong.
|
If depending on Boost.DLL is undesirable, loading and using shared libraries is actually quite simple: For POSIX:
For Windows:
It doesn't get much more complicated than that if you just want to call C-named functions. Let me know if you'd like me to support on this. |
Sorry, something went wrong.
Thank you. It would help to not have to depend on Boost.DLL if there is an implementation for the current platform. For GCC, I can use my system's Boost.DLL, |
Sorry, something went wrong.
|
Alright, I'll write the changes and open PR against the branch in your repo, we can discuss further over there once its up 👍🏻 |
Sorry, something went wrong.
|
Thanks to the contribution of JohelEGP#1 by @DyXel and @edo9300 |
Sorry, something went wrong.
| return false; | ||
| (load := load_metafunction(name)) | ||
| if load.metafunction { | ||
| load.metafunction( rtype ); |
There was a problem hiding this comment.
Have you thought about creating a lookup table for already loaded metafunctions?
Sorry, something went wrong.
There was a problem hiding this comment.
Yes.
But this has worked well so far.
Sorry, something went wrong.
|
I'm thinking of just adding a metafunction to lower a declaration with the CPPFRONTAPI macro. There are similarities to export declarations of C++ modules Relatedly, we might eventually want to lower extern "C" declarations. Anyways, I'll try to think of some fitting name to get things moving here. |
Sorry, something went wrong.
I guess technically you could use the C declaration for this case, but you'd need to do the casting to void*, plus namespace handling would be out of the window. Or, since you can detect the signature of a metafunction already, metafunctions detected within a body of another could be lowered to the specific C-magic. Both very ugly hacks but could work for a POC. Indeed there needs to be a way to mark stuff to use C-linkage and/or declare its symbol visibility (in general, a way of spelling this specific stuff, I am sure there are more out there), but I wonder, should that functionality be covered as you mention with a metafunction (c_api)? I didn't think of using them like that, feels like that is not what metafunctions are intended for, and you are still modifying the signature of a function within cpp2 limits, no? It should error out saying its not valid code. Edit: Saw the commit, of course adding a flag to modify the lowering behavior, that works! But I still am questioning whether metafunctions are the right tool for this. |
Sorry, something went wrong.
The undefined symbol being that of a metafunction is a coincidence. |
Sorry, something went wrong.
|
Now I can use a cppfront compiled with the hidden visibility preset. |
Sorry, something went wrong.
|
I have a plan to solve the name lookup problem for good using only the current source file.
The only chance for surprise is when a user expects a non-local name to be found. |
Sorry, something went wrong.
I think having a specific C function per TO/DLL that is able to tell whether or not it has the symbol, has value on its own, for example: CPP2_C_API int cpp2_meta_library_has_metafunction(const char* name, size_t size) {
static std::set<std::string_view> mfs = {"greeter", /*...*/};
return mfs.count(std::string_view{name, size});
}(...or alternatively, a function that gives you a list of strings from which you can build a look-up table) Armed with a function like this you would be able to tell what was exported, but also, you could first check the existence of this same function before proceeding with anything else, granting the opportunity to give the user a good explanatory message, like "cpp2_meta_library_has_metafunction was not found in DLL 'x', are you sure 'x' is a cppfront meta library?". Just my 2 cents though. |
Sorry, something went wrong.
|
Great idea, thank you! |
Sorry, something went wrong.
I don't disagree. And potentially @all_freestanding, @freestanding, @freestanding_deleted, and @hosted
More generally, we still need a replacement for some uses of the preprocessor. |
Sorry, something went wrong.
Yeah, for a POC is fine. I do want people trying this functionality to its maximum and give good feedback to Herb, but I do worry about its future, been thinking about this for a while now so I might as well share my opinion: A user-defined metafunction right now can do way too much, after all, it is arbitrary Cpp1 code being compiled and executed. It was fine being only used internally by the cppfront compiler, but if we give users total freedom to do whatever they want within a metafunction (e.g. execute arbitrary code and generate side-effects) then it'll become a problem once cpp1 reflection/generation lands, assuming it would be a subset of what cppfront offers, there would be competition between what cppfront can do and what was standardized ("teach a man how to fish..."). Another thing that I don't like is the necessary double-pass introduced by this user-defined meta functionality, in order to use a user-defined metafunction you'd need to write Cpp2, which gets lowered to Cpp1, which then gets compiled, and then used somewhere else in Cpp2, this extends the usage from simple transpiler (cpp2 -> cpp1 -> compile and run!) to something more complicated (cpp2 -> cpp1 -> compile meta -> cpp2 -> cpp1 -> compile and run!), which might drive adoption away. For the latter, I think in a perfect world, cppfront would be able to interpret and apply the metafunction itself without having to loopback, and then when cpp1 meta lands (hopefully a good implementation with feedback received from this experiment!), we start generating cpp1 code instead of interpreting, and so users would be minimally affected. |
Sorry, something went wrong.
For C++26 (P2996), the feature sets would be disjoint. |
Sorry, something went wrong.
|
Regarding the above compiler flags:
|
Sorry, something went wrong.
|
By the way, as a side-note: Could we please change the envvar names a little? I got them mixed up during our call (sorry for that), though the conclusion was correct, I think they are too similar: CPPFRONT_METAFUNCTION_LIBRARY But I don't know what to suggest, maybe CPPFRONT_META_LIB_NAME and CPPFRONT_META_LOAD_LIBS? |
Sorry, something went wrong.
|
I also checked the workflow on my machine.
main.cpp2...Contract violation: failed to load DLL './libmetafunctions.so': ./libmetafunctions.so: undefined symbol: _ZNR4cpp24meta16type_declaration10add_memberERKSt17basic_string_viewIcSt11char_traitsIcEE
// FIXME Doesn't work for a library with more than one source providing a metafunction
// See https://github.com/hsutter/cppfront/pull/907#issuecomment-1872644205
I understand that you wanted to fix the situation if you have e.g. two files meta1.cpp2 and meta2.cpp2 both containing a metafunction. Then you need two calls to cppfront and cppfront will inject two times the function cpp2_metafunction_get_symbol_names_. This will create a linker error when the two object files are linked. E.g. g++ -o meta.so meta1.cpp2.o meta2.cpp2.o. Your solution with CPPFRONT_METAFUNCTION_LIBRARY will not solve this case, since in both cases you need to call cppfront with CPPFRONT_METAFUNCTION_LIBRARY=meta.so cppfront meta1.cpp2. This will create the same name for cpp2_metafunction_get_symbol_names_ . If you change the name to CPPFRONT_METAFUNCTION_LIBRARY=meta1.so cppfront meta1.cpp2 then There are two options to address this issue:
greeter: (inout t: cpp2::meta::type_declaration) = {
t.add_member($R"(say_hi: () = std::cout << "Hello, world!\nFrom (t.name())$\n";)");
}
@create_meta_export(greeter)
static const int init_greeter = ::cpp2::meta::add_metafunction(greeter, "greeter"); It think I would prefer option 1. Here is a patch that removes CPPFRONT_METAFUNCTION_LIBRARY: On my machine the reduced layout creating and using a metafunction is now: # Compiling cppfront g++ -rdynamic cppfront.cpp -o cppfront # Creating the library ./cppfront metafunctions.cpp2 g++ -std=c++20 -fPIC -shared -o libmetafunctions.so metafunctions.cpp # Using the library CPPFRONT_METAFUNCTION_LIBRARIES=./libmetafunctions.so ./cppfront main.cpp2 g++ -std=c++20 main.cpp -o main ./main |
Sorry, something went wrong.
|
I think we should at least attempt to follow the auto-registering mechanism that most test framework out there have (and what I hacked very briefly on my test_metafunction branch), that would be Option 2. In fact, I would go as far as saying that maybe the compiler should have some kind of generic infrastructure to aid this "pattern"? Because this can also be extended not just to test frameworks, but to this metafunction registration/look-up problem, registry of bindings for other languages and probably more. Essentially, you write your code as normal, auto registration is generated on a per-TU basis, and then you need a way to signal the generation of a single and unambiguous function per shared object/program (as opposed to per TU), and we'd want the equivalent as well for tear-down. EDIT: Note: I completely side-stepped this issue entirely on my test_metafunction branch by piggybacking on the fact that there must be only 1 main function in the program--I simply inject the "run test framework" in that function. Maybe we could have something similar for metafunctions? |
Sorry, something went wrong.
Is this similar to what I suggested in the Note in this 797 comment? |
Sorry, something went wrong.
If you mean
Then yeah, it would be exactly that. |
Sorry, something went wrong.
|
Sorry, I meant this part (I didn't repaste it here because I didn't want to lose the link to Johel's followup comments about feasibility).
|
Sorry, something went wrong.
|
Ah, I think I see what you mean. Let's grok this in 2 parts: On the double-pass for a single file: I don't think this is can be made full solution currently, as soon as you consider multiple files, everything breaks apart;
Marking metafunctions: I don't think that marking/annotating them is strictly necessary (after all, currently checked-in code simply detects the signature just fine¹), Personally I would argue in favor of actually marking them because:
¹ a bit limited but overall seems decent. Side note: Maybe just me, but its getting harder and harder to keep track of everything that has been discussed, specially since since stuff is getting so meta 😅 I will try to make a post at a later date collecting all the problems and some potential solutions to them in a single post, because this is getting unruly--jumping to several different places to get all the context is hard. |
Sorry, something went wrong.
|
In this post I compile all current "constraints" that I know of, that this specific solution has, as currently implemented in this PR. Please let me know of any developments so I can directly update this post, and let us enumerate/name these constraints so we can more easily refer to them later. ConstraintsConstraint Nº1: Distinct compilation step required for metafunctionsDetailsCurrently, we need to manually identify that we are building a metafunction library as opposed to a regular C++ program. I think it comes with the design of the solution, but having to remember extra steps is inconvenient from a User Experience (UX) standpoint. As I mentioned before in a different post: This extends the usage of cppfront from simple transpiler (Cpp2 -> Cpp1 -> compile and run!) to something more involved when authoring metafunctions or using user-defined ones (Cpp2 -> Cpp1 -> compile meta -> Cpp2 -> Cpp1 -> compile and run!), which may cause potential adopters to lose interest. Constraint Nº2: Inability to apply metafunctions defined in the same TU or "step"DetailsA variation of the classic chicken-and-egg problem, bootstrapping (compilers), etc. Essentially, in order to have a metafunction available for use, you must have first compiled and loaded it as a DLL; Therefore you can't both define a metafunction and use it in the same "step" in your compilation process. In a similar vein, if the user tangles themselves enough, they could be lead to circular dependencies between regular code and metafunction code, breaking causality. Consider this trivial code for example: foo: @bar type = { } // Depends on @bar
bar: (inout t: cpp2::meta::type_declaration) = {
a: foo = (); // Depends on foo
_ = a;
}
// Which came first: foo or bar?Currently, by having a strict separation of metafunction code and regular code, the circular dependency can be avoided somewhat, but it is something to keep in mind if we want to implement a workaround. Constraint Nº3: Multiple symbols with the same nameDetailsThis one I would say spans three distinct levels:
In my opinion, we should treat metafunction definitions just like C++ treats them: It is a violation to have multiple definitions with the same fully-qualified name (and going further for our use-case, even across different loaded DLLs). In cppfront we should actually catch these violations and report them to the user when possible. Constraint Nº4: Need for an entry-point for the DLL ...... and the need for CPPFRONT_METAFUNCTION_LIBRARYDetailsThe main reason (I believe?) why CPPFRONT_METAFUNCTION_LIBRARY is needed right now. There must be a way to identify exported C names in distinct DLLs for "entry-point"s that are loaded simultaneously. As pointed out by @/MaxSagebaum, you can have multiple DLLs with the same name and different definitions, they shouldn't collide if you are manually loading a DLL via dlopen or LoadLibrary. Further, with the current approach, you'd need to define CPPFRONT_METAFUNCTION_LIBRARY for each file/TU in order to get a distinct name that won't have collisions when linking the entire DLL, this might not be viable at scale. Constraint Nº5: Names are "C-namespaced" and name look-up is limitedDetailsThere's a limitation in name look-up explained here. It is marked as a temporary alpha limitation so I expect this to be solvable with extra effort. TODO: Test how limited it is/put example code showcasing the limitation. Constraint Nº6: There's no concept of "construction" or "tear-down" for generationDetailsCurrently, there's no specification or guarantees to the user or metafunction author on how their libraries are loaded, therefore, it is impossible to determine exactly when static constructors and destructors are going to be called within the library, and thus, cannot be relied upon for things like setting up resources or closing down out-of-file generation states. Static local variables can be used as reference starting point. dlopen/LoadLibrary and dlclose/CloseLibrary pairs seem to execute static constructors and destructors properly, so it could be possible to give the users the guarantees that global static objects will be constructed/destructed on a per file (TU) basis. Later, if cppfront is able to handle entire program compilations by itself, an additional guarantee could be given (no idea if its actually a good idea):
Constraint Nº7: The mechanism for out-of-class generation is hard-coded to metafunctionsDetailsIn order to generate a unique entry-point to load the library among other things, out-of-class/out-of-function generation is done, however, this process is not generic/reusable for metafunction authors. Ideally the whole mechanism used right now should be refactored such that it can both be used by this implementation and by users alike. |
Sorry, something went wrong.
|
I have drafted JohelEGP#2 in order to possibly tackle Constraint Nº3, Constraint Nº4 and Constraint Nº5. |
Sorry, something went wrong.
|
@DyXel |
Sorry, something went wrong.
|
Hey, thanks for your response.
I have been there as well, hope to see you active again later!
Will ponder about it and see where that leads me. Cheers! |
Sorry, something went wrong.
|
I think I have thought about it for long enough, and I've decided to take over this feature; As said in my mail to Johel, my agenda is to simplify this solution by means of re-implementing and/or re-factoring the current work, and to solve several of the constraints mentioned in my comment above while doing so. Here's my current plan, re-using some (most?) of the work in this PR:
If there are no objections to this roadmap, I would start working on it next weekend. @MaxSagebaum do I have your permission to reuse your work from #809 if needed? |
Sorry, something went wrong.
|
@DyXel yes sure. You have my permission. Your plan sound good to me. |
Sorry, something went wrong.
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>). For `cppfront` to emit program-defined metafunctions, the environment variable `CPPFRONT_METAFUNCTION_LIBRARY` should be set to the library's path. For `cppfront` to load program-defined metafunctions, the environment variable `CPPFRONT_METAFUNCTION_LIBRARIES` should be set to the `:`-separated library paths of the used metafunctions. Here is an example of program-defined metafunctions. 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++ -std=c++20 -o cppfront.cpp.o -c cppfront.cpp g++ -Wl,--export-dynamic -rdynamic cppfront.cpp.o -o cppfront ``` Build `metafunctions`: ```bash CPPFRONT_METAFUNCTION_LIBRARY=libmetafunctions.so ./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 ``` \@edo9300 Please, share your GitHub-provided `no-reply` email (<https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors>). Co-authored-by: Edoardo Lolletti <> Co-authored-by: Dylam De La Torre <DyXel04@gmail.com>
| Back | FazBrowse Home | New Git URL |
feat: evaluate program-defined metafunctions (based on #797)
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).
For cppfront to emit program-defined metafunctions,
the environment variable CPPFRONT_METAFUNCTION_LIBRARY
should be set to the library's path.
For cppfront to load program-defined metafunctions,
the environment variable CPPFRONT_METAFUNCTION_LIBRARIES
should be set to the :-separated library paths of the used metafunctions.
Here is an example of program-defined metafunctions.
The commands were cleaned up from the CMake buildsystem in #797.
metafunctions.cpp2:
greeter: (inout t: cpp2::meta::type_declaration) = { t.add_member($R"(say_hi: () = std::cout << "Hello, world!\nFrom (t.name())$\n";)"); }main.cpp2:
my_class: @greeter type = { } main: () = my_class().say_hi();Build cppfront:
g++ -std=c++20 cppfront.cpp -o cppfront # Note: check that we don't need to specify these flags explicitly, if they're defaults # g++ -std=c++20 -o cppfront.cpp.o -c cppfront.cpp # g++ -Wl,--export-dynamic -rdynamic cppfront.cpp.o -o cppfrontBuild metafunctions:
Build and run main:
Output: