-
Notifications
You must be signed in to change notification settings - Fork 249
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
feat: evaluate program-defined metafunctions (based on #797) #907
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
I opened #909 for the design write-up. |
Now To regenerate cppfront -p reflect.h2 -o cpp2reflect.h
mv cpp2reflect.h ../include/ I use |
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. |
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, |
Alright, I'll write the changes and open PR against the branch in your repo, we can discuss further over there once its up 👍🏻 |
This comment was marked as outdated.
This comment was marked as outdated.
Thanks to the contribution of JohelEGP#1 by @DyXel and @edo9300 |
This comment was marked as resolved.
This comment was marked as resolved.
source/reflect.h2
Outdated
return false; | ||
(load := load_metafunction(name)) | ||
if load.metafunction { | ||
load.metafunction( rtype ); |
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.
Have you thought about creating a lookup table for already loaded metafunctions?
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.
Yes.
But this has worked well so far.
This comment was marked as resolved.
This comment was marked as resolved.
I'm thinking of just adding a metafunction to lower a declaration with the There are similarities to Relatedly, we might eventually want to lower Anyways, I'll try to think of some fitting name to get things moving here. |
I guess technically you could use the C declaration for this case, but you'd need to do the casting to 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 ( 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. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
The undefined symbol being that of a metafunction is a coincidence. |
Now I can use a |
This comment was marked as resolved.
This comment was marked as resolved.
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. |
This comment was marked as resolved.
This comment was marked as resolved.
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 " Just my 2 cents though. |
Great idea, thank you! |
I don't disagree. And potentially
More generally, we still need a replacement for some uses of the preprocessor. |
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 ( 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. |
For C++26 (P2996), the feature sets would be disjoint. |
#907 (comment) I briefly mentioned something similar here. Thinking about it, what you'd need is a static object for which you can register the metafunctions automatically when the DLL is loaded, then you can have a per-DLL function with that unique name that gives you back the mapping between a name and the actual metafunction. it would also be a good place (needed even?) for a "teardown", as we discussed. |
Regarding the above compiler flags:
|
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:
But I don't know what to suggest, maybe |
I also checked the workflow on my machine.
I understand that you wanted to fix the situation if you have e.g. two files There are two options to address this issue:
It think I would prefer option 1. Here is a patch that removes On my machine the reduced layout creating and using a metafunction is now:
|
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 |
Is this similar to what I suggested in the Note in this 797 comment? |
If you mean
Then yeah, it would be exactly that. |
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).
|
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. |
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 metafunctionsCurrently, 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 ( Constraint Nº2: Inability to apply metafunctions defined in the same TU or "step"A 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 nameThis 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
|
I have drafted JohelEGP#2 in order to possibly tackle Constraint Nº3, Constraint Nº4 and Constraint Nº5. |
@DyXel |
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! |
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? |
@DyXel yes sure. You have my permission. Your plan sound good to me. |
4daa88b
to
6f2c71b
Compare
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>
6f2c71b
to
613f352
Compare
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
:main.cpp2
:Build
cppfront
:Build
metafunctions
:Build and run
main
:Output: