cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

[SUGGESTION] Improve `in` parameter passing (restore old behavior, exclude problem cases only)

Open JohelEGP opened this issue 2 years ago • 14 comments
trafficstars

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 this function can have parameters of incomplete type. When declaring an override this or final this function in a different TU, we can't know if its in parameters 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:

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.

JohelEGP avatar Sep 08 '23 23:09 JohelEGP

Identified problems:

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.

JohelEGP avatar Sep 09 '23 00:09 JohelEGP

The conditions when applying cpp2::in is wrong are two only (AFAIK).

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

JohelEGP avatar Sep 09 '23 00:09 JohelEGP

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>) with is_complete<T> for the same reasons as the former (https://github.com/hsutter/cppfront/issues/317#issuecomment-1499247426). This gives a clear error message about is_complete<C> being false rather than about using sizeof on an incomplete type C.

JohelEGP avatar Sep 09 '23 01:09 JohelEGP

  • 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.

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.

JohelEGP avatar Sep 16 '23 22:09 JohelEGP

Afterwards, that parameters outside special member functions should be optimized. In the distant future, this parameters should be optimized (with an explicit object parameter).

JohelEGP avatar Sep 22 '23 15:09 JohelEGP

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.

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 _.

JohelEGP avatar Nov 14 '23 03:11 JohelEGP

AFAIK, optimizing shouldn't be a problem in modules. The strong ownership model means you can't move the definitions anywhere outside that module.

JohelEGP avatar Nov 14 '23 04:11 JohelEGP

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.

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).

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 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 this function][] can have parameters of incomplete type. When declaring an override this or final this function in a different TU, we can't know if its in parameters 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:

JohelEGP avatar Nov 16 '23 03:11 JohelEGP

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.

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.

JohelEGP avatar Nov 16 '23 03:11 JohelEGP