# HG changeset patch
# User Petter Tomner <tomner@kth.se>
# Date 1639357328 -3600
#      Mon Dec 13 02:02:08 2021 +0100
# Node ID 2bea1bfdf50c7cecc640c46c457bdaa977095bfd
# Parent  458b014d88295a837557429814b905c3d37713fc
Object pool for scalar types and stack frames

Make octave_scalar_base an its descendents use a object pool to
decrease malloc and free usage. Same for some stack frames.

libinterp/corefcn/
	* stack-frame.cc: Add new, delete, ~object_pool_cleaner()
libinterp/octave-value/
	* ov-base-scalar.cc: Add new, delete, ~object_pool_cleaner()
	* ov-base-scalar.h:
	* ov-base.cc: "Use" pool cleaners

diff -r 458b014d8829 -r 2bea1bfdf50c libinterp/corefcn/stack-frame.cc
--- a/libinterp/corefcn/stack-frame.cc	Thu Dec 09 21:21:37 2021 -0800
+++ b/libinterp/corefcn/stack-frame.cc	Mon Dec 13 02:02:08 2021 +0100
@@ -142,14 +142,76 @@
 
     void accept (stack_frame_walker& sfw);
 
+    // Overloaded new and delete operators that uses a object pool.
+    static void * operator new (std::size_t count)
+    {
+      // If n_in_obj_pool is negative, the pool is closed
+      if (n_in_obj_pool > 0)
+        return obj_pool[--n_in_obj_pool];
+
+      return ::operator new (count);
+    }
+
+    static void operator delete (void* ptr)
+    {
+      // If n_in_obj_pool is negative, the pool is closed
+      if (n_in_obj_pool >= 0 && n_in_obj_pool < obj_pool_size)
+        {
+          obj_pool[n_in_obj_pool++] = ptr;
+          return;
+        }
+      ::operator delete (ptr);
+    }
+
+    // Class that cleans the object pool when the thread terminates
+    class object_pool_cleaner
+    {
+    public:
+      ~object_pool_cleaner()
+      {
+        int n = n_in_obj_pool;
+        // Set the counter to -1 so that any lingering objects
+        // do use the pool
+        n_in_obj_pool = -1;
+
+        for (int i = 0; i < n; i++)
+          octave_value_list::operator delete (obj_pool[i]);
+      }
+    };
+
+    // For work around reasons pool_cleaner can't be private. Its addres
+    // has to be taken somewhere to fool the compiler to trigger its
+    // destructor.
+    thread_local static object_pool_cleaner pool_cleaner
+    __attribute__ ((tls_model ("initial-exec")));
+
   private:
 
     // Compiled function object associated with this stack frame.
     // Should always be a built-in, .oct or .mex file function and
     // should always be valid.
     octave_function *m_fcn;
+
+    static constexpr int obj_pool_size = 8;
+
+    // Counter for amount of objects in the pool
+    thread_local static int  n_in_obj_pool
+    __attribute__ ((tls_model ("initial-exec")));
+    // The pool
+    thread_local static void * obj_pool[obj_pool_size]
+    __attribute__ ((tls_model ("initial-exec")));
   };
 
+  thread_local compiled_fcn_stack_frame::object_pool_cleaner
+  compiled_fcn_stack_frame::pool_cleaner
+  __attribute__ ((tls_model ("initial-exec")));
+
+  thread_local int  compiled_fcn_stack_frame::n_in_obj_pool
+  __attribute__ ((tls_model ("initial-exec")));
+  // The pool
+  thread_local void * compiled_fcn_stack_frame::obj_pool[obj_pool_size]
+  __attribute__ ((tls_model ("initial-exec")));
+
   // Scripts have a symbol_scope object to store the set of variables
   // in the script, but values for those variables are stored in the
   // stack frame corresponding to the nearest calling function or in
@@ -482,6 +544,49 @@
 
     void break_closure_cycles (const std::shared_ptr<stack_frame>& frame);
 
+    // Overloaded new and delete operators that uses a object pool.
+    static void * operator new (std::size_t count)
+    {
+      // If n_in_obj_pool is negative, the pool is closed
+      if (n_in_obj_pool > 0)
+        return obj_pool[--n_in_obj_pool];
+
+      return ::operator new (count);
+    }
+
+    static void operator delete (void* ptr)
+    {
+      // If n_in_obj_pool is negative, the pool is closed
+      if (n_in_obj_pool >= 0 && n_in_obj_pool < obj_pool_size)
+        {
+          obj_pool[n_in_obj_pool++] = ptr;
+          return;
+        }
+      ::operator delete (ptr);
+    }
+
+    // Class that cleans the object pool when the thread terminates
+    class object_pool_cleaner
+    {
+    public:
+      ~object_pool_cleaner()
+      {
+        int n = n_in_obj_pool;
+        // Set the counter to -1 so that any lingering objects
+        // do use the pool
+        n_in_obj_pool = -1;
+
+        for (int i = 0; i < n; i++)
+          octave_value_list::operator delete (obj_pool[i]);
+      }
+    };
+
+    // For work around reasons pool_cleaner can't be private. Its addres
+    // has to be taken somewhere to fool the compiler to trigger its
+    // destructor.
+    thread_local static object_pool_cleaner pool_cleaner
+    __attribute__ ((tls_model ("initial-exec")));
+
   private:
 
     // User-defined object associated with this stack frame.  Should
@@ -491,8 +596,41 @@
     // The nearest unwind protect frame that was active when this
     // stack frame was created.  Should always be valid.
     unwind_protect *m_unwind_protect_frame;
+
+    static constexpr int obj_pool_size = 8;
+
+    // Counter for amount of objects in the pool
+    thread_local static int  n_in_obj_pool
+    __attribute__ ((tls_model ("initial-exec")));
+    // The pool
+    thread_local static void * obj_pool[obj_pool_size]
+    __attribute__ ((tls_model ("initial-exec")));
   };
 
+  thread_local user_fcn_stack_frame::object_pool_cleaner
+  user_fcn_stack_frame::pool_cleaner
+  __attribute__ ((tls_model ("initial-exec")));
+
+  thread_local int  user_fcn_stack_frame::n_in_obj_pool
+  __attribute__ ((tls_model ("initial-exec")));
+  // The pool
+  thread_local void * user_fcn_stack_frame::obj_pool[obj_pool_size]
+  __attribute__ ((tls_model ("initial-exec")));
+
+  // thread_local destructors seems abit bugged/non-standard compliant in
+  // gcc and clang and seems to only be executed if the object is "used" somehow
+  // even though the destructor has side effects.
+  //
+  // So we just make objects here that are initialized with the addres of the
+  // cleaner object.
+  //
+  // See e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61991
+  compiled_fcn_stack_frame::object_pool_cleaner *
+  pcleaner_cfs = &compiled_fcn_stack_frame::pool_cleaner;
+
+  user_fcn_stack_frame::object_pool_cleaner *
+  pcleaner_ufs = &user_fcn_stack_frame::pool_cleaner;
+
   // Pure scope stack frames (primarily the top-level workspace) have
   // a set of variables and values are stored in the stack frame.  All
   // variable accesses are direct as there are no parent stack frames.
diff -r 458b014d8829 -r 2bea1bfdf50c libinterp/octave-value/ov-base-scalar.cc
--- a/libinterp/octave-value/ov-base-scalar.cc	Thu Dec 09 21:21:37 2021 -0800
+++ b/libinterp/octave-value/ov-base-scalar.cc	Mon Dec 13 02:02:08 2021 +0100
@@ -39,6 +39,48 @@
 #include "ov-base-scalar.h"
 #include "pr-output.h"
 
+// We don't want the new or delete operator in the header, since dynamically
+// loaded object files can't have access to the thread_local objects, since
+// those have the attribute tls_model ("initial-exec"). If they were in the
+// header, they would e.g. be inlined in foreign user libraries and maybe
+// mess up stacks or what not.
+template <typename ST>
+void *
+octave_base_scalar<ST>::operator new (std::size_t count)
+{
+  // If n_in_obj_pool is negative, the pool is closed
+  if (n_in_obj_pool > 0)
+    return obj_pool[--n_in_obj_pool];
+
+  return ::operator new (count);
+}
+
+template <typename ST>
+void octave_base_scalar<ST>::operator delete (void* ptr)
+{
+  // If n_in_obj_pool is negative, the pool is closed
+  if (n_in_obj_pool >= 0 && n_in_obj_pool < obj_pool_size)
+    {
+      obj_pool[n_in_obj_pool++] = ptr;
+      return;
+    }
+  ::operator delete (ptr);
+}
+
+// Can't have this in the header either, since that would expose the
+// thread_local objects
+template <typename ST>
+octave_base_scalar<ST>::object_pool_cleaner::~object_pool_cleaner ()
+{
+  int n = n_in_obj_pool;
+  // Set the counter to -1 so that any lingering objects
+  // do not put them self in the pool
+  n_in_obj_pool = -1;
+
+  for (int i = 0; i < n; i++)
+    octave_base_scalar<ST>::operator delete (obj_pool[i]);
+}
+
 template <typename ST>
 octave_value
 octave_base_scalar<ST>::subsref (const std::string& type,
diff -r 458b014d8829 -r 2bea1bfdf50c libinterp/octave-value/ov-base-scalar.h
--- a/libinterp/octave-value/ov-base-scalar.h	Thu Dec 09 21:21:37 2021 -0800
+++ b/libinterp/octave-value/ov-base-scalar.h	Mon Dec 13 02:02:08 2021 +0100
@@ -63,6 +63,18 @@
 
   ~octave_base_scalar (void) = default;
 
+  // Overloaded new and delete operators that uses a object pool.
+  static void* operator new (std::size_t count) OCTINTERP_API;
+
+  static void operator delete (void* ptr) OCTINTERP_API;
+
+  // Placement new does not use the pool, but we need to provide one
+  // since ordinary new is overridden.
+  static void * operator new (std::size_t count, void *where)
+  {
+    return ::operator new (count, where);
+  }
+
   octave_value squeeze (void) const { return scalar; }
 
   octave_value full_value (void) const { return scalar; }
@@ -170,10 +182,52 @@
   OCTINTERP_API bool
   fast_elem_insert_self (void *where, builtin_type_t btyp) const;
 
+  // Class that cleans the object pool when the thread terminates
+  class OCTINTERP_API object_pool_cleaner
+  {
+  public:
+    OCTINTERP_API ~object_pool_cleaner ();
+  };
+
+  // For work around reasons pool_cleaner can't be private. Its addres
+  // has to be taken somewhere to fool the compiler to trigger its
+  // destructor.
+  thread_local static object_pool_cleaner pool_cleaner
+  __attribute__ ((tls_model ("initial-exec")));
+
 protected:
 
   // The value of this scalar.
   ST scalar;
+
+private:
+
+  static constexpr int obj_pool_size = 8;
+
+  // Counter for amount of objects in the pool
+  thread_local static int  n_in_obj_pool
+    __attribute__ ((tls_model ("initial-exec")));
+  // The pool
+  thread_local static void * obj_pool[obj_pool_size]
+    __attribute__ ((tls_model ("initial-exec")));
 };
 
+// Definitions for the object pool
+
+template <typename ST>
+thread_local
+int octave_base_scalar<ST>::n_in_obj_pool
+__attribute__ ((tls_model ("initial-exec")));
+
+template <typename ST>
+thread_local void *
+octave_base_scalar<ST>::obj_pool[octave_base_scalar<ST>::obj_pool_size]
+__attribute__ ((tls_model ("initial-exec")));
+
+template <typename ST>
+thread_local
+typename octave_base_scalar<ST>::object_pool_cleaner
+octave_base_scalar<ST>::pool_cleaner
+__attribute__ ((tls_model ("initial-exec")));
+
 #endif
diff -r 458b014d8829 -r 2bea1bfdf50c libinterp/octave-value/ov-base.cc
--- a/libinterp/octave-value/ov-base.cc	Thu Dec 09 21:21:37 2021 -0800
+++ b/libinterp/octave-value/ov-base.cc	Mon Dec 13 02:02:08 2021 +0100
@@ -61,6 +61,7 @@
 #include "pr-output.h"
 #include "utils.h"
 #include "variables.h"
+#include "ov-magic-int.h"
 
 builtin_type_t btyp_mixed_numeric (builtin_type_t x, builtin_type_t y)
 {
@@ -1524,6 +1525,63 @@
   return (fcn && fcn->name () == "builtin");
 }
 
+// thread_local destructors seems abit bugged/non-standard compliant in
+// gcc and clang and seems to only be executed if the object is "used" somehow
+// even though the destructor has side effects.
+//
+// So we just make objects here that are initialized with the addres of the
+// cleaner object.
+//
+// See e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61991
+//
+// The pool_cleaner:s are used for the object pools for scalar types.
+
+octave_base_scalar<double>::object_pool_cleaner *
+pcleaner_scalar = &octave_base_scalar<double>::pool_cleaner;
+
+octave_base_scalar<float>::object_pool_cleaner *
+pcleaner_float_scalar = &octave_base_scalar<float>::pool_cleaner;
+
+octave_base_scalar<Complex>::object_pool_cleaner *
+pcleaner_complex_scalar =
+  &octave_base_scalar<Complex>::pool_cleaner;
+octave_base_scalar<FloatComplex>::object_pool_cleaner *
+pcleaner_float_complex =
+  &octave_base_scalar<FloatComplex>::pool_cleaner;
+
+octave_base_scalar<bool>::object_pool_cleaner *
+pcleaner_bool = &octave_base_scalar<bool>::pool_cleaner;
+
+octave_base_scalar<octave_int8>::object_pool_cleaner *
+pcleaner_int8 = &octave_base_scalar<octave_int8>::pool_cleaner;
+octave_base_scalar<octave_int16>::object_pool_cleaner *
+pcleaner_int16 = &octave_base_scalar<octave_int16>::pool_cleaner;
+octave_base_scalar<octave_int32>::object_pool_cleaner *
+pcleaner_int32 = &octave_base_scalar<octave_int32>::pool_cleaner;
+octave_base_scalar<octave_int64>::object_pool_cleaner *
+pcleaner_int64 = &octave_base_scalar<octave_int64>::pool_cleaner;
+
+octave_base_scalar<octave_uint8>::object_pool_cleaner *
+pcleaner_uint8 = &octave_base_scalar<octave_uint8>::pool_cleaner;
+octave_base_scalar<octave_uint16>::object_pool_cleaner *
+pcleaner_uint16 = &octave_base_scalar<octave_uint16>::pool_cleaner;
+octave_base_scalar<octave_uint32>::object_pool_cleaner *
+pcleaner_uint32 = &octave_base_scalar<octave_uint32>::pool_cleaner;
+octave_base_scalar<octave_uint64>::object_pool_cleaner *
+pcleaner_uint64 = &octave_base_scalar<octave_uint64>::pool_cleaner;
+
+// The size of magic ints and the octave_base_scalar of 64bit integer sizes
+// need to match, since they are using the same object pool.
+//
+// If these asserts fail becouse of some change, just overload the new and
+// delete operator for magic ints to "= default" so that they don't use the
+// pool
+static_assert (sizeof (octave_magic_int) ==
+               sizeof (octave_base_scalar<octave_int64>));
+static_assert (sizeof (octave_magic_uint) ==
+               sizeof (octave_base_scalar<octave_uint64>));
+
+
 OCTAVE_NAMESPACE_BEGIN
 
 void
