-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[RVec] Forward declare VDT functions in RVec.hxx header
#22927
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: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,28 @@ | |
| #include <vector> | ||
|
|
||
| #ifdef R__HAS_VDT | ||
| #include <vdt/vdtMath.h> | ||
| // Forward declarations of the vdt functions used by the fast_* helpers below. | ||
| // We deliberately avoid including <vdt/vdtMath.h> from this public header so | ||
| // that we don't rely on this header always being in the include path. | ||
| namespace vdt { | ||
| inline double fast_exp(double); | ||
| inline double fast_log(double); | ||
| inline double fast_sin(double); | ||
| inline double fast_cos(double); | ||
| inline double fast_tan(double); | ||
| inline double fast_asin(double); | ||
| inline double fast_acos(double); | ||
| inline double fast_atan(double); | ||
|
|
||
| inline float fast_expf(float); | ||
| inline float fast_logf(float); | ||
| inline float fast_sinf(float); | ||
| inline float fast_cosf(float); | ||
| inline float fast_tanf(float); | ||
| inline float fast_asinf(float); | ||
| inline float fast_acosf(float); | ||
| inline float fast_atanf(float); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should that go in vdt itself as vdt_fwd.h?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's worth it, these forward declaration headers make more sense if the types are complicated and expected to change sometimes (e.g. template types where template arguments are expected to be added). The VDT function signatures have not changed in 14 years though, so I wouldn't consider a forward declaration header necessary.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I think that should be up to the maintainer of vdt, @dpiparo
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but that section can go out of sync if upstream changes. Logistically putting it in the vdt library is the right layering approach afaict.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then it doesn't solve the problem anymore: the whole point of this change is to make In the case where you need VDT, you can simply include it yourself. This follows the "only pay for what you use" model that we're generally trying to follow now.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize that I do not understand the whole idiom. Eg: #define RVEC_VDT_UNARY_FUNCTION(F) RVEC_UNARY_FUNCTION(F, vdt::F). Why do we carry all these in the public header file? If they are implementation defined we might be able to put that logic in a local header file.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Also the "standalone vdt forward declaration header" would have to be in a place where ROOT can find it at runtime, also increasing the include path surface that this PR tries to minimize to begin with. Alternatively, ROOT would have to ship it, which goes against the direction of making ROOT dependencies more modular.
Thanks!
These template <typename T>
RVec<PromoteType<T>> fast_expf(const RVec<T> &v) {
RVec<PromoteType<T>> ret(v.size());
std::transform(v.begin(), v.end(), ret.begin(),
[](const T &x) { return vdt::fast_expf(x); });
return ret;
}For the most common types
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I do not get that part. We can still have the fwd declaring header in vdt and only include it in R_HAS_VDT context. We can make it even more convenient by adding an X-macro facility that will expand for each macro defined in the RVec.hxx file.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I.e. one can get this error (https://cdash.cern.ch/tests/13049607) |
||
| } // namespace vdt | ||
| #endif | ||
|
|
||
|
|
||
|
|
@@ -1411,7 +1432,7 @@ RVecs as arguments. | |
| - erf, erfc | ||
| - lgamma, tgamma | ||
|
|
||
| If the VDT library is available, the following functions can be invoked. Internally the calculations | ||
| If the VDT library is available and the `<vdt/vdtMath.h>` header included, the following functions can be invoked. Internally the calculations | ||
| are vectorized: | ||
| - fast_expf, fast_logf, fast_sinf, fast_cosf, fast_tanf, fast_asinf, fast_acosf, fast_atanf | ||
| - fast_exp, fast_log, fast_sin, fast_cos, fast_tan, fast_asin, fast_acos, fast_atan | ||
|
|
||
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.
I think this is might be the wrong approach. If ROOT says it has vdt, it should have vdt, and not only broken forward declarations.
What we could do is check whether the header can be included using
__has_include, and then perform the inclusion.If
__has_includeis false, the functions get disabled. Forcing the user to include a header that they might not know and might have trouble finding seems to break most expectations. An equivalent example would bein order to call
vector.begin();. It should be self-contained.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.
If I understand correctly, what you have in mind is to stabilise the interpreter?
In that case, would forward declaring the functions when the header can't be included do the trick, and including it when it is found?
In general, things that are declared should also work, but maybe we should make an exception if it prevents interpreter startup.
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.
Not at all what I have in mind! Let me try to state the point better, as I obviously explained it badly the first time 😆
The core thing I keep failing to get across is that this change separates two things that are currently entangled:
fast_*methods in RVec exist? This is determined by how ROOT was built (R__HAS_VDT), baked into RConfigure.h. Single source of truth on whether VDT was available at build time.#includes RVec.hxxat runtime (or downstream build time if you use ROOT as a library)? This is entirely separate question about the downstream build, in particular if ROOT was built withvdt=ONandbuiltin_vdt=OFF, in which case the availability of VDT headers might not be the same at build time and runtime.With
builtin_vdt=OFF, vdt is a private system dependency of libvecops. So (1) can be true while(2) is false, and today that combination means someone who only wants
Sum/Filter/Map(i.e. ~95% of RDataFrame users, who never touchfast_*) gets fatal error:'vdt/vdtMath.h' file not foundfor a dependency they never asked for. That's the bug I want to fix: makeRVec.hxxself-contained so vdt is needed only at link time, and only if you actually callfast_*.On
"if ROOT says it has vdt, it should have vdt, not broken forward declarations": agreed on the sentiment, and I don't think these are broken or can easily break. They can't silently drift, becauseRVec.cxxcompiles against the real<vdt/vdtMath.h>when it emits the explicitfloat/doubleinstantiations. If a signature ever changed, the ROOT nightly build fails with a conflicting-declaration error. And these signatures haven't moved in 14 years, so it's not really something that we expect to drift anyway.Worth noting we already do exactly this pattern in other places, for the same reason:
core/imt/inc/ROOT/RTaskArena.hxx: the header comment literally says"Necessary in order to keep tbb away from ROOT headers."
PyObject(struct _object; typedef _object PyObject;) in TPython.h, etc, so you can build against those without Python's dev headers on your include path, even though ROOT very much "has Python"On "forcing the user to include a header they might not know": well yes that's true, as you see in the tutorial change I had to make, you might need to include VDT. But that only affects the rare power users that use the
fast_*functions in the interpreter (in compiled code, they will resolve to the extern definitions inRVec.cxx). And the only place to learn about these is exactly from the tutorial that will tell you to include vdt now. So this is the small sacrifice I'd be willing to make to increase relocatability to systems without VDT.On
__has_include: I'd push back on that one specifically, because it re-entangles (1) and (2): it makes the existence offast_*depend on the downstream include path (just like Vassil's previous suggestion with the forward declaration header in VDT). The same ROOT install would then offerfast_*in one translation unit and not another depending on-Iflags, and it puts the vdt header back into the vecops module. Which is #9736 again. The forward-decl approach keeps one deterministic source of truth (R__HAS_VDT) and makes the header compile everywhere.Does this framing change how you see it?
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.
Only if vdt does not have its own pcm file which would be the "right" layering of the system. Perhaps adding a modulemap for vdt is not a bad way out...
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.
I think we're talking past each other here: the situation I want to improve is people compiling their own code against ROOT with a plain compiler that isn't going through ROOT's pcms at all. For them,
#include <vdt/vdtMath.h>in RVec.hxx is just an annoying include if they don't use thefast_*functions that fails when the vdt headers aren't on the machine: thevdt=ONbuiltin_vdt=OFF+ vdt-not-installed case. A modulemap doesn't help there, I think modules organize the declarations when the header is available, they don't produce it when it's absent. The forward declarations are what makeRVec.hxxtruly self-contained for that plain-compile case when one doesn't want to use the VDT features.I guess the reason you circle back on modules and layering here is that you opened #9736, which isn't what this PR addresses directly. But I'd argue that issue is now better closed than met with new machinery: the concrete case it was opened for,
Vcleaking into a dictionary, is gone from the codebase, and this PR removes thevdtin RVec.hxx case too. I'd rather not add a diagnostic to guard a misconfiguration that can no longer happen. That's maintenance cost for a case that doesn't exist. Could that be where we're talking past each other? What would be your preferred way to address #9736 at this point?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.
What about disconnecting this PR from #9736. Would that be okay for you? The problem I'm trying to solve here has nothing to do with modules, as I said, and more with using
RVec.hxxin compiled code when the VDT headers aren't on the include path. Would it be okay to go ahead with this PR and leave #9736 open for the general case of modules depending on external headers (even though it's hypothetical for ROOT at this point, it might come back)?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.
The issue is more subtle I guess. However, with modules and when vdt is on, at runtime when we start root we have to provide the include path for the vdt header files. That’s the underlying issue we are trying to solve right?
If so, then it is the same and worse for modules because instead of getting error: file not found, ROOT crashes.
What I am trying to say (and perhaps missing something) is that the forward declarations solve it nicely by dissolving the relationship between the headers of root and vdt. What I was/am worried about is that forward declaration logic is disconnected from vdt and if vdt adds a new function we don’t see it automatically. Indeed that does not make things worse from status quo as we also enumerate all we know about.
So, my review was mostly on improving the invalidation mechanism and was not really blocking this fix. My wish is if we can get rid of the vdt declarations from that header altogether would be the best and move everything to the implementation but I do not see a possible way forward.
As I said originally, the current approach is landable because it is an improvement over the status quo.
Uh oh!
There was an error while loading. Please reload this page.
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.
OK, I understand better, but there is maybe a problem. Most of the vdt functions are in the header, and they are supposed to get inlined to allow for auto-vec throughout entire loops. Forward-declaring them breaks vdt, because you can't link to these. I see a few ways forward:
If the header is in the include path, ROOT works with vdt. If it's not, the functions don't get declared. Does this solve the problem of the 95%?
2a) We outline all vdt usage into libROOTVecOps. The problem here is that we can't do it for every possible template argument, but that doesn't matter because vdt anyway only supports float/double IIRC.
2b) We change RVec to use the library functions of vdt. Note that currently, we ignore the vdt library throughout ROOT and only inline the functions from the headers. For RVec in particular, however, we could move over to the library vdt functions at the cost of not being able to inline them. Now, you can forward-declare them, and we don't need vdt headers the are publicly visible anywhere in ROOT. The usage of VDT can now become a
PRIVATEcmake dependency, and we don't need to install the headers. We still need the library, unless we link statically.Looking at what I wrote, I would go for 1. or for 2.a. What do you think?