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

Remove some maybe-uninitialized warnings #5516

Merged
merged 3 commits into from
Feb 8, 2025

Conversation

daizhirui
Copy link
Contributor

@daizhirui daizhirui commented Feb 6, 2025

Description

Add a GCC -Wmaybe-uninitialized warning suppression in pybind11/eigen/matrix.h

See #5516 (comment) for a situation in which this helps.

Suggested changelog entry:

A ``-Wmaybe-uninitialized`` warning suppression was added in pybind11/eigen/matrix.h

In the Eigen matrix type_caster, resize the matrix instead of assigning with a new one when the matrix size needs to be adjusted.

This can remove lots of compiling warnings about "maybe-uninitialized".
@rwgk
Copy link
Collaborator

rwgk commented Feb 6, 2025

Could you please report how exactly you got the maybe-uninitialized warnings (compiler details, options, error messages)?

I think the change is fine, although I want to ask someone familiar with Eigen and numpy to take a look, but let's get the background info complete first.

@daizhirui
Copy link
Contributor Author

daizhirui commented Feb 6, 2025

Hi, @rwgk, here is a simple example to reproduce the warning.

  • CMakeLists.txt
cmake_minimum_required(VERSION 3.30)

project(test_pybind
        LANGUAGES CXX
)

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")

find_package(Eigen3 REQUIRED)
find_package(pybind11 REQUIRED)

pybind11_add_module(binding binding.cpp)
target_link_libraries(binding
        PRIVATE Eigen3::Eigen
)

EDIT: The include order below is incorrect, please see

pybind11/docs/basics.rst

Lines 81 to 86 in d2e7e8c

.. note::
``pybind11/pybind11.h`` includes ``Python.h``, as such it must be the first file
included in any source file or header for `the same reasons as Python.h`_.
.. _`the same reasons as Python.h`: https://docs.python.org/3/extending/extending.html#a-simple-example

  • binding.cpp
#include <Eigen/Dense>
#include <pybind11/eigen.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>

// good
Eigen::MatrixXd add_matrix(const Eigen::Ref<const Eigen::MatrixXd> &m1,
                           const Eigen::Ref<const Eigen::MatrixXd> &m2) {
  return m1 + m2;
}

// good
Eigen::MatrixXd add_matrix2(const Eigen::MatrixXd &m1,
                            const Eigen::MatrixXd &m2) {
  return m1 + m2;
}

// this one causes "maybe-uninitialized" warning
Eigen::Vector4d add_vector(const Eigen::Vector4d &v1,
                           const Eigen::Vector4d &v2) {
  return v1 + v2;
}

PYBIND11_MODULE(binding, m) {
  namespace py = pybind11;
  m.def("add_matrix", &add_matrix, "Add two matrices", py::arg("m1"),
        py::arg("m2"))
      .def("add_matrix2", &add_matrix2, "Add two matrices", py::arg("m1"),
           py::arg("m2"))
      .def("add_vector", &add_vector, "Add two vectors", py::arg("v1"),
           py::arg("v2"));
}

@rwgk
Copy link
Collaborator

rwgk commented Feb 6, 2025

That's great but I was simply looking for the compiler info (e.g. gcc --version), one example compilation command (e.g. g++ ...), and the error message.

It's usually/often very troublesome to reproduce something based on guessing such details.

@daizhirui
Copy link
Contributor Author

daizhirui commented Feb 6, 2025

I tested both GCC 13 and GCC 14. They both gave the following warnings:

/usr/bin/g++-13 -fPIC -Wall -Wextra -O3 -DNDEBUG -flto -shared  -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
In member function ‘operator=’,
    inlined from ‘operator=’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘load’ at /usr/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘MEM[(struct PlainObjectBase &)&D.13181].m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^
/usr/include/pybind11/eigen/matrix.h: In member function ‘load’:
/usr/include/pybind11/eigen/matrix.h:320:17: note: ‘<anonymous>’ declared here
  320 |         value = Type(fits.rows, fits.cols);
      |                 ^

GCC13: 13.3.1
GCC14: 14.2.1

