-
Notifications
You must be signed in to change notification settings - Fork 251
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
Design for program-defined metafunctions for cppfront #909
Comments
Is my understanding correct that by loading you mean final user's program will load some library like DLL? |
The metaprogramming environment is just a compile-time thing, the very end result that you'd compile and run on your embedded system would be Cpp1 code, which at this stage would not involve any kind of runtime overhead (unless of course, generated by the metafunctions themselves). |
In regards to name lookup in DLLs, the secret sauce is indeed to always use
It is likely that all of this could even be done with an extra metafunction that registers somewhere outside and ensures the resulting metafunction is lowered with the correct cppfront-specific mangling (which would be then a implementation detail):
Could potentially generate code similar to this: namespace n1 {
namespace n2 {
auto my_meta_function(meta::type_declaration& t) -> void {
// ...
}
}
}
extern "C" void __cppfront_n1_n2_my_meta_function(void* t) {
n1::n2::my_meta_function(*static_cast<meta::type_declaration*>(t));
}
// Outside code could then:
auto* my_func = dlopen("__cppfront_n1_n2_my_meta_function");
// After resolving the full name via the exposed "tree". This of course would still be limiting, but I think that having something greppable that we could get rid of later is very helpful in any case (im talking about |
I have thought about that for supporting an overload with an Doesn't |
Oh yeah, seems you are right. I guess that would simplify generation in that case. From cppreference:
This implies what you stated. |
The problem with having to perform name lookup in cppfront is shared with #666 (comment).
That would require |
I would raise the question "Why do we need overload detection?" The interface for calling a metafunction is already defined. It needs to have one argument which is of type So I currently do not see the use case for an overload detection in the library loader. Comments to the document:
|
We want to support both of these function types:
The one with the
|
Ok, one could argue that the If both need to be supported, then it would result in a fixed set of function definitions. These could be represented by an enum and the entry name of the enum could be included in the function mangling. E.g. One idea would be to include the declaration kind in the enum. This would allow better error messages. The enum fields could be: Metafunction declaration could then include this:
Maybe make it a flag enum and have the |
This is all we need. |
The lack of name lookup is a big issue.
These aren't my concerns:
These cases, which surprise users, are my concerns:
Right now, Cpp2 source files are processed in isolation. For starters, it would serve us to keep a structure of introduced names for name lookup. This could also serve as a starting point for other features that aren't possible to implement right now. In a better world, |
I think this is a solvable problem today if I can loop over a DLL's symbols. |
The were also concerns when cross-compiling with the Circle model of meta-programming. Remember that in this design a metafunction is loaded from a library. If cppfront can't be cross-compiled, then metafunctions can't be used. |
The pattern that is common in many applications that load plugins as dynamic libraries is to have a common For example the interface might have an |
IIUC, that inverts the logic so that plugins register themselves, right? |
Yes, mostly. The application still needs to know that libraries to load but this process is just reduced to system calls to load the library and find one "C" function with a known name. |
A note about this:
My understanding is that |
Design for program-defined metafunctions for cppfront
Introduction
This write-up presents a design to extend cppfront to evaluate program-defined metafunctions.
Conception
Support for metafunctions was first added by commit d8c1a50,
"First checkin of partial meta function support, with
interface
meta type function".Its commit message also included the following sentence.
After a lot of thinking, the idea of a "Cpp2 interpreter" seemed backwards to what cppfront is.
Cppfront takes Cpp2 and lowers it to Cpp1, just like
Cfront takes Cpp1 and lowers it to C.
Interpreting Cpp2 could then be taken to mean one of two things:
This way, interpreted Cpp2 (i.e., metafunctions) is just as capable as normal Cpp2 code.
This would be like
constexpr
in C++11, and would probably evolve similarly.Interpretation 1 means changing what cppfront fundamentally is.
Interpretation 2 feels unsatisfactory.
It is very constrained and without the power of the whole language at your disposal.
I thus realized that there is an alternative to interpreted Cpp2.
That alternative is loading a metafunction compiled in a library during the execution of
cppfront
.This model doesn't change what cppfront is.
Additionally, a metafunction is normal Cpp2 code, just like the implementations of built-in metafunctions.
Counterpoints
In this design, a metafunction is "normal Cpp2 code".
In the Circle model of meta-programming, "normal Cpp1 code" can be executed at compile-time.
This has raised concerns, quoted below, that are relevant to the present design.
In our case, rather than compile-time, it's during metafunction evaluation.
Alternatives
Any alternative that requires recompiling
cppfront
or hard-coding metafunctions isn't viable at scale.I also considered whether we could use Cpp1's
constexpr
andconsteval
.These don't serve us if we are to use an existing
cppfront
program.Consider the counterpoints.
Given Cpp1's
if consteval
, aconstexpr
function can't be guaranteed to not use IO.That said, it could be possible to require a metafunction to be
constexpr
and to actually evaluate it during constant evaluation to produce the updated type.
The technique to implement that would me similar to the one presented in
Interactive C++ in a Jupyter Notebook Using Modules for Incremental Compilation - Steven R. Brandt.
But that is not this design (and I haven't explored such a design).
Counter-counterpoints
Maybe a metafunction can be required to be
@pure
(#797 (comment)).Then, even thought a metafunction is still normal Cpp2 code, it isn't as problematic.
Although
@pure
still seems too restrictive.Design
This is based on what I learned from studying the documentation of Boost.DLL.
We need to emit a metafunction as an
extern "C"
symbol.The mangling of a Cpp1 symbol is experimental and not as portable (https://www.boost.org/doc/libs/master/doc/html/boost_dll/mangled_import.html).
When loading the symbol of a metafunction, we need to use the same emitted name.
This means that we need a protocol for the symbol name and to "C namespace" it.
In its simplest form, we just need a function that,
given the Cpp2 name of a metafunction (as
@
-used),it returns a function object that evaluates the metafunction.
There is an implementation of this design at #907.
Details on how this design was applied, as well as other implementations details, can be found there.
Evolution
Name lookup
Up until now, cppfront has been able to rely on the name lookup of lowered Cpp1 code.
But this design introduces an evaluation point that happens outside the C++ abstract machine.
It wants to look up a name that has already been compiled in Cpp1
and use it as named in Cpp2 code before the Cpp2 code has been lowered to Cpp1.
The current design doesn't consider name lookup.
It expects a metafunction name to be
@
-used unqualified and to follow C "namespacing" conventions.Dependency scanning
The current design only requires specifying a protocol for lowering and loading a metafunction.
To author and consume a metafunction at scale, we also need dependency scanning, pretty much like Cpp1 modules.
Many of us use a build system to manage the complexity of building Cpp1 code.
We would like to avoid having
cppfront
run on a Cpp2 source that hasn't changedand if all of the libraries that provide the metafunctions it uses haven't changed.
Conversely, we want
cppfront
to rerun if one of those libraries has changed.We can't know which metafunction a Cpp2 source uses
without manually duplicating this information in the build system description.
cppfront
can't just emit the dependency information after the fact (like Cpp1 compilers on#include
d headers)because the libraries need to have been built before it starts evaluating the metafunction.
It has been suggested that
cppfront
could have a command line argument for compiling a metafunction library.That would obviate the need for a dependency scanner, but this inversion of the build logic has drawbacks.
There was an article that I can't find, I think linked from the LLVM Discourse,
about how some other language's compiler (Go or Scala?) forked itself to build a module's sources in parallel.
That ended up resulting in file system races in very rare cases.
They rewrote their module compilation system to not fork itself and instead rely on their build system.
That fixed the issues, and even (significantly? in some cases?) reduced compile times.
I think the general issue is attempting to do what should be done at a higher level.
The higher level being that of the build system.
The CMake support for Cpp1 modules already went in the direction of a dependency scanner
(along with a long trail of papers for proper modules support).
I think it'd be unwise to go in the other direction,
which doesn't even seem to have build system support.
The text was updated successfully, but these errors were encountered: