[libcxx-commits] [PATCH] D154238: [libc++][PSTL] Move exception handling to the CPU backends

Louis Dionne via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 3 10:08:59 PDT 2023


ldionne requested changes to this revision.
ldionne added inline comments.
This revision now requires changes to proceed.


================
Comment at: libcxx/include/__algorithm/pstl_backends/cpu_backends/any_of.h:63
   }
   return false;
 }
----------------
We should document how backends are expected to handle exceptions in `libcxx/include/__algorithm/pstl_backend.h` and in `libcxx/include/__algorithm/pstl_backends/cpu_backend.h`.


================
Comment at: libcxx/include/__algorithm/pstl_backends/cpu_backends/find_if.h:92
 
 template <class _ExecutionPolicy, class _ForwardIterator, class _Predicate>
 _LIBCPP_HIDE_FROM_ABI _ForwardIterator
----------------
Notes from our 1:1 session:

```
// Option 1:
// Pros:
// - CPU sub-backends don't have to handle user exceptions in any special way. They only have to care
//   about any exception that might be thrown in their own setup code (aka std::bad_alloc).
// - Handling of excetpions at the `__pstl_foo` level is uniform across all algorithms.
// - We can write all code inside the PSTL with the mindset that exceptions are disabled,
//   i.e. we never throw and we use appropriate types to report errors instead. This might be
//   a nice systematic approach?
// Cons:
// - Requires introducing a custom std::expected-like type for internal use.
// - Hardcodes the fact that we can only throw `std::bad_alloc` from backends.
// - We can't elide the move construction of the return value out of `__pstl_find_if`.
template <class _ExecutionPolicy, class _ForwardIterator, class _Predicate>
_LIBCPP_HIDE_FROM_ABI _ForwardIterator
__pstl_find_if(__cpu_backend_tag, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) {
  auto __maybe_result = std::__terminate_on_exception([&] -> __funky_expected<_ForwardIterator, std::bad_alloc> {
    if constexpr (__is_parallel_execution_policy_v<_ExecutionPolicy> &&
                  __has_random_access_iterator_category<_ForwardIterator>::value) {
      return std::__parallel_find(
          __first,
          __last,
          [&__pred](_ForwardIterator __brick_first, _ForwardIterator __brick_last) {
            return std::__pstl_find_if<__remove_parallel_policy_t<_ExecutionPolicy>>(
                __cpu_backend_tag{}, __brick_first, __brick_last, __pred);
          },
          less<>{},
          true);
    } else if constexpr (__is_unsequenced_execution_policy_v<_ExecutionPolicy> &&
                        __has_random_access_iterator_category<_ForwardIterator>::value) {
      using __diff_t = __iter_diff_t<_ForwardIterator>;
      return std::__simd_first(__first, __diff_t(0), __last - __first, [&__pred](_ForwardIterator __iter, __diff_t __i) {
        return __pred(__iter[__i]);
      });
    } else {
      return std::find_if(__first, __last, __pred);
    }
  });
  return *std::move(__maybe_result).or_else([](auto&& __exception) -> __funky_expected<_ForwardIterator, void> { std::__throw_bad_alloc(); });
}

// Option 2:
// Pros:
// - Each layer fully handles all of its exceptions independently. That can be simpler or more complicated depending
//   on how you look at it.
// Cons:
// - When you're writing a CPU backend, you need to explicitly handle user-thrown exceptions and carve out the
//   setup-thrown exceptions from the `terminate_on_exception` code paths.
template <class _ExecutionPolicy, class _ForwardIterator, class _Predicate>
_LIBCPP_HIDE_FROM_ABI _ForwardIterator
__pstl_find_if(__cpu_backend_tag, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) {
  if constexpr (__is_parallel_execution_policy_v<_ExecutionPolicy> &&
                __has_random_access_iterator_category<_ForwardIterator>::value) {
    return std::__parallel_find( // we would document that CPU sub-backends need to handle exceptions appropriately
        __first,
        __last,
        [&__pred](_ForwardIterator __brick_first, _ForwardIterator __brick_last) {
          return std::__pstl_find_if<__remove_parallel_policy_t<_ExecutionPolicy>>(
              __cpu_backend_tag{}, __brick_first, __brick_last, __pred);
        },
        less<>{},
        true);
  } else if constexpr (__is_unsequenced_execution_policy_v<_ExecutionPolicy> &&
                      __has_random_access_iterator_category<_ForwardIterator>::value) {
    using __diff_t = __iter_diff_t<_ForwardIterator>;
    return std::__terminate_on_exception([&] {
      return std::__simd_first(__first, __diff_t(0), __last - __first, [&__pred](_ForwardIterator __iter, __diff_t __i) {
        return __pred(__iter[__i]);
      });
    });
  } else {
    return std::__terminate_on_exception([&] {
      return std::find_if(__first, __last, __pred);
    });
  }
}
```

At the end, we both agreed that option 2 is the way to go for now, largely because implementing an `expected`-like type in C++17 drives the cost of option 1 up a lot.



================
Comment at: libcxx/include/__algorithm/pstl_backends/cpu_backends/find_if.h:109-111
     return std::__simd_first(__first, __diff_t(0), __last - __first, [&__pred](_ForwardIterator __iter, __diff_t __i) {
       return __pred(__iter[__i]);
     });
----------------
If an exception is thrown in here, we would also need to terminate. This needs to be fixed + a test.


================
Comment at: libcxx/include/__algorithm/pstl_backends/cpu_backends/find_if.h:113
   } else {
     return std::find_if(__first, __last, __pred);
   }
----------------
If an exception is thrown in here, we should also terminate even though this is actually running serial. This needs fix + tests.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D154238/new/

https://reviews.llvm.org/D154238



More information about the libcxx-commits mailing list