@rwgk
Copy link
Collaborator

rwgk commented Feb 6, 2025

Sharing this here:


Is this a common problem when using Eigen::matrix (unrelated to pybind11)?

/usr/bin/g++-13 -fPIC -Wall -Wextra -O3 -DNDEBUG -flto -shared -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
In member function ‘operator=’,
inlined from ‘operator=’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
inlined from ‘load’ at /usr/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘MEM[(struct PlainObjectBase &)&D.13181].m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
504 | m_storage = std::move(other.m_storage);
| ^
/usr/include/pybind11/eigen/matrix.h: In member function ‘load’:
/usr/include/pybind11/eigen/matrix.h:320:17: note: ‘’ declared here
320 | value = Type(fits.rows, fits.cols);
| ^
ChatGPT said:
This issue is not specific to Eigen::Matrix itself but is likely related to how it is being used. The warning message may be used uninitialized suggests that there might be a situation where an Eigen matrix is being assigned or moved from an object that is potentially uninitialized.

Key Observations:
Compiler Warning (-Wmaybe-uninitialized)

This is a warning, not an error, meaning the compiler detected a possible but not guaranteed uninitialized usage.
The warning is emitted for PlainObjectBase.h:504, where Eigen's internal m_storage member is being moved.
Pybind11 Eigen Integration (pybind11/eigen/matrix.h)

The warning appears when loading an Eigen matrix through Pybind11, specifically at:
cpp
Copy
Edit
value = Type(fits.rows, fits.cols);
where Type is the Eigen matrix type.
Potential Causes:

value might be default-constructed before assignment, which could trigger the warning.
There might be an edge case where fits.rows and fits.cols are not properly initialized.
Some compilers may falsely detect an uninitialized value when optimizing.
How to Fix It:

  1. Explicitly Initialize the Matrix Before Assignment
    Try explicitly initializing the matrix to avoid any potential uninitialized state:

cpp
Copy
Edit
value = Type(fits.rows, fits.cols).setZero();
or even:

cpp
Copy
Edit
value = Type::Zero(fits.rows, fits.cols);
This ensures that m_storage is initialized with zeros instead of potentially being left in an undefined state.

  1. Disable the Warning (If It’s a False Positive)
    If you determine that this is a false positive (i.e., Eigen::Matrix is always initialized correctly but the compiler is overly cautious), you can suppress the warning:

cpp
Copy
Edit
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
// Your code using Eigen
#pragma GCC diagnostic pop
Alternatively, use -Wno-maybe-uninitialized in the compilation flags.

  1. Verify Compiler and Eigen Version
    Since you are using GCC 13, it might have more aggressive warnings.
    Ensure you are using an up-to-date version of Eigen.
    If using LTO (Link-Time Optimization) (-flto flag), try disabling it to check if it affects the warning.
    Conclusion:
    This is not an inherent Eigen issue but rather a compiler-level warning due to potential uninitialized usage in pybind11/eigen/matrix.h. Try explicitly initializing matrices or suppressing the warning if you confirm it's a false positive.

@rwgk
Copy link
Collaborator

rwgk commented Feb 6, 2025

Also:


What do you think about this solution?

