changeset 30488:abb4823df535 stable

with C++17, match malloc/free for MEX memory (bug #61472) * Array-fwd.h, Array.h: If std::pmr::polymorphic_allocator<T> is available, use it as the default allocator for the Array<T> class. * mex.cc (fp_type_traits): New traits class and specializations. (mx_memory_resource): If std::pmr::polymorphic_allocator<T> is available, define class to manage MEX memory allocations when they are transferred to octave_value objects. (the_mx_memory_resource): New static object. (fp_to_ov): Use resource-aquiring Array constructor with a pointer to the_mx_memory_resource so that the Array object will own the allocated array and use std::free to release the storage storage allocated with mxMalloc or mxRealloc after it is returned to Octave in an octave_value object. If the polymorphic allocator is not availble (for example, compiling with a pre-c++17 compiler) then don't mix malloc with delete[] for arrays of Complex or FloatComplex objects. (int_to_ov): Set up to use the same kind of resource management as for fp_to_ov, except disable for now (we need to extend the intNDArray classes to have resource-acquiring constructors). * acinclude.m4 (OCTAVE_CHECK_STD_PMR_POLYMORPHIC_ALLOCATOR): New macro. * configure.ac: Use it. * mk-octave-config-h.sh: Insert definition for OCTAVE_CHECK_STD_PMR_POLYMORPHIC_ALLOCATOR into octave-config.h.
author John W. Eaton <jwe@octave.org>
date Sat, 04 Dec 2021 09:13:12 -0500
parents 1f51aa6719e5
children bd67d0045e21
files build-aux/mk-octave-config-h.sh configure.ac libinterp/corefcn/mex.cc liboctave/array/Array-fwd.h liboctave/array/Array.h m4/acinclude.m4
diffstat 6 files changed, 193 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/build-aux/mk-octave-config-h.sh	Wed Dec 15 16:22:35 2021 -0500
+++ b/build-aux/mk-octave-config-h.sh	Sat Dec 04 09:13:12 2021 -0500
@@ -279,6 +279,7 @@
 $SED -n 's/#\(\(undef\|define\) OCTAVE_ENABLE_OPENMP.*$\)/#  \1/p' $config_h_file
 $SED -n 's/#\(\(undef\|define\) OCTAVE_HAVE_LONG_LONG_INT.*$\)/#  \1/p' $config_h_file
 $SED -n 's/#\(\(undef\|define\) OCTAVE_HAVE_UNSIGNED_LONG_LONG_INT.*$\)/#  \1/p' $config_h_file
+$SED -n 's/#\(\(undef\|define\) OCTAVE_HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR.*$\)/#  \1/p' $config_h_file
 $SED -n 's/#\(\(undef\|define\) OCTAVE_HAVE_OVERLOAD_CHAR_INT8_TYPES.*$\)/#  \1/p' $config_h_file
 $SED -n 's/#\(\(undef\|define\) OCTAVE_SIZEOF_F77_INT_TYPE.*$\)/#  \1/p' $config_h_file
 $SED -n 's/#\(\(undef\|define\) OCTAVE_SIZEOF_IDX_TYPE.*$\)/#  \1/p' $config_h_file
--- a/configure.ac	Wed Dec 15 16:22:35 2021 -0500
+++ b/configure.ac	Sat Dec 04 09:13:12 2021 -0500
@@ -400,6 +400,8 @@
   OCTAVE_CONFIGURE_WARNING([warn_stl_algo_h])
 fi
 
+OCTAVE_CHECK_STD_PMR_POLYMORPHIC_ALLOCATOR
+
 ### Check version number when using gcc.
 dnl It might be different from the g++ version number.
 
--- a/libinterp/corefcn/mex.cc	Wed Dec 15 16:22:35 2021 -0500
+++ b/libinterp/corefcn/mex.cc	Sat Dec 04 09:13:12 2021 -0500
@@ -27,6 +27,12 @@
 #  include "config.h"
 #endif
 
+#define DEBUG 1
+
+#if defined (DEBUG)
+#  include <iostream>
+#endif
+
 #include <cstdarg>
 #include <cstdlib>
 #include <cstring>
@@ -34,9 +40,12 @@
 
 #include <limits>
 #include <map>
+#include <memory_resource>
 #include <set>
 #include <string>
 
+// Needed to instantiate Array objects with custom allocator.
+#include "Array.cc"
 #include "f77-fcn.h"
 #include "lo-ieee.h"
 #include "oct-locbuf.h"
@@ -253,18 +262,12 @@
   extern OCTINTERP_API void mxSetImagData (mxArray *ptr, void *pi);
 }
 
