Skip to content

Draft: Add constexpr support in many expressions

Reference issue

What does this implement/fix?

This implements constexpr support for many common expressions, allowing compile-time computation of basic matrix arithmetic. This is far from complete, but it allows evaluating basic matrix expressions such as m = m1 + m2 or m2 = m1*m2 at compile-time (gcc fails to do the latter due to a bug which appears to be fixed in the forthcoming gcc 12). I've constantly tested with gcc 11.2 and clang 13.0 on mac, as well occasionally with msvc (Visual Studio 2022) on Windows. Please see the included testcase for what it supported.

I'm especially happy that quaternion calculations at compile-time work, this covers most of my initialization uses :) In order to prove that actual matrix math at compile time is also possible, I implemented solving of triangular matrices for vectors.

There are a few caveats:

  • most of this is C++20 Supporting C++14 would need a rewrite of initialization, otherwise only Maps to static global buffers can be used in C++14 mode. Dynamic matrices cannot be supported by C++14 as a matter of principle. I only recently discovered __builtin_is_constant_evaluated (supported by clang and gcc) which would allow implementing things like matrix addition (but not multiplication!) in C++14 (with that extension)
  • actual math is impossible. In C++20 most math functions aren't constexpr. Imagine doing anything without std::abs and std::sqrt. gcc supports them though, see below.
  • (mentioned above) a bug in gcc 11.2 prevents matrix products from working. The corresponding tests are disabled if gcc is used as compiler
  • tests/householder.cpp fails with this patch. Having to check for constant evaluation in absence of if constexpr means doubling some code that will never be executed 😞 I'm not sure why this ends up being a size mismatch though.
  • this is by no means exhaustive. So far I went forward by making individual cases work, in order to avoid changing code that I'm not actually testing. A lot of functions could be changed to constexpr in a blanket fashion, but the way forward is up to discussion of course.

Details on patch series: the sequence of patches contains two preparatory patches: one which adds an EIGEN_CONSTEXPR20 macro which is used to flag functions that need C++20 (i.e. either they call std::is_constant_evaluated via the wrapper internal::is_constant_evaluated in the patch, or they are constructors which do not initialize all underlying memory). The second adds a wrapper for std::construct_at to avoid placement new.

The next two patches add lots of EIGEN_CONSTEXPR annotations, the first to some of the utility macros which are used to generate some of the more repetitive parts of the code, the second to the functions exercised by the testcase (added later in series). As for the utility macros, since constexpr destructors are not possible in C++14 and non-virtual empty destructors don't really do anything, I removed them, but not on NVCC (i.e. CUDA). That requirement was pointed out to me on discord.

The next patch disables aligment checks during constant evaluation where it doesn't make sense (there is no SIMD code in constant evaluation, also the mmeory allocated there is completely separate from the runtime). Finally the next patch adds the actual functionality, by making the evaluation code fall back to the non-SIMD defaults in constant evaluation mode. It adds a testcase which exercises all the functionality changed (I think). The testcase relies on C++20 in large parts, but make check runs with C++14, so most of the testcase is disabled in the standard operation, but it can be compiled manually in C++20 mode to verify that it works

Near term:

  • enable running specific tests in C++20 mode to allow exercising constexpr code without manual intervention

Future items:

  • understand and fix the issue with householder.cpp
  • expand scope of this work to more functions
  • add macros to guard gcc's constexpr math functions, enable them where possible
  • add __builtin_is_constant_evaluated support to allow more functionality on C++14, but this is probably only useful together with the next time
  • fixed-size objects can be made to work in C++14, by adding a class of constructors that initialize all memory in the knowledge that it is immediately overwritten by other parts of the construction. A proof of this concept exists on my HDD and works, but since I wasn't aware of __builtin_is_constant_evaluated I didn't pursue this for this patch
  • operator<< initialization is nice and useful and I have a patch that works, but gcc and clang disagree on the lifetimes of the temporary objects involved (it works with gcc), so I don't know if it is correct. For the time being, I submitted bugs to both compilers, and we'll have to see whose interpretation is correct.
  • in C++14 constexpr is a keyword so I would suggest to get rid of EIGEN_CONSTEXPR, also constexpr implies inline, so I would suggest a cleaning pass where all instances of inline EIGEN_CONSTEXPR are reduced to just constexpr

Additional information

Edited by Tobias Schlüter

Merge request reports