diff --git a/include/pybind11/eigen/matrix.h b/include/pybind11/eigen/matrix.h
index 5cf1f0a2a0..a62c542d51 100644
--- a/include/pybind11/eigen/matrix.h
+++ b/include/pybind11/eigen/matrix.h
@@ -317,7 +317,7 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain::value>> {
}

     // Allocate the new type, then build a numpy reference into it
  •    value = Type(fits.rows, fits.cols);
    
  •    value.resize(fits.rows, fits.cols);
       auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
       if (dims == 1) {
           ref = ref.squeeze();
    

ChatGPT said:
Your proposed fix of replacing:

cpp
Copy
Edit
value = Type(fits.rows, fits.cols);
with:

cpp
Copy
Edit
value.resize(fits.rows, fits.cols);
in pybind11/eigen/matrix.h makes a lot of sense and is a good improvement. Here’s why:

Why This Fix Works
Avoids Uninitialized Storage Warning

Type(fits.rows, fits.cols); constructs a new matrix but might leave memory uninitialized, depending on the constructor behavior.
resize(fits.rows, fits.cols); works on an already-existing matrix and ensures memory allocation properly.
Safer Memory Management

Eigen::Matrix::resize() ensures the matrix is properly resized, potentially avoiding unnecessary allocations and keeping existing memory where possible.
More Compatible with Eigen’s Recommended Practices

Eigen::Matrix::resize() is the preferred way to modify an existing matrix's shape instead of reassigning.
Any Downsides?
If value was uninitialized before this line, then calling resize() on it may still trigger issues.
If value was meant to be constructed in-place, resize() modifies an existing object instead of constructing a new one, which may or may not be what the original code intended.
Alternative for Absolute Safety
If you want to completely avoid any uninitialized values, you could further modify it to:

cpp
Copy
Edit
value.setZero(fits.rows, fits.cols);
or

cpp
Copy
Edit
value = Type::Zero(fits.rows, fits.cols);
But this comes at the cost of zero-initialization, which may not always be necessary.

Verdict: Good Fix!
Your solution is definitely an improvement over the original code, and it should prevent the -Wmaybe-uninitialized warning. It is safe unless value is truly uninitialized before this line. Would you like to test it and see if the warning disappears?

@rwgk
Copy link
Collaborator

rwgk commented Feb 6, 2025

This ChatGPT "Any Downsides" was on my mind:

modifies an existing object instead of constructing a new one

This would only be a problem if load() is called multiple times, which it isn't meant to, but ... ultimately this PR is a behavior change.

I'm OK accepting it.

@seberg do you have an opinion?

@seberg
Copy link
Contributor

seberg commented Feb 7, 2025

Thanks for the ping. Unfortunately, I am not familiar with the code (and especially how a user would use it!). Neither it is really clear to me why the compiler doesn't like things.

Now, is this safe? As you said, mutating value is the potential problem. But I don't undestand how it might get used (in the above example it clearly isn't a problem).
If it might be used in a state-full pattern like:

class MyAlgo:
    def __init__(self, numpy_array):
        self.value = numpy_array  # copy to C++ eigen matrix via `load`

    def update(self, numpy_array):
        self.value = numpy_array  # update via `load`

    def current_value(self) -> np.ndarray:
        return self.value  # convert back to Python

then this would be clearly wrong.

@daizhirui
Copy link
Contributor Author

daizhirui commented Feb 8, 2025

I don't think it will be wrong. The changed line manages memory allocation in a way that the compiler prefers. At line 328, data from the numpy array is copied into value after the memory allocation.

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

Could you please try a super quick experiment?

Move the Eigen include after the pybind11 includes like this:

#include <pybind11/eigen.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <Eigen/Dense>

Do the warnings go away?

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

I spent >20 minutes trying to reproduce the warning but I wasn't successful. (I only got a successful build without warnings, using gcc 13.3.0.)

Could you please post complete information here?

  • Complete command line and complete output from running cmake (i.e. the config step).

  • Complete command line and complete output from running VERBOSE=1 make.

(The command you posted before is for linking, not compiling.)

@daizhirui
Copy link
Contributor Author

@rwgk Thank you for trying to fix this issue.

Here is the output I got:

With GNU 13.3.1

$ CC=/usr/bin/gcc-13 CXX=/usr/bin/g++-13 cmake .. -DCMAKE_BUILD_TYPE=Release

-- The CXX compiler identification is GNU 13.3.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/g++-13 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Python: /usr/bin/python3.13 (found suitable version "3.13.1", minimum required is "3.7") found components: Interpreter Development.Module Development.Embed
-- Performing Test HAS_FLTO
-- Performing Test HAS_FLTO - Success
-- Found pybind11: /usr/include (found version "2.13.6")
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /home/daizhirui/test_pybind/build
$ VERBOSE=1 make

/usr/bin/cmake -S/home/daizhirui/test_pybind -B/home/daizhirui/test_pybind/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles /home/daizhirui/test_pybind/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/depend
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
cd /home/daizhirui/test_pybind/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/daizhirui/test_pybind /home/daizhirui/test_pybind /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/DependInfo.cmake "--color="
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/build
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
[ 50%] Building CXX object CMakeFiles/binding.dir/binding.cpp.o
/usr/bin/g++-13 -Dbinding_EXPORTS -isystem /usr/include/python3.13 -isystem /usr/include/eigen3 -Wall -Wextra -O3 -DNDEBUG -std=c++17 -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects -MD -MT CMakeFiles/binding.dir/binding.cpp.o -MF CMakeFiles/binding.dir/binding.cpp.o.d -o CMakeFiles/binding.dir/binding.cpp.o -c /home/daizhirui/test_pybind/binding.cpp
[100%] Linking CXX shared module binding.cpython-313-x86_64-linux-gnu.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/binding.dir/link.txt --verbose=1
/usr/bin/g++-13 -fPIC -Wall -Wextra -O3 -DNDEBUG -flto -Wl,--dependency-file=CMakeFiles/binding.dir/link.d -shared  -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
lto-wrapper: warning: using serial compilation of 2 LTRANS jobs
lto-wrapper: note: see the ‘-flto’ option documentation for more information
In member function ‘operator=’,
    inlined from ‘operator=’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘load’ at /usr/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘MEM[(struct PlainObjectBase &)&D.13181].m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^
/usr/include/pybind11/eigen/matrix.h: In member function ‘load’:
/usr/include/pybind11/eigen/matrix.h:320:17: note: ‘<anonymous>’ declared here
  320 |         value = Type(fits.rows, fits.cols);
      |                 ^
/usr/bin/strip /home/daizhirui/test_pybind/build/binding.cpython-313-x86_64-linux-gnu.so
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
[100%] Built target binding
make[1]: Leaving directory '/home/daizhirui/test_pybind/build'
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles 0

With GNU 14.2.1:

$ cmake .. -DCMAKE_BUILD_TYPE=Release

-- The CXX compiler identification is GNU 14.2.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Python: /usr/bin/python3.13 (found suitable version "3.13.1", minimum required is "3.7") found components: Interpreter Development.Module Development.Embed
-- Performing Test HAS_FLTO
-- Performing Test HAS_FLTO - Success
-- Found pybind11: /usr/include (found version "2.13.6")
-- Configuring done (0.9s)
-- Generating done (0.0s)
-- Build files have been written to: /home/daizhirui/test_pybind/build
$ VERBOSE=1 make

/usr/bin/cmake -S/home/daizhirui/test_pybind -B/home/daizhirui/test_pybind/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles /home/daizhirui/test_pybind/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/depend
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
cd /home/daizhirui/test_pybind/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/daizhirui/test_pybind /home/daizhirui/test_pybind /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/DependInfo.cmake "--color="
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/build
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
[ 50%] Building CXX object CMakeFiles/binding.dir/binding.cpp.o
/usr/bin/c++ -Dbinding_EXPORTS -isystem /usr/include/python3.13 -isystem /usr/include/eigen3 -Wall -Wextra -O3 -DNDEBUG -std=c++17 -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects -MD -MT CMakeFiles/binding.dir/binding.cpp.o -MF CMakeFiles/binding.dir/binding.cpp.o.d -o CMakeFiles/binding.dir/binding.cpp.o -c /home/daizhirui/test_pybind/binding.cpp
[100%] Linking CXX shared module binding.cpython-313-x86_64-linux-gnu.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/binding.dir/link.txt --verbose=1
/usr/bin/c++ -fPIC -Wall -Wextra -O3 -DNDEBUG -flto -Wl,--dependency-file=CMakeFiles/binding.dir/link.d -shared  -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
lto-wrapper: warning: using serial compilation of 2 LTRANS jobs
lto-wrapper: note: see the ‘-flto’ option documentation for more information
In member function ‘operator=’,
    inlined from ‘operator=’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘load’ at /usr/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘MEM[(struct PlainObjectBase &)&D.13156].m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^
/usr/include/pybind11/eigen/matrix.h: In member function ‘load’:
/usr/include/pybind11/eigen/matrix.h:320:17: note: ‘<anonymous>’ declared here
  320 |         value = Type(fits.rows, fits.cols);
      |                 ^
/usr/bin/strip /home/daizhirui/test_pybind/build/binding.cpython-313-x86_64-linux-gnu.so
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
[100%] Built target binding
make[1]: Leaving directory '/home/daizhirui/test_pybind/build'
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles 0

I also tried the include order you suggested. I got the same output shown as above.

@daizhirui
Copy link
Contributor Author

I notice that link time optimization is enabled. I turned it off and tried again with 13.3.1
Here is the output:

/usr/bin/cmake -S/home/daizhirui/test_pybind -B/home/daizhirui/test_pybind/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles /home/daizhirui/test_pybind/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/depend
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
cd /home/daizhirui/test_pybind/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/daizhirui/test_pybind /home/daizhirui/test_pybind /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/DependInfo.cmake "--color="
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/build
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
[ 50%] Building CXX object CMakeFiles/binding.dir/binding.cpp.o
/usr/bin/g++-13 -Dbinding_EXPORTS -isystem /usr/include/python3.13 -isystem /usr/include/eigen3 -Wall -Wextra -O3 -DNDEBUG -std=c++17 -fPIC -fvisibility=hidden -MD -MT CMakeFiles/binding.dir/binding.cpp.o -MF CMakeFiles/binding.dir/binding.cpp.o.d -o CMakeFiles/binding.dir/binding.cpp.o -c /home/daizhirui/test_pybind/binding.cpp
In file included from /usr/include/eigen3/Eigen/Core:294,
                 from /usr/include/pybind11/eigen/matrix.h:28,
                 from /usr/include/pybind11/eigen.h:12,
                 from /home/daizhirui/test_pybind/binding.cpp:1:
