cppfront
cppfront copied to clipboard
[SUGGESTION] Improve `in` parameter passing (restore old behavior, exclude problem cases only)
Restore in optimization for class types
At first, in parameters were optimized for class types.
Cppfront's optimization of in parameters was faithful to
F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const.
Then, #270 brought to light that the optimization didn't play well with Cpp2's order independence.
After #317, the net effect is that Cppfront never optimizes in parameters of class type.
I still lament the fact that we lost this optimization to Cpp2 itself.
Similar to #317, here I propose to "improve in parameter passing (restore old behavior, exclude problem cases only)".
https://github.com/hsutter/cppfront/issues/270#issuecomment-1475708424 describes the general problem.
However, it generally doesn't apply to Cpp2.
Cpp2 generally requires parameters of complete type.
That's because most functions are initialized and will have a Cpp1 definition emitted.
So we can generally try to optimize in parameters of class types to pass-by-value.
I'd love to have this optimization restored for the general case.
The conditions when applying cpp2::in is wrong are two only (AFAIK).
- The case of #270. In the current source order, it only applies to parameters of type that come later in source order.
- Virtual functions.
An uninitialized
virtual thisfunction can have parameters of incomplete type. When declaring anoverride thisorfinal thisfunction in a different TU, we can't know if itsinparameters of class type had opted-into the optimization with the current Cppfront.
I propose that we identify these conditions,
and only when at least one of these conditions is true,
do we continue emit a version of cpp2::in that doesn't optimize class types.
Otherwise, we should emit a version of cpp2::in that does try to optimize class types.
Identified problems:
- In templates, the optimization is observable. This is evident with function types (e.g., see https://github.com/hsutter/cppfront/pull/526#issuecomment-1615317452). With this suggestion, the effect would, once again, extend to optimized class types.
- Similar to #533, we might need to do lexical analysis to determine that a parameter's type indeed names a type that shouldn't be optimized.
References:
- #270.
- https://github.com/hsutter/cppfront/issues/270#issuecomment-1475708424.
- https://github.com/hsutter/cppfront/issues/273#issuecomment-1538234710.
- #317.
- https://github.com/hsutter/cppfront/pull/526#issuecomment-1615317452.
- #533.
- https://github.com/hsutter/cppfront/discussions/641#discussioncomment-6869498.
- https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-in.
Related links:
Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code? No.
Will your feature suggestion automate or eliminate X% of current C++ guidance literature?
Yes.
F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const.
Describe alternatives you've considered. None.
Identified problems:
- In templates, the optimization is observable. This is evident with function types (e.g., see https://github.com/hsutter/cppfront/pull/526#issuecomment-1615317452). With this suggestion, the effect would, once again, extend to optimized class types.
Here's an example with the main branch: https://cpp2.godbolt.org/z/b8s5vqsEG.
- Similar to #533, we might need to do lexical analysis to determine that a parameter's type indeed names a type that shouldn't be optimized.
Here's an example that should continue using a version of cpp2::in that doesn't optimize class types:
https://cpp2.godbolt.org/z/sE6b5z8Kx.
my_type_alias: type == my_type;
f: (_: my_type_alias) = { }
my_type: @struct type = { }
main: () = { }
my_type_alias doesn't meet the condition to never optimize, but the type it names does.
The conditions when applying
cpp2::inis wrong are two only (AFAIK).
- The case of #270. In the current source order, it only applies to parameters of type that come later in source order.
The pedantic condition is parameters of type that come later in source order emitted in Phase 2 "Cpp2 type definitions and function declarations". I.e., when a parameter is part of the declaration of all those things emitted in Phase 2. This would include cases like: https://github.com/hsutter/cppfront/blob/a92fdc8b63497e19e6f1ad0b3981d19acebf0926/regression-tests/pure2-bugfix-for-non-local-function-expression.cpp2#L5-L7
Here's my proposed version of a cpp2::in that optimized class types: https://cpp2.godbolt.org/z/W9cP8a64h.
template<class T, bool V = (requires { sizeof(T); })> constexpr bool is_complete{V};
template<typename T>
constexpr bool prefer_pass_by_value_optimized =
sizeof(T) <= 2*sizeof(void*)
&& std::is_trivially_copy_constructible_v<T>;
template<typename T>
requires std::is_array_v<T> || std::is_function_v<T>
constexpr bool prefer_pass_by_value_optimized<T> = false;
template<typename T>
requires is_complete<T>
using in_optimized =
std::conditional_t <
prefer_pass_by_value_optimized<T>,
T const,
T const&
>;
Compared to main's cpp2::in:
- It removes
std::is_class_v<T> || std::is_union_v<T>to enable optimizing those. - It replaces
(!std::is_void_v<T>)withis_complete<T>for the same reasons as the former (https://github.com/hsutter/cppfront/issues/317#issuecomment-1499247426). This gives a clear error message aboutis_complete<C>beingfalserather than about usingsizeofon an incomplete typeC.
- Similar to #533, we might need to do lexical analysis to determine that a parameter's type indeed names a type that shouldn't be optimized.
Here's an example that should continue using a version of
cpp2::inthat doesn't optimize class types: https://cpp2.godbolt.org/z/sE6b5z8Kx.my_type_alias: type == my_type; f: (_: my_type_alias) = { } my_type: @struct type = { } main: () = { }
my_type_aliasdoesn't meet the condition to never optimize, but the type it names does.
This can still result in a missed optimization due to a false positive.
// header.h2
ns: namespace = {
my_type: @struct type = { }
}
// main.cpp2
#include "header.h2"
ns: namespace = {
my_type_alias: type == my_type;
f: (_: my_type_alias) = { }
}
my_type: @struct type = { }
main: () = { }
The truth is that my_type_alias names the complete type ns::my_type.
But lexical lookup suggests it names ::my_type.
The result is a non-optimized parameter that could have been optimized.
Afterwards, that parameters outside special member functions should be optimized.
In the distant future, this parameters should be optimized (with an explicit object parameter).
https://github.com/hsutter/cppfront/issues/270#issuecomment-1475708424 describes the general problem. However, it generally doesn't apply to Cpp2. Cpp2 generally requires parameters of complete type. That's because most functions are initialized and will have a Cpp1 definition emitted. So we can generally try to optimize
inparameters of class types to pass-by-value. I'd love to have this optimization restored for the general case.
This might not be true anymore given that
.h2 headers permit splitting the interface and implementation to different TUs
(see https://github.com/hsutter/cppfront/issues/705#issuecomment-1809468010).
Working around such an issue might be another use case for inout _: const _ parameters (#696).
Opting out of the in optimization is certainly a use case for inout _: const _.
Certain things in https://wg21.link/temp are specified with lexical analysis (AFAIU).
If the same kind of analysis to not optimize in parameters falls short, reach out for inout _: const _.
AFAIK, optimizing shouldn't be a problem in modules. The strong ownership model means you can't move the definitions anywhere outside that module.
https://github.com/hsutter/cppfront/issues/270#issuecomment-1475708424 describes the general problem. However, it generally doesn't apply to Cpp2. Cpp2 generally requires parameters of complete type. That's because most functions are initialized and will have a Cpp1 definition emitted. So we can generally try to optimize
inparameters of class types to pass-by-value. I'd love to have this optimization restored for the general case.This might not be true anymore given that
.h2headers permit splitting the interface and implementation to different TUs (see https://github.com/hsutter/cppfront/issues/705#issuecomment-1809468010).
Let me clarify.
Compiling an .h2 header with -pure-cpp2 lowers it to two Cpp1 headers.
An .h header with the interface and an .hpp header with the implementation (see https://github.com/hsutter/cppfront/issues/594#issuecomment-1793627053).
This merely adds another condition when optimizing in parameters is wrong.
Why `in` parameters can generally be optimized in Cpp2 (from the opening comment):
https://github.com/hsutter/cppfront/issues/270#issuecomment-1475708424 describes the general problem. However, it generally doesn't apply to Cpp2. Cpp2 generally requires parameters of complete type. That's because most functions are initialized and will have a Cpp1 definition emitted. So we can generally try to optimize
inparameters of class types to pass-by-value. I'd love to have this optimization restored for the general case.The conditions when applying
cpp2::inis wrong are two only (AFAIK).
- The case of #270. In the current [source order][], it only applies to parameters of type that come later in [source order][].
- Virtual functions. An [uninitialized
virtual thisfunction][] can have parameters of incomplete type. When declaring anoverride thisorfinal thisfunction in a different TU, we can't know if itsinparameters of class type had opted-into the optimization with the current Cppfront.
This is because users can consume the interface without completing its types. Only the TU that provides the implementation needs to complete them all.
However, we can still optimize in parameters when this new condition doesn't hold:
- When compiling an
.h2header without-pure-cpp2(everything lowers to the.hheader). - When compiling a module, as mentioned in the comment right above (https://github.com/hsutter/cppfront/issues/666#issuecomment-1809515512).
Here's an example that should continue using a version of
cpp2::inthat doesn't optimize class types: https://cpp2.godbolt.org/z/sE6b5z8Kx.my_type_alias: type == my_type; f: (_: my_type_alias) = { } my_type: @struct type = { } main: () = { }
my_type_aliasdoesn't meet the condition to never optimize, but the type it names does.
You can replace my_type_alias: type == my_type; with Cpp1 struct my_type_alias;
and my_type: @struct type = { } with Cpp1 struct my_type_alias { }; to the same effect (https://cpp2.godbolt.org/z/nn9vvz3z9):
struct my_type_alias;
f: (_: my_type_alias) = { }
struct my_type_alias { };
main: () = { }
So perhaps only pure Cpp2 .cpp2 sources can always benefit from optimized in parameters.