-// #define DEBUG 1
-
-#if DEBUG
-#  include <iostream>
-#endif
-
 static void *
 xmalloc (size_t n)
 {
   void *ptr = std::malloc (n);
 
-#if DEBUG
+#if defined (DEBUG)
   std::cerr << "xmalloc (" << n << ") = " << ptr << std::endl;
 #endif
 
@@ -276,7 +279,7 @@
 {
   void *newptr = std::realloc (ptr, n);
 
-#if DEBUG
+#if defined (DEBUG)
   std::cerr << "xrealloc (" << ptr << ", " << n << ") = " << newptr
             << std::endl;
 #endif
@@ -287,7 +290,7 @@
 static void
 xfree (void *ptr)
 {
-#if DEBUG
+#if defined (DEBUG)
   std::cerr << "xfree (" << ptr << ")" << std::endl;
 #endif
 
@@ -310,6 +313,63 @@
   return max_len;
 }
 
+// FIXME: Is there a better/standard way to do this job?
+
+template <typename T>
+class fp_type_traits
+{
+public:
+  static const bool is_complex = false;
+};
+
+template <>
+class fp_type_traits<Complex>
+{
+public:
+  static const bool is_complex = true;
+};
+
+template <>
+class fp_type_traits <FloatComplex>
+{
+public:
+  static const bool is_complex = true;
+};
+
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+#  include <memory_resource>
+
+class mx_memory_resource : public std::pmr::memory_resource
+{
+private:
+
+  void * do_allocate (std::size_t bytes, size_t /*alignment*/)
+  {
+    void *ptr = xmalloc (bytes);
+
+    if (! ptr)
+      throw std::bad_alloc ();
+
+    return ptr;
+  }
+
+  void do_deallocate (void* ptr, std::size_t /*bytes*/,
+                      std::size_t /*alignment*/)
+  {
+    xfree (ptr);
+  }
+
+  bool do_is_equal (const std::pmr::memory_resource& other) const noexcept
+  {
+    return this == dynamic_cast<const mx_memory_resource *> (&other);
+  }
+};
+
+// FIXME: Is it OK for the memory resource object to be defined this
+// way?
+static mx_memory_resource the_mx_memory_resource;
+#endif
+
 // ------------------------------------------------------------------
 
 mxArray_base::mxArray_base (bool interleaved)
@@ -2076,13 +2136,46 @@
   octave_value
   fp_to_ov (const dim_vector& dv) const
   {
+    octave_value retval;
+
     ELT_T *ppr = static_cast<ELT_T *> (m_pr);
 
-    Array<ELT_T> val (ppr, dv);
-
-    maybe_disown_ptr (m_pr);
-
-    return octave_value (val);
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+
+    octave::unwind_action act ([=] () { maybe_disown_ptr (m_pr); });
+
+    return octave_value (Array<ELT_T> (ppr, dv, &the_mx_memory_resource));
+
+#else
+
+    if (fp_type_traits<ELT_T>::is_complex)
+      {
+        // Mixing malloc and delete[] for arrays of Complex and
+        // FloatComplex objects is not possible.
+
+        Array<ELT_T> val (dv);
+
+        ELT_T *ptr = val.fortran_vec ();
+
+        mwSize nel = get_number_of_elements ();
+
+        for (mwIndex i = 0; i < nel; i++)
+          ptr[i] = ppr[i];
+
+        return octave_value (val);
+      }
+    else
+      {
+        // Although behavior is not specified by the standard, it should
+        // work to mix malloc and delete[] for arrays of float and
+        // double.
+
+        octave::unwind_action act ([=] () { maybe_disown_ptr (m_pr); });
+
+        return octave_value (Array<ELT_T> (ppr, dv));
+      }
+
+#endif
   }
 
   template <typename ELT_T, typename ARRAY_T, typename ARRAY_ELT_T>
@@ -2094,11 +2187,17 @@
 
     ELT_T *ppr = static_cast<ELT_T *> (m_pr);
 
-#if 0
-    ARRAY_T val (ppr, dv);
-
-    maybe_disown_ptr (m_pr);
+#if 0 && defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+
+    octave::unwind_action act ([=] () { maybe_disown_ptr (m_pr); });
+
+    return ARRAY_T (ppr, dv, &the_mx_memory_resource);
+
 #else
+
+    // All octave_int types are objects so we can't mix malloc and
+    // delete[] and we always have to copy.
+
     ARRAY_T val (dv);
 
     ARRAY_ELT_T *ptr = val.fortran_vec ();
@@ -2107,9 +2206,10 @@
 
     for (mwIndex i = 0; i < nel; i++)
       ptr[i] = ppr[i];
-#endif
 
     return octave_value (val);
+
+#endif
   }
 
 protected:
@@ -2360,6 +2460,9 @@
 
     T *ppr = static_cast<T *> (m_pr);
 
+    // We allocate in the Array<T> constructor and copy here, so we
+    // don't need the custom allocator for this object.
+
     Array<std::complex<T>> val (dv);
 
     std::complex<T> *ptr = val.fortran_vec ();
--- a/liboctave/array/Array-fwd.h	Wed Dec 15 16:22:35 2021 -0500
+++ b/liboctave/array/Array-fwd.h	Sat Dec 04 09:13:12 2021 -0500
@@ -28,7 +28,17 @@
 
 #include "octave-config.h"
 
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+#  include <memory_resource>
+
+template <typename T, typename Alloc = std::pmr::polymorphic_allocator<T>>
+class OCTARRAY_API Array;
+
+#else
+
 template <typename T, typename Alloc = std::allocator<T>>
 class OCTARRAY_API Array;
 
 #endif
+
+#endif
--- a/liboctave/array/Array.h	Wed Dec 15 16:22:35 2021 -0500
+++ b/liboctave/array/Array.h	Sat Dec 04 09:13:12 2021 -0500
@@ -789,7 +789,12 @@
 
   //! Apply function fcn to each element of the Array<T, Alloc>.  This function
   //! is optimized with a manually unrolled loop.
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+  template <typename U, typename F,
+            typename A = std::pmr::polymorphic_allocator<U>>
+#else
   template <typename U, typename F, typename A = std::allocator<U>>
+#endif
   Array<U, A>
   map (F fcn) const
   {
@@ -821,12 +826,20 @@
 
   //@{
   //! Overloads for function references.
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+  template <typename U, typename A = std::pmr::polymorphic_allocator<U>>
+#else
   template <typename U, typename A = std::allocator<U>>
+#endif
   Array<U, A>
   map (U (&fcn) (T)) const
   { return map<U, U (&) (T), A> (fcn); }
 
+#if defined (HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR)
+  template <typename U, typename A = std::pmr::polymorphic_allocator<U>>
+#else
   template <typename U, typename A = std::allocator<U>>
+#endif
   Array<U, A>
   map (U (&fcn) (const T&)) const
   { return map<U, U (&) (const T&), A> (fcn); }
--- a/m4/acinclude.m4	Wed Dec 15 16:22:35 2021 -0500
+++ b/m4/acinclude.m4	Sat Dec 04 09:13:12 2021 -0500
@@ -255,6 +255,51 @@
   fi
 ])
 dnl
+dnl Check whether std::pmr::polymorphic_allocator is available.
+dnl
+AC_DEFUN([OCTAVE_CHECK_STD_PMR_POLYMORPHIC_ALLOCATOR], [
+  AC_CACHE_CHECK([whether std::pmr::polymorphic_allocator is avalable],
+    [octave_cv_std_pmr_polymorphic_allocator],
+    [AC_LANG_PUSH(C++)
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+      #include <cstdlib>
+      #include <memory_resource>
+      #include <vector>
+      class mx_memory_resource : public std::pmr::memory_resource
+      {
+      private:
+        void * do_allocate (std::size_t bytes, size_t /*alignment*/)
+        {
+          void *ptr = std::malloc (bytes);
+          if (! ptr)
+            throw std::bad_alloc ();
+            return ptr;
+        }
+        void do_deallocate (void* ptr, std::size_t /*bytes*/,
+                            std::size_t /*alignment*/)
+        {
+          std::free (ptr);
+        }
+        bool do_is_equal (const std::pmr::memory_resource& other) const noexcept
+        {
+          return this == dynamic_cast<const mx_memory_resource *> (&other);
+          return true;
+        }
+      };
+      mx_memory_resource the_mx_memory_resource;
+    ]], [[
+      std::pmr::vector<int> my_int_vec { &the_mx_memory_resource };
+    ]])],
+    octave_cv_std_pmr_polymorphic_allocator=yes,
+    octave_cv_std_pmr_polymorphic_allocator=no)
+    AC_LANG_POP(C++)
+  ])
+  if test $octave_cv_std_pmr_polymorphic_allocator = yes; then
+    AC_DEFINE(HAVE_STD_PMR_POLYMORPHIC_ALLOCATOR, 1,
+      [Define to 1 if std::pmr::polymorphic_allocator is available.])
+  fi
+])
+dnl
 dnl Check whether CXSparse is version 2.2 or later
 dnl FIXME: This test uses a version number.  It potentially could
 dnl        be re-written to actually call a function, but is it worth it?