In member function ‘Eigen::PlainObjectBase<Derived>& Eigen::PlainObjectBase<Derived>::operator=(Eigen::PlainObjectBase<Derived>&&) [with Derived = Eigen::Matrix<double, 4, 1>]’,
    inlined from ‘Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>::operator=(Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>&&) [with _Scalar = double; int _Rows = 4; int _Cols = 1; int _Options = 0; int _MaxRows = 4; int _MaxCols = 1]’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘bool pybind11::detail::type_caster<Type, typename std::enable_if<std::integral_constant<bool, (pybind11::detail::negation<std::integral_constant<bool, (decltype (pybind11::detail::is_template_base_of_impl<Eigen::DenseBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value && std::is_base_of<Eigen::MapBase<Derived, 0>, T>::value)> >::value && decltype (pybind11::detail::is_template_base_of_impl<Eigen::PlainObjectBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value)>::value, void>::type>::load(pybind11::handle, bool) [with Type = Eigen::Matrix<double, 4, 1>]’ at /usr/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘<unnamed>.Eigen::Matrix<double, 4, 1, 0, 4, 1>::<unnamed>.Eigen::PlainObjectBase<Eigen::Matrix<double, 4, 1> >::m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^~~~~~~~~
/usr/include/pybind11/eigen/matrix.h: In member function ‘bool pybind11::detail::type_caster<Type, typename std::enable_if<std::integral_constant<bool, (pybind11::detail::negation<std::integral_constant<bool, (decltype (pybind11::detail::is_template_base_of_impl<Eigen::DenseBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value && std::is_base_of<Eigen::MapBase<Derived, 0>, T>::value)> >::value && decltype (pybind11::detail::is_template_base_of_impl<Eigen::PlainObjectBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value)>::value, void>::type>::load(pybind11::handle, bool) [with Type = Eigen::Matrix<double, 4, 1>]’:
/usr/include/pybind11/eigen/matrix.h:320:17: note: ‘<anonymous>’ declared here
  320 |         value = Type(fits.rows, fits.cols);
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~
[100%] Linking CXX shared module binding.cpython-313-x86_64-linux-gnu.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/binding.dir/link.txt --verbose=1
/usr/bin/g++-13 -fPIC -Wall -Wextra -O3 -DNDEBUG -Wl,--dependency-file=CMakeFiles/binding.dir/link.d -shared  -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
/usr/bin/strip /home/daizhirui/test_pybind/build/binding.cpython-313-x86_64-linux-gnu.so
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
[100%] Built target binding
make[1]: Leaving directory '/home/daizhirui/test_pybind/build'
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles 0

@seberg
Copy link
Contributor

seberg commented Feb 8, 2025

I don't think it will be wrong. The changed line manages memory allocation

The important question (and maybe the answer is "obviously nothing"), is if anything might have happened with the existing value before this function is called.
Clearly from the perspective of within load, this is totally fine.

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

Here is a minimal reproducer (based on these outputs):

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/Dense>

void pass_vector(const Eigen::Vector4d &) {}

PYBIND11_MODULE(binding, m) {
    m.def("pass_vector", &pass_vector);
}
g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
g++ -Ipybind11/include -isystem /usr/include/python3.12 -isystem /usr/include/eigen3 -Wall -Wextra -O3 -DNDEBUG -std=c++17 -fPIC -fvisibility=hidden -c pr5516_reproducer.cpp

With master @ d2e7e8c:

In file included from /usr/include/eigen3/Eigen/Core:294,
                 from pybind11/include/pybind11/eigen/matrix.h:28,
                 from pybind11/include/pybind11/eigen.h:12,
                 from pr5516_reproducer.cpp:2:
In member function ‘Eigen::PlainObjectBase<Derived>& Eigen::PlainObjectBase<Derived>::operator=(Eigen::PlainObjectBase<Derived>&&) [with Derived = Eigen::Matrix<double, 4, 1>]’,
    inlined from ‘Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>::operator=(Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>&&) [with _Scalar = double; int _Rows = 4; int _Cols = 1; int _Options = 0; int _MaxRows = 4; int _MaxCols = 1]’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘bool pybind11::detail::type_caster<Type, typename std::enable_if<std::integral_constant<bool, (pybind11::detail::negation<std::integral_constant<bool, (decltype (pybind11::detail::is_template_base_of_impl<Eigen::DenseBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value && std::is_base_of<Eigen::MapBase<Derived, 0>, T>::value)> >::value && decltype (pybind11::detail::is_template_base_of_impl<Eigen::PlainObjectBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value)>::value, void>::type>::load(pybind11::handle, bool) [with Type = Eigen::Matrix<double, 4, 1>]’ at pybind11/include/pybind11/eigen/matrix.h:320:15:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘<unnamed>.Eigen::Matrix<double, 4, 1, 0, 4, 1>::<unnamed>.Eigen::PlainObjectBase<Eigen::Matrix<double, 4, 1> >::m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^~~~~~~~~
pybind11/include/pybind11/eigen/matrix.h: In member function ‘bool pybind11::detail::type_caster<Type, typename std::enable_if<std::integral_constant<bool, (pybind11::detail::negation<std::integral_constant<bool, (decltype (pybind11::detail::is_template_base_of_impl<Eigen::DenseBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value && std::is_base_of<Eigen::MapBase<Derived, 0>, T>::value)> >::value && decltype (pybind11::detail::is_template_base_of_impl<Eigen::PlainObjectBase>::check((typename pybind11::detail::intrinsic_type<T>::type*)(nullptr)))::value)>::value, void>::type>::load(pybind11::handle, bool) [with Type = Eigen::Matrix<double, 4, 1>]’:
pybind11/include/pybind11/eigen/matrix.h:320:17: note: ‘<anonymous>’ declared here
  320 |         value = Type(fits.rows, fits.cols);
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

@daizhirui JIC we want to make more changes: Please do not force push to this branch, it will break the references to commits.

I added two commits:

Rationale:

  • I'm very uncomfortable introducing a subtle behavior change when (apparently) nobody fully understands what we're doing. I agree it is very unlikely that the behavior change will cause problems in the wild, but the risk is not zero. (In the past, when I was still at Google, I may have run global testing, and assuming nothing breaks, I might have just merged the original change, but I don't have access to global testing anymore.)

  • In the face of uncertainty, I feel a conservative fix is more appropriate. Note also that we have a similar warning suppression for Eigen already.

I tested the warning suppression with the command shown under the comment above.

@daizhirui Could you please confirm that the warning suppression solves your original problem?

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

I forgot to add: Ideally we'd add a test in GitHub Actions that fails before this PR, but I'm out of time. Help appreciated. I'm OK though merging this PR without a test, after @daizhirui confirms that it solves the original problem.

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

BTW, please see:

pybind11/docs/basics.rst

Lines 81 to 86 in d2e7e8c

.. note::
``pybind11/pybind11.h`` includes ``Python.h``, as such it must be the first file
included in any source file or header for `the same reasons as Python.h`_.
.. _`the same reasons as Python.h`: https://docs.python.org/3/extending/extending.html#a-simple-example

It doesn't make a difference for the reproducer, but good to keep in mind at all times.

@daizhirui
Copy link
Contributor Author

@rwgk Thanks for reminding the header order. I fixed it when I ran the reproducer.
I tested the warning suppression you propose. It doesn't suppress the warning as expected.

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

@rwgk Thanks for reminding the header order. I fixed it when I ran the reproducer. I tested the warning suppression you propose. It doesn't suppress the warning as expected.

Ugh.

Could you please provide a minimal reproducer similar to what I posted above?

@daizhirui
Copy link
Contributor Author

I tried the reproducer you proposed here #5516 (comment)

I read the Eigen docs about assignment and resizing: link.

I think the modification I propose keeps the same behavior and is more efficient.

As mentioned in Eigen docs, operator= causes resizing value and copying data from the right to the left. But that is for a = b, where b is created somewhere before the assignment.
value = Type(fits.rows, fits.cols) triggers a different implementation, where an std::move is used according to the compiler warnings in the output.
In my opinion, value.resize(fits.rows, fits.cols) and value = Type(fits.rows, fits.cols) have the same behavior (result may be a better word here). value.resize(fits.rows, fits.cols) only has the memory allocation that is done for Type(fits.rows, fits.cols), no std::move(other.m_storage) even.

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

I tried the reproducer you proposed here #5516 (comment)

Huh, why? (And what did you observe? Please be more thorough in providing ground truth information.)

Please reduce your problem. Start with your original code, then strip it down as much as possible. In your case I expect that'll take only a few minutes.

I read the Eigen docs about assignment and resizing: link.

I think the modification I propose keeps the same behavior and is more efficient.

It's a behavior change that could send others down a rabbit hole. I've seen situations where people spent days to debug what subtle change (like you're original one) broke their service.

@daizhirui
Copy link
Contributor Author

I added lines as you proposed:

PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // See PR #5516
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
PYBIND11_WARNING_POP

And with the same mini reproducer:

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/Dense>

void pass_vector(const Eigen::Vector4d &) {}

PYBIND11_MODULE(binding, m) {
  m.def("pass_vector", &pass_vector);
}

and the following CMakeLists.txt:

cmake_minimum_required(VERSION 3.30)

project(test_pybind
        LANGUAGES CXX
)

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")

add_subdirectory(pybind11)
find_package(Eigen3 REQUIRED)

pybind11_add_module(binding binding.cpp)
target_link_libraries(binding
        PRIVATE Eigen3::Eigen
)

, I get the following output:

/usr/bin/cmake -S/home/daizhirui/test_pybind -B/home/daizhirui/test_pybind/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles /home/daizhirui/test_pybind/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/depend
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
cd /home/daizhirui/test_pybind/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/daizhirui/test_pybind /home/daizhirui/test_pybind /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build /home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/DependInfo.cmake "--color="
Dependencies file "CMakeFiles/binding.dir/binding.cpp.o.d" is newer than depends file "/home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/compiler_depend.internal".
Dependencies file "CMakeFiles/binding.dir/link.d" is newer than depends file "/home/daizhirui/test_pybind/build/CMakeFiles/binding.dir/compiler_depend.internal".
Consolidate compiler generated dependencies of target binding
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
make  -f CMakeFiles/binding.dir/build.make CMakeFiles/binding.dir/build
make[2]: Entering directory '/home/daizhirui/test_pybind/build'
[ 50%] Building CXX object CMakeFiles/binding.dir/binding.cpp.o
/usr/bin/c++ -Dbinding_EXPORTS -isystem /home/daizhirui/test_pybind/pybind11/include -isystem /usr/include/python3.13 -isystem /usr/include/eigen3 -Wall -Wextra -O3 -DNDEBUG -std=c++17 -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects -MD -MT CMakeFiles/binding.dir/binding.cpp.o -MF CMakeFiles/binding.dir/binding.cpp.o.d -o CMakeFiles/binding.dir/binding.cpp.o -c /home/daizhirui/test_pybind/binding.cpp
[100%] Linking CXX shared module binding.cpython-313-x86_64-linux-gnu.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/binding.dir/link.txt --verbose=1
/usr/bin/c++ -fPIC -Wall -Wextra -O3 -DNDEBUG -flto -Wl,--dependency-file=CMakeFiles/binding.dir/link.d -shared  -o binding.cpython-313-x86_64-linux-gnu.so CMakeFiles/binding.dir/binding.cpp.o
lto-wrapper: warning: using serial compilation of 2 LTRANS jobs
lto-wrapper: note: see the ‘-flto’ option documentation for more information
In member function ‘operator=’,
    inlined from ‘operator=’ at /usr/include/eigen3/Eigen/src/Core/Matrix.h:281:22,
    inlined from ‘load’ at /home/daizhirui/test_pybind/pybind11/include/pybind11/eigen/matrix.h:322:15,
    inlined from ‘load_impl_sequence’ at /home/daizhirui/test_pybind/pybind11/include/pybind11/cast.h:1660:51,
    inlined from ‘load_args’ at /home/daizhirui/test_pybind/pybind11/include/pybind11/cast.h:1638:68,
    inlined from ‘operator()’ at /home/daizhirui/test_pybind/pybind11/include/pybind11/pybind11.h:260:42,
    inlined from ‘_FUN’ at /home/daizhirui/test_pybind/pybind11/include/pybind11/pybind11.h:256:21:
/usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:504:7: warning: ‘MEM[(struct PlainObjectBase &)&D.16493].m_storage’ may be used uninitialized [-Wmaybe-uninitialized]
  504 |       m_storage = std::move(other.m_storage);
      |       ^
/home/daizhirui/test_pybind/pybind11/include/pybind11/eigen/matrix.h: In function ‘_FUN’:
/home/daizhirui/test_pybind/pybind11/include/pybind11/eigen/matrix.h:322:17: note: ‘<anonymous>’ declared here
  322 |         value = Type(fits.rows, fits.cols);
      |                 ^
/usr/bin/strip /home/daizhirui/test_pybind/build/binding.cpython-313-x86_64-linux-gnu.so
make[2]: Leaving directory '/home/daizhirui/test_pybind/build'
[100%] Built target binding
make[1]: Leaving directory '/home/daizhirui/test_pybind/build'
/usr/bin/cmake -E cmake_progress_start /home/daizhirui/test_pybind/build/CMakeFiles 0

When I commented set(CMAKE_CXX_FLAGS "-Wall -Wextra") in the CMakeLists.txt and tried again, no warnings at all. We don't need this pull request. Everything is good.

-Wall and -Wextra makes the warning suppression in the code ineffective.
Aren't -Wall and -Wextra widely used for robustness?

@rwgk
Copy link
Collaborator

rwgk commented Feb 8, 2025

I'll merge this PR, it clearly helps at least a little bit, based on the reproducer I posted.

@rwgk rwgk merged commit 31d7c87 into pybind:master Feb 8, 2025
76 checks passed
@github-actions github-actions bot added the needs changelog Possibly needs a changelog entry label Feb 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs changelog Possibly needs a changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants