Skip to content
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

Add checks for unsafe implicit conversions in RangePolicy #6754

Merged

Conversation

ldh4
Copy link
Contributor

@ldh4 ldh4 commented Jan 27, 2024

Resolves #6709

Added checks in RangePolicy to detect non value-preserving implicit conversions between input bounds and the policy's IndexType.

Cases considered:

  • signed arg type to unsigned index types
  • unsigned arg type to signed index types
  • Narrowing conversions

core/src/Kokkos_ExecPolicy.hpp Outdated Show resolved Hide resolved
core/src/Kokkos_ExecPolicy.hpp Outdated Show resolved Hide resolved
core/src/Kokkos_ExecPolicy.hpp Outdated Show resolved Hide resolved
@crtrott
Copy link
Member

crtrott commented Feb 1, 2024

I believe the logic isn't quite right for what it should check. I would suggest we just copy the stuff from mdspan (and backs-porting):

https://github.com/llvm/llvm-project/blob/0e8eb445db0cc2552d9d077b527a43c779785cb9/libcxx/include/__mdspan/extents.h#L228-L252

Also the function for the representability check should only be called in debug mode maybe?

@dalg24
Copy link
Member

dalg24 commented Feb 1, 2024

DongHun and I met earlier today. I shared an alternative implementation https://godbolt.org/z/WEKM444n4 and we rediscovered that MDRange had

// Checked narrowing conversion that calls abort if the cast changes the value
template <class To, class From>
constexpr To checked_narrow_cast(From arg) {
constexpr const bool is_different_signedness =
(std::is_signed<To>::value != std::is_signed<From>::value);
auto const ret = static_cast<To>(arg);
if (static_cast<From>(ret) != arg ||
(is_different_signedness &&
is_less_than_value_initialized_variable(arg) !=
is_less_than_value_initialized_variable(ret))) {
Kokkos::abort("unsafe narrowing conversion");
}
return ret;
}

We also discussed that C++20 has std::in_range that does pretty much what we want.

We decided to focus on making sure that we got the tests right and agreed we can refactor as needed later.

…en deprecated code is used

Modified the unit test to test warning outputs
Changed implicit conversion check to be tested in debug mode only
@ldh4 ldh4 force-pushed the rangepolicy_check_bounds_implicit_conversion branch from 67fcdb0 to c8befbd Compare February 1, 2024 05:18
@ldh4
Copy link
Contributor Author

ldh4 commented Feb 1, 2024

I believe the logic isn't quite right for what it should check. I would suggest we just copy the stuff from mdspan (and backs-porting):
https://github.com/llvm/llvm-project/blob/0e8eb445db0cc2552d9d077b527a43c779785cb9/libcxx/include/__mdspan/extents.h#L228-L252

I think there may be some disparities between what the mdspan extent is requiring and what is currently accepted for indicies in RangePolicy.

For example of this case:

Kokkos::RangePolicy<Kokkos::IndexType<int>> p(-3, -1);

(in godbolt)
Although not a very intuitive example, currently it's valid to pass in negative values as bounds in RangePolicy if IndexType is set as a signed integral type. Also the kokkos documentation on RangePolicy doesn't seem to be requiring IndexType to be a positive value. (IndexType defaults to int64_t)

But looking at the comment in the mdspan extent's representability check, the precondition is that an extent must be a positive integer:

// Function to check whether a value is representable as another type
// value must be a positive integer otherwise returns false

So, there is an upfront check to disallow any negative extents, and because of this requirement, the rest of representability check would allow signed to unsigned conversion (which would be an unsafe implicit conversion for RangePolicy described in #6709 that this PR aims to catch).

I think we would need to establish that RangePolicy should not be taking in negative values for bounds in order to use that mdspan extent check.

@dalg24
Copy link
Member

dalg24 commented Feb 1, 2024

DongHun is correct. std::mdspan says

representable as a nonnegative value of type index_type

and we allow negative values in RangePolicy

@dalg24
Copy link
Member

dalg24 commented Feb 2, 2024 via email

Comment on lines 118 to 119
#if !defined(KOKKOS_ENABLE_DEPRECATED_CODE_4) || \
defined(KOKKOS_ENABLE_DEPRECATION_WARNINGS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether or not we disable deprecation warnings shouldn't affect the interface exposed. Whether we warn or error out is determined in the implementation of check_conversion_safety anyway and there the guards make sense.

Suggested change
#if !defined(KOKKOS_ENABLE_DEPRECATED_CODE_4) || \
defined(KOKKOS_ENABLE_DEPRECATION_WARNINGS)
#ifndef KOKKOS_ENABLE_DEPRECATED_CODE_4

What happens if is_convertible is false anyway? Wouldn't that already result in a compilation error? In that case, we can just enable the new interface unconditionally anyway, right?

edit: I meant always using the new interface instead of the old one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether or not we disable deprecation warnings shouldn't affect the interface exposed.

I agree with this statement in general. But the change you suggest would inhibit diagnosing narrowing conversions. (I am not saying we cannot come up with something else, just that the suggestion does not work.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated my comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether we warn or error out is determined in the implementation of check_conversion_safety anyway and there the guards make sense.
What happens if is_convertible is false anyway? Wouldn't that already result in a compilation error? In that case, we can just enable the new interface unconditionally anyway, right?

Yes, since the old interface simply accepted arguments that are implicitly convertible to the member_type, semantically it should be no different from enforcing the explicit is_convertible check to the interface. I placed in those ifdef guards as a form of compatibility safety and mainly to follow our regular process for changing user facing interface.
But I am for using the new interface right away and leave the ifdef gurads only in check_conversion_safety as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether or not we disable deprecation warnings shouldn't affect the interface exposed.

I agree with this statement in general. But the change you suggest would inhibit diagnosing narrowing conversions. (I am not saying we cannot come up with something else, just that the suggestion does not work.)

@dalg24, any opinion on replacing the old interface with the new interface right away and leave the two ifdef guards only in check_conversion_saftey?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dalg24, any opinion on replacing the old interface with the new interface right away and leave the two ifdef guards only in check_conversion_saftey?

OK with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to use the new interfaces and removed ifdef guards. 3065552.

@dalg24
Copy link
Member

dalg24 commented Feb 7, 2024

Ignoring failure of one of the HIP build (machine issue) and

5: [ RUN      ] cuda.workgraph_fib
5: /var/jenkins/workspace/Kokkos_PR-6754/core/unit_test/TestWorkGraph.hpp:127: Failure
5: Expected equality of these values:
5:   h_values(0)
5:     Which is: 5168
5:   full_fibonacci(m_input)
5:     Which is: 6765
5: [  FAILED  ] cuda.workgraph_fib (1416 ms)

@dalg24 dalg24 merged commit 442e4d4 into kokkos:develop Feb 7, 2024
30 of 31 checks passed
@dalg24 dalg24 mentioned this pull request Feb 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants