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

[BUG] make_unique not annotated with except + #5560

Closed
wence- opened this issue Jul 25, 2023 · 4 comments · Fixed by #5569
Closed

[BUG] make_unique not annotated with except + #5560

wence- opened this issue Jul 25, 2023 · 4 comments · Fixed by #5569

Comments

@wence-
Copy link

wence- commented Jul 25, 2023

Describe the bug

When make_unique was provided in memory.pxd in #487, it didn't come with an except + annotation. At the time, this was due to the Cython compiler not emitting the correct std::move(...) in the subsequent assignment in the transpiled source code.

AFAICT this can be worked around in a reasonable manner by explicitly putting a move call in the rvalue of the assignment, and although that is less ergonomic, perhaps it is better because exceptions will actually be caught.
In the intervening years, it looks like this restriction has gone, and so except + should be reintroduced. The issue here is that a (potentially) throwing constructor wrapped by make_unique does not

Code to reproduce the behaviour:

from libcpp.utility cimport move
from libcpp.memory cimport unique_ptr
from libcpp.vector cimport vector

def some_function(int n):
    cdef unique_ptr[vector[double]] vec

    with nogil:
        vec = move(make_unique[vector[double]](n))

    return vec.get().size()

Expected behaviour

I expect this to be transpiled to:

        /* "cython_except.pyx":9
 * 
 *     with nogil:
 *         vec = move(make_unique[vector[double]](n))             # <<<<<<<<<<<<<<
 * 
 *     return vec.get().size()
 */
        try {
          __pyx_t_1 = std::make_unique<std::vector<double> >(__pyx_v_n);
        } catch(...) {
          #ifdef WITH_THREAD
          PyGILState_STATE __pyx_gilstate_save = __Pyx_PyGILState_Ensure();
          #endif
          __Pyx_CppExn2PyErr();
          #ifdef WITH_THREAD
          __Pyx_PyGILState_Release(__pyx_gilstate_save);
          #endif
          __PYX_ERR(0, 9, __pyx_L4_error)
        }
        __pyx_v_vec = cython_std::move<std::unique_ptr<std::vector<double> > >(__PYX_STD_MOVE_IF_SUPPORTED(__pyx_t_1));
      }

Whereas in fact it is transpiled to:

      /*try:*/ {

        /* "cython_except.pyx":9
 * 
 *     with nogil:
 *         vec = move(make_unique[vector[double]](n))             # <<<<<<<<<<<<<<
 * 
 *     return vec.get().size()
 */
        __pyx_v_vec = cython_std::move<std::unique_ptr<std::vector<double> > >(std::make_unique<std::vector<double> >(__pyx_v_n));
      }

OS

Linux

Python version

3.10.6

Cython version

3.0.0

Additional context

No response

wence- added a commit to wence-/cudf that referenced this issue Jul 25, 2023
make_unique in Cython's libcpp headers is not annotated with `except
+`. As a consequence, if the constructor throws, we do not catch it in
Python. To work around this (see cython/cython#5560 for details),
provide our own implementation.

Due to the way assignments occur to temporaries, we need to now
explicitly wrap all calls to `make_unique` in `move`, but that is
arguably preferable to not being able to catch exceptions.

- Closes rapidsai#13743
wence- added a commit to wence-/cudf that referenced this issue Jul 25, 2023
make_unique in Cython's libcpp headers is not annotated with `except
+`. As a consequence, if the constructor throws, we do not catch it in
Python. To work around this (see cython/cython#5560 for details),
provide our own implementation.

Due to the way assignments occur to temporaries, we need to now
explicitly wrap all calls to `make_unique` in `move`, but that is
arguably preferable to not being able to catch exceptions.

- Closes rapidsai#13743
@da-woods
Copy link
Contributor

Maybe I'm missing some detail of C++, but I believe the explicit call to std::move should be unnecessary and the generated code can be simpler:

// exception stuff as above
__pyx_v_vec = __PYX_STD_MOVE_IF_SUPPORTED(__pyx_t_1);

If that's the case there's basically no reason not to add except+ to our definition since all existing code would continue to work. The only question is whether the code detecting move support is robust enough.

@vyasr
Copy link
Contributor

vyasr commented Jul 26, 2023

I think that is correct. From some quick local testing that does appear to work. I'm going to try rebuilding a few of my libraries with this change to verify that there are no surprises.

wence- added a commit to wence-/cudf that referenced this issue Jul 26, 2023
make_unique in Cython's libcpp headers is not annotated with `except
+`. As a consequence, if the constructor throws, we do not catch it in
Python. To work around this (see cython/cython#5560 for details),
provide our own implementation.

Due to the way assignments occur to temporaries, we need to now
explicitly wrap all calls to `make_unique` in `move`, but that is
arguably preferable to not being able to catch exceptions.

- Closes rapidsai#13743
@wence-
Copy link
Author

wence- commented Jul 26, 2023

Maybe I'm missing some detail of C++, but I believe the explicit call to std::move should be unnecessary and the generated code can be simpler:

// exception stuff as above
__pyx_v_vec = __PYX_STD_MOVE_IF_SUPPORTED(__pyx_t_1);

If that's the case there's basically no reason not to add except+ to our definition since all existing code would continue to work. The only question is whether the code detecting move support is robust enough.

I was (also) testing with Cython < 3 which transpiles this example:

from libcpp.utility cimport move
from libcpp.memory cimport unique_ptr
from libcpp.vector cimport vector

cdef extern from "<memory>" namespace "std" nogil:
    unique_ptr[T] make_unique[T](...) except +

def some_function(int n):
    cdef unique_ptr[vector[double]] vec

    with nogil:
        vec = make_unique[vector[double]](n)

    return vec.get().size()

to

      /*try:*/ {

        /* "cython_except.pyx":12
 * 
 *     with nogil:
 *         vec = make_unique[vector[double]](n)             # <<<<<<<<<<<<<<
 * 
 *     return vec.get().size()
 */
        try {
          __pyx_t_1 = std::make_unique<std::vector<double> >(__pyx_v_n);
        } catch(...) {
          #ifdef WITH_THREAD
          PyGILState_STATE __pyx_gilstate_save = __Pyx_PyGILState_Ensure();
          #endif
          __Pyx_CppExn2PyErr();
          #ifdef WITH_THREAD
          __Pyx_PyGILState_Release(__pyx_gilstate_save);
          #endif
          __PYX_ERR(0, 12, __pyx_L4_error)
        }
        __pyx_v_vec = __pyx_t_1;
      }

However, with cython 3 we get

      /*try:*/ {

        /* "cython_except.pyx":12
 * 
 *     with nogil:
 *         vec = make_unique[vector[double]](n)             # <<<<<<<<<<<<<<
 * 
 *     return vec.get().size()
 */
        try {
          __pyx_t_1 = std::make_unique<std::vector<double> >(__pyx_v_n);
        } catch(...) {
          #ifdef WITH_THREAD
          PyGILState_STATE __pyx_gilstate_save = __Pyx_PyGILState_Ensure();
          #endif
          __Pyx_CppExn2PyErr();
          #ifdef WITH_THREAD
          __Pyx_PyGILState_Release(__pyx_gilstate_save);
          #endif
          __PYX_ERR(0, 12, __pyx_L4_error)
        }
        __pyx_v_vec = __PYX_STD_MOVE_IF_SUPPORTED(__pyx_t_1);
      }

So I agree it looks like the explicit move should not be necessary. Though perhaps that also means one shouldn't backport to cython < 3 since existing code might break.

wence- added a commit to wence-/cudf that referenced this issue Jul 26, 2023
make_unique in Cython's libcpp headers is not annotated with `except
+`. As a consequence, if the constructor throws, we do not catch it in
Python. To work around this (see cython/cython#5560 for details),
provide our own implementation.

Due to the way assignments occur to temporaries, we need to now
explicitly wrap all calls to `make_unique` in `move`, but that is
arguably preferable to not being able to catch exceptions, and will
not be necessary once we move to Cython 3.

- Closes rapidsai#13743
rapids-bot bot pushed a commit to rapidsai/cudf that referenced this issue Jul 26, 2023
`make_unique` in Cython's libcpp headers is not annotated with `except +`. As a consequence, if the constructor throws, we do not catch it in Python. To work around this (see cython/cython#5560 for details), provide our own implementation.

Due to the way assignments occur to temporaries, we need to now explicitly wrap all calls to `make_unique` in `move`, but that is arguably preferable to not being able to catch exceptions, and will not be necessary once we move to Cython 3.

- Closes #13743

Authors:
  - Lawrence Mitchell (https://github.com/wence-)

Approvers:
  - Ashwin Srinath (https://github.com/shwina)

URL: #13746
da-woods added a commit to da-woods/cython that referenced this issue Jul 26, 2023
Also tweak the selection for move-if-supported. This now lets
the user enable it by a define even if the compiler support isn't
detected. This is useful because the new signature for
make_unique kind of relies on move-if-supported to work.

Fixes cython#5560
@da-woods
Copy link
Contributor

da-woods commented Jul 26, 2023

I don't think this should be backported since it needs updated features to work. I've proposed a change. I've also changed how we detect MOVE_IF_SUPPORTED to let the user force it to on. My one worry with this signature change is that it breaks compatibility for anyone who we aren't detecting move support for. And having a way to force it on mitigates that.

scoder pushed a commit that referenced this issue Jul 31, 2023
Also tweak the selection for move-if-supported by adding a feature flag `CYTHON_USE_CPP_STD_MOVE`.
This now lets the user enable it even if the compiler support isn't
detected. This is useful because the new signature for `make_unique()`
kind of relies on move-if-supported to work.

Fixes #5560
@scoder scoder added the defect label Jul 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants