Browse Source

DeletedBufferChain

David Rose 18 years ago
parent
commit
982f614991

+ 6 - 2
dtool/LocalSetup.pp

@@ -503,11 +503,15 @@ $[cdefine __USE_LARGEFILE64]
 #define USE_MEMORY_MALLOC
 #define USE_MEMORY_NOWRAPPERS
 #if $[ALTERNATIVE_MALLOC]
-  #if $[and $[HAVE_THREADS], $[not $[SIMPLE_THREADS]]]
-    // A fast thread-safe alternative implementation.
+  #if $[and $[WIN32_PLATFORM], $[HAVE_THREADS], $[not $[SIMPLE_THREADS]]]
+    // A fast thread-safe alternative implementation, but which only
+    // seems to be a good choice on Windows.  (It crashes on Linux and
+    // isn't thread-safe on OSX).
     #set USE_MEMORY_PTMALLOC2 1
   #else
     // A faster, but non-thread-safe, alternative implementation.
+    // When threading support is compiled in, we use a global mutex to
+    // protect it.
     #set USE_MEMORY_DLMALLOC 1
   #endif
 #else

+ 3 - 0
dtool/src/dtoolbase/Sources.pp

@@ -14,6 +14,7 @@
     atomicAdjustPosixImpl.h atomicAdjustPosixImpl.I \
     atomicAdjustWin32Impl.h atomicAdjustWin32Impl.I \
     cmath.I cmath.h \
+    deletedBufferChain.h deletedBufferChain.I \
     deletedChain.h deletedChain.T \
     dtoolbase.h dtoolbase_cc.h dtoolsymbols.h \
     fakestringstream.h \
@@ -46,6 +47,7 @@
     atomicAdjustI386Impl.cxx \
     atomicAdjustPosixImpl.cxx \
     atomicAdjustWin32Impl.cxx \
+    deletedBufferChain.cxx \
     dtoolbase.cxx \
     memoryBase.cxx \
     memoryHook.cxx \
@@ -67,6 +69,7 @@
     atomicAdjustPosixImpl.h atomicAdjustPosixImpl.I \
     atomicAdjustWin32Impl.h atomicAdjustWin32Impl.I \
     cmath.I cmath.h \
+    deletedBufferChain.h deletedBufferChain.I \
     deletedChain.h deletedChain.T \
     dtoolbase.h dtoolbase_cc.h dtoolsymbols.h fakestringstream.h \
     indent.I indent.h \

+ 86 - 0
dtool/src/dtoolbase/deletedBufferChain.I

@@ -0,0 +1,86 @@
+// Filename: deletedBufferChain.I
+// Created by:  drose (20Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::validate
+//       Access: Public
+//  Description: Returns true if the pointer is valid, false if it has
+//               been deleted or if it was never a valid pointer.
+//
+//               This is only meaningful in debug mode, where
+//               USE_DELETEDCHAINFLAG is defined.  If not, this
+//               trivially returns true.
+////////////////////////////////////////////////////////////////////
+INLINE bool DeletedBufferChain::
+validate(void *ptr) {
+  TAU_PROFILE("bool DeletedBufferChain::validate(void *)", " ", TAU_USER);
+  if (ptr == (void *)NULL) {
+    return false;
+  }
+
+#ifdef USE_DELETEDCHAINFLAG
+  const ObjectNode *obj = buffer_to_node(ptr);
+  return AtomicAdjust::get(obj->_flag) == DCF_alive;
+#else
+  return true;
+#endif  // USE_DELETEDCHAINFLAG
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::get_buffer_size
+//       Access: Public
+//  Description: Returns the size of the buffer that is actually
+//               returned at each request.
+////////////////////////////////////////////////////////////////////
+INLINE size_t DeletedBufferChain::
+get_buffer_size() const {
+  return _buffer_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::node_to_buffer
+//       Access: Private, Static
+//  Description: Casts an ObjectNode* to a void* buffer.
+////////////////////////////////////////////////////////////////////
+INLINE void *DeletedBufferChain::
+node_to_buffer(DeletedBufferChain::ObjectNode *node) {
+#ifdef USE_DELETEDCHAINFLAG
+  // In development mode, we increment the pointer so that the
+  // returned data does not overlap our _flags member.
+  return (void *)(((PN_int32 *)node) + 1);
+#else
+  return (void *)node;
+#endif  // NDEBUG
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::buffer_to_node
+//       Access: Private, Static
+//  Description: Casts a void* buffer to an ObjectNode* .
+////////////////////////////////////////////////////////////////////
+INLINE DeletedBufferChain::ObjectNode *DeletedBufferChain::
+buffer_to_node(void *ptr) {
+#ifdef USE_DELETEDCHAINFLAG
+  // In development mode, we decrement the pointer to undo the
+  // increment we did above.
+  return (ObjectNode *)(((PN_int32 *)ptr) - 1);
+#else
+  return (ObjectNode *)ptr;
+#endif  // NDEBUG
+}

+ 134 - 0
dtool/src/dtoolbase/deletedBufferChain.cxx

@@ -0,0 +1,134 @@
+// Filename: deletedBufferChain.cxx
+// Created by:  drose (20Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "deletedBufferChain.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::Constructor
+//       Access: Protected
+//  Description: Use the global MemoryHook to get a new
+//               DeletedBufferChain of the appropriate size.
+////////////////////////////////////////////////////////////////////
+DeletedBufferChain::
+DeletedBufferChain(size_t buffer_size) {
+  _deleted_chain = NULL;
+  _buffer_size = buffer_size;
+  _alloc_size = _buffer_size;
+
+#ifdef USE_DELETEDCHAINFLAG
+  // In development mode, we also need to reserve space for _flag.
+  _alloc_size += sizeof(PN_int32);
+#endif  // NDEBUG
+
+  // We must allocate at least this much space for bookkeeping
+  // reasons.
+  _buffer_size = max(_buffer_size, sizeof(ObjectNode));
+  _alloc_size = max(_alloc_size, sizeof(ObjectNode));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::allocate
+//       Access: Public
+//  Description: Allocates the memory for a new buffer of the
+//               indicated size (which must be no greater than the
+//               fixed size associated with the DeletedBufferChain).
+////////////////////////////////////////////////////////////////////
+void *DeletedBufferChain::
+allocate(size_t size, TypeHandle type_handle) {
+  TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", " ", TAU_USER);
+  assert(size <= _buffer_size);
+
+  ObjectNode *obj;
+
+  _lock.lock();
+  if (_deleted_chain != (ObjectNode *)NULL) {
+    obj = _deleted_chain;
+    _deleted_chain = _deleted_chain->_next;
+    _lock.release();
+
+#ifdef USE_DELETEDCHAINFLAG
+    assert(obj->_flag == (PN_int32)DCF_deleted);
+    obj->_flag = DCF_alive;
+#endif  // NDEBUG
+
+    void *ptr = node_to_buffer(obj);
+
+#ifdef DO_MEMORY_USAGE
+    //    type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_inactive, _alloc_size);
+    type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, _alloc_size);
+#endif  // DO_MEMORY_USAGE
+
+    return ptr;
+  }
+  _lock.release();
+
+  // If we get here, the deleted_chain is empty; we have to allocate a
+  // new object from the system pool.
+
+  obj = (ObjectNode *)NeverFreeMemory::alloc(_alloc_size);
+
+#ifdef USE_DELETEDCHAINFLAG
+  obj->_flag = DCF_alive;
+#endif  // USE_DELETEDCHAINFLAG
+
+  void *ptr = node_to_buffer(obj);
+
+#ifdef DO_MEMORY_USAGE
+  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, _alloc_size);
+#endif  // DO_MEMORY_USAGE
+
+  return ptr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedBufferChain::deallocate
+//       Access: Public
+//  Description: Frees the memory for a buffer previously allocated
+//               via allocate().
+////////////////////////////////////////////////////////////////////
+void DeletedBufferChain::
+deallocate(void *ptr, TypeHandle type_handle) {
+  TAU_PROFILE("void DeletedBufferChain::deallocate(void *, TypeHandle)", " ", TAU_USER);
+  assert(ptr != (void *)NULL);
+
+#ifdef DO_MEMORY_USAGE
+  type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_active, _alloc_size);
+  //  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_inactive, _alloc_size);
+
+#endif  // DO_MEMORY_USAGE
+
+  ObjectNode *obj = buffer_to_node(ptr);
+
+#ifdef USE_DELETEDCHAINFLAG
+  PN_int32 orig_flag = AtomicAdjust::compare_and_exchange(obj->_flag, DCF_alive, DCF_deleted);
+
+  // If this assertion is triggered, you double-deleted an object.
+  assert(orig_flag != (PN_int32)DCF_deleted);
+
+  // If this assertion is triggered, you tried to delete an object
+  // that was never allocated, or you have heap corruption.
+  assert(orig_flag == (PN_int32)DCF_alive);
+#endif  // NDEBUG
+
+  _lock.lock();
+
+  obj->_next = _deleted_chain;
+  _deleted_chain = obj;
+
+  _lock.release();
+}

+ 113 - 0
dtool/src/dtoolbase/deletedBufferChain.h

@@ -0,0 +1,113 @@
+// Filename: deletedBufferChain.h
+// Created by:  drose (20Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef DELETEDBUFFERCHAIN_H
+#define DELETEDBUFFERCHAIN_H
+
+#include "dtoolbase.h"
+#include "neverFreeMemory.h"
+#include "mutexImpl.h"
+#include "atomicAdjust.h"
+#include "numeric_types.h"
+#include "typeHandle.h"
+#include <assert.h>
+
+// Though it's tempting, it doesn't seem to be possible to implement
+// DeletedBufferChain via the atomic exchange operation.
+// Specifically, a pointer may be removed from the head of the chain,
+// then the same pointer reinserted in the chain, while another thread
+// is waiting; and that thread will not detect the change.  So
+// instead, we always use a mutex.
+
+#ifndef NDEBUG
+// In development mode, we define USE_DELETEDCHAINFLAG, which
+// triggers the piggyback of an additional word of data on every
+// allocated block, so we can ensure that an object is not
+// double-deleted and that the deleted chain remains intact.
+#define USE_DELETEDCHAINFLAG 1
+#endif // NDEBUG
+
+#ifdef USE_DELETEDCHAINFLAG
+enum DeletedChainFlag {
+  DCF_deleted = 0xfeedba0f,
+  DCF_alive = 0x12487654,
+};
+#endif
+
+////////////////////////////////////////////////////////////////////
+//       Class : DeletedBufferChain
+// Description : This template class can be used to provide faster
+//               allocation/deallocation for many Panda objects.  It
+//               works by maintaining a linked list of deleted buffers
+//               that are all of the same size; when a new object is
+//               allocated that matches that size, the same space is
+//               just reused.
+//
+//               This class manages untyped buffers of a fixed size.
+//               It can be used directly; or it also serves as a
+//               backbone for DeletedChain, which is a template class
+//               that manages object allocations.
+//
+//               Use MemoryHook to get a new DeletedBufferChain of a
+//               particular size.
+////////////////////////////////////////////////////////////////////
+class DeletedBufferChain {
+protected:
+  DeletedBufferChain(size_t buffer_size);
+
+public:
+  void *allocate(size_t size, TypeHandle type_handle);
+  void deallocate(void *ptr, TypeHandle type_handle);
+
+  INLINE bool validate(void *ptr);
+  INLINE size_t get_buffer_size() const;
+
+private:
+  class ObjectNode {
+  public:
+#ifdef USE_DELETEDCHAINFLAG
+    // In development mode, we piggyback this extra data.  This is
+    // maintained out-of-band from the actual pointer returned, so we
+    // can safely use this flag to indicate the difference between
+    // allocated and freed pointers.
+    TVOLATILE PN_int32 _flag;
+#endif
+
+    // This pointer sits within the buffer, in the same space
+    // referenced by the actual pointer returned (unlike _flag,
+    // above).  It's only used when the buffer is deleted, so there's
+    // no harm in sharing space with the undeleted buffer.
+    ObjectNode *_next;
+  };
+
+  static INLINE void *node_to_buffer(ObjectNode *node);
+  static INLINE ObjectNode *buffer_to_node(void *buffer);
+
+  ObjectNode *_deleted_chain;
+  
+  MutexImpl _lock;
+  size_t _buffer_size;
+  size_t _alloc_size;
+
+  friend class MemoryHook;
+};
+
+#include "deletedBufferChain.I"
+
+#endif
+

+ 23 - 155
dtool/src/dtoolbase/deletedChain.T

@@ -28,58 +28,14 @@ template<class Type>
 INLINE Type *DeletedChain<Type>::
 allocate(size_t size, TypeHandle type_handle) {
   TAU_PROFILE("Type *DeletedChain<Type>::allocate(size_t, TypeHandle)", " ", TAU_USER);
-  assert(size <= sizeof(Type));
-
-  size_t alloc_size = sizeof(Type);
-#ifdef USE_DELETEDCHAINFLAG
-  // In development mode, we also need to reserve space for _flag.
-  alloc_size += sizeof(PN_int32);
-#endif  // NDEBUG
-
-  ObjectNode *obj;
-
-  init_lock();
-  _lock->lock();
-  if (_deleted_chain != (ObjectNode *)NULL) {
-    obj = _deleted_chain;
-    _deleted_chain = _deleted_chain->_next;
-    _lock->release();
-
-#ifdef USE_DELETEDCHAINFLAG
-    assert(obj->_flag == (PN_int32)DCF_deleted);
-    obj->_flag = DCF_alive;
-#endif  // NDEBUG
-
-    Type *ptr = node_to_type(obj);
+  init_deleted_chain();
+  void *ptr = _chain->allocate(size, type_handle);
 
 #ifdef DO_MEMORY_USAGE
-    type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
-    type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
-    memory_hook->mark_pointer(ptr, max(size, sizeof(ObjectNode)), make_ref_ptr(ptr));
+  memory_hook->mark_pointer(ptr, _chain->get_buffer_size(), make_ref_ptr(ptr));
 #endif  // DO_MEMORY_USAGE
 
-    return ptr;
-  }
-  _lock->release();
-
-  // If we get here, the deleted_chain is empty; we have to allocate a
-  // new object from the system pool.
-
-  alloc_size = max(alloc_size, sizeof(ObjectNode));
-  obj = (ObjectNode *)NeverFreeMemory::alloc(alloc_size);
-
-#ifdef USE_DELETEDCHAINFLAG
-  obj->_flag = DCF_alive;
-#endif  // USE_DELETEDCHAINFLAG
-
-  Type *ptr = node_to_type(obj);
-
-#ifdef DO_MEMORY_USAGE
-  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
-  memory_hook->mark_pointer(ptr, max(size, sizeof(ObjectNode)), make_ref_ptr(ptr));
-#endif  // DO_MEMORY_USAGE
-
-  return ptr;
+  return (Type *)ptr;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -91,47 +47,18 @@ template<class Type>
 INLINE void DeletedChain<Type>::
 deallocate(Type *ptr, TypeHandle type_handle) {
   TAU_PROFILE("void DeletedChain<Type>::deallocate(Type *, TypeHandle)", " ", TAU_USER);
-  assert(ptr != (Type *)NULL);
 
 #ifdef DO_MEMORY_USAGE
-  size_t alloc_size = sizeof(Type);
-
   memory_hook->mark_pointer(ptr, 0, make_ref_ptr(ptr));
+#endif
 
-#ifdef USE_DELETEDCHAINFLAG
-  // In development mode, we also need to reserve space for _flag.
-  alloc_size += sizeof(PN_int32);
-#endif  // NDEBUG
-  type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
-  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
-
-#endif  // DO_MEMORY_USAGE
-
-  ObjectNode *obj = type_to_node(ptr);
-
-#ifdef USE_DELETEDCHAINFLAG
-  PN_int32 orig_flag = AtomicAdjust::compare_and_exchange(obj->_flag, DCF_alive, DCF_deleted);
-
-  // If this assertion is triggered, you double-deleted an object.
-  assert(orig_flag != (PN_int32)DCF_deleted);
-
-  // If this assertion is triggered, you tried to delete an object
-  // that was never allocated, or you have heap corruption.
-  assert(orig_flag == (PN_int32)DCF_alive);
-#endif  // NDEBUG
-
-  // We have to test the lock again, even though we must have
-  // allocated this object at some point, because Windows might make
-  // multiple different DeletedChain instances for a particular Type,
-  // and there's no guarantee this one is the same one we used to
-  // allocate this object.
-  init_lock();
-  _lock->lock();
+  // We have to call this again, even though it must have been called
+  // in allocate(), because with template resolution across dll's it
+  // is possible that this DeletedChain object is not the same one
+  // that allocated the object.
+  init_deleted_chain();
 
-  obj->_next = _deleted_chain;
-  _deleted_chain = obj;
-
-  _lock->release();
+  _chain->deallocate(ptr, type_handle);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -148,16 +75,13 @@ template<class Type>
 INLINE bool DeletedChain<Type>::
 validate(const Type *ptr) {
   TAU_PROFILE("bool DeletedChain<Type>::validate(Type *)", " ", TAU_USER);
-  if (ptr == (Type *)NULL) {
-    return false;
-  }
 
 #ifdef USE_DELETEDCHAINFLAG
-  const ObjectNode *obj = type_to_node((Type *)ptr);
-  return AtomicAdjust::get(obj->_flag) == DCF_alive;
+  init_deleted_chain();
+  return _chain->validate((void *)ptr);
 #else
   return true;
-#endif  // NDEBUG
+#endif  // USE_DELETEDCHAINFLAG
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -195,76 +119,20 @@ make_ref_ptr(ReferenceCount *ptr) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DeletedChain::node_to_type
-//       Access: Private, Static
-//  Description: Casts an ObjectNode* to a Type*.
-////////////////////////////////////////////////////////////////////
-template<class Type>
-INLINE Type *DeletedChain<Type>::
-node_to_type(TYPENAME DeletedChain<Type>::ObjectNode *node) {
-#ifdef USE_DELETEDCHAINFLAG
-  // In development mode, we increment the pointer so that the
-  // returned data does not overlap our _flags member.
-  return (Type *)(((PN_int32 *)node) + 1);
-#else
-  return (Type *)node;
-#endif  // NDEBUG
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DeletedChain::type_to_node
-//       Access: Private, Static
-//  Description: Casts a Type* to an ObjectNode* .
-////////////////////////////////////////////////////////////////////
-template<class Type>
-INLINE TYPENAME DeletedChain<Type>::ObjectNode *DeletedChain<Type>::
-type_to_node(Type *ptr) {
-#ifdef USE_DELETEDCHAINFLAG
-  // In development mode, we decrement the pointer to undo the
-  // increment we did above.
-  return (ObjectNode *)(((PN_int32 *)ptr) - 1);
-#else
-  return (ObjectNode *)ptr;
-#endif  // NDEBUG
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DeletedChain::init_lock
-//       Access: Private
-//  Description: Ensures the lock pointer has been allocated.
-////////////////////////////////////////////////////////////////////
-template<class Type>
-INLINE void DeletedChain<Type>::
-init_lock() {
-  if (_lock == (MutexImpl *)NULL) {
-    do_init_lock(_lock);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DeletedChain::do_init_lock
+//     Function: DeletedChain::init_deleted_chain
 //       Access: Private
-//  Description: Allocates the lock pointer if necessary.  Takes some
-//               pains to protect itself from race conditions.
-//
-//               We have to receive the MutexImpl object as a
-//               parameter, because this is a non-inline function, and
-//               the template pointer might get evaluated differently
-//               for inline vs. non-inline functions.
+//  Description: Assigns the _chain pointer if it is not already
+//               assigned.  This can't be done by a constructor, since
+//               often the DeletedChain instance is used before its
+//               static construct has had a chance to be called.
 ////////////////////////////////////////////////////////////////////
 template<class Type>
 void DeletedChain<Type>::
-do_init_lock(MutexImpl *&lock) {
-  MutexImpl *new_lock = new MutexImpl;
-
-  MutexImpl *result;
-  result = (MutexImpl *)AtomicAdjust::compare_and_exchange_ptr((void * TVOLATILE &)lock, (void *)NULL, (void *)new_lock);
-  
-  if (result != NULL) {
-    delete new_lock;
+init_deleted_chain() {
+  if (_chain == (DeletedBufferChain *)NULL) {
+    init_memory_hook();
+    _chain = memory_hook->get_deleted_chain(sizeof(Type));
   }
-  
-  assert(lock != (MutexImpl *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 7 - 59
dtool/src/dtoolbase/deletedChain.h

@@ -20,36 +20,9 @@
 #define DELETEDCHAIN_H
 
 #include "dtoolbase.h"
-#include "neverFreeMemory.h"
-#include "mutexImpl.h"
-#include "atomicAdjust.h"
-#include "numeric_types.h"
-#include "register_type.h"
-#include "typeHandle.h"
+#include "deletedBufferChain.h"
 #include <assert.h>
 
-// Though it's tempting, it doesn't seem to be possible to implement
-// DeletedChain via the atomic exchange operation.  Specifically, a
-// pointer may be removed from the head of the chain, then the same
-// pointer reinserted in the chain, while another thread is waiting;
-// and that thread will not detect the change.  So instead, we always
-// use a mutex.
-
-#ifndef NDEBUG
-// In development mode, we define USE_DELETEDCHAINFLAG, which
-// triggers the piggyback of an additional word of data on every
-// allocated block, so we can ensure that an object is not
-// double-deleted and that the deleted chain remains intact.
-#define USE_DELETEDCHAINFLAG 1
-#endif // NDEBUG
-
-#ifdef USE_DELETEDCHAINFLAG
-enum DeletedChainFlag {
-  DCF_deleted = 0xfeedba0f,
-  DCF_alive = 0x12487654,
-};
-#endif
-
 ////////////////////////////////////////////////////////////////////
 //       Class : DeletedChain
 // Description : This template class can be used to provide faster
@@ -59,13 +32,10 @@ enum DeletedChainFlag {
 //               allocated that matches that type, the same space is
 //               just reused.
 //
-//               This is particularly important to do in the case of a
-//               multithreaded pipeline, so we minimize contention
-//               between the parallel threads.  If we didn't do this,
-//               the threads might compete overmuch on the system
-//               mutex protecting the malloc() call.  This way, there
-//               can be a different mutex for each type of object; or on
-//               some systems (e.g. i386), no mutex at all.
+//               This class is actually a layer on top of
+//               DeletedBufferChain, which handles the actual
+//               allocation.  This class just provides the
+//               typecasting.
 //
 //               Of course, this trick of maintaining the deleted
 //               object chain won't work in the presence of
@@ -88,31 +58,9 @@ public:
   static INLINE ReferenceCount *make_ref_ptr(ReferenceCount *ptr);
 
 private:
-  class ObjectNode {
-  public:
-#ifdef USE_DELETEDCHAINFLAG
-    // In development mode, we piggyback this extra data.  This is
-    // maintained out-of-band from the actual pointer returned, so we
-    // can safely use this flag to indicate the difference between
-    // allocated and freed pointers.
-    TVOLATILE PN_int32 _flag;
-#endif
+  INLINE void init_deleted_chain();
 
-    // This pointer sits in the same space referenced by the actual
-    // pointer returned (unlike _flag, above).  It's only used when
-    // the object is deleted, so there's no harm in sharing space with
-    // the undeleted object.
-    ObjectNode *_next;
-  };
-
-  static INLINE Type *node_to_type(ObjectNode *node);
-  static INLINE ObjectNode *type_to_node(Type *ptr);
-
-  ObjectNode *_deleted_chain;
-  
-  INLINE void init_lock();
-  void do_init_lock(MutexImpl *&lock);
-  MutexImpl *_lock;
+  DeletedBufferChain *_chain;
 };
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 0
dtool/src/dtoolbase/dtoolbase_composite1.cxx

@@ -3,6 +3,7 @@
 #include "atomicAdjustI386Impl.cxx"
 #include "atomicAdjustPosixImpl.cxx"
 #include "atomicAdjustWin32Impl.cxx"
+#include "deletedBufferChain.cxx"
 #include "dtoolbase.cxx"
 #include "memoryBase.cxx"
 #include "memoryHook.cxx"

+ 79 - 14
dtool/src/dtoolbase/memoryHook.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "memoryHook.h"
+#include "deletedBufferChain.h"
 
 #ifdef WIN32
 
@@ -40,19 +41,15 @@
 //
 // Memory manager: DLMALLOC
 //
-// This is Doug Lea's memory manager.  It is very fast,
-// but it is not thread-safe.
+// This is Doug Lea's memory manager.  It is very fast, but it is not
+// thread-safe.  However, we provide thread locking within MemoryHook.
 //
 /////////////////////////////////////////////////////////////////////
 
-#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
-#error Cannot use dlmalloc library with threading enabled!
-#endif
-
 #define USE_DL_PREFIX 1
 #define NO_MALLINFO 1
 #ifdef _DEBUG
-  #define DEBUG
+  #define DEBUG 1
 #endif
 #include "dlmalloc.h"
 #include "dlmalloc_src.cxx"
@@ -60,17 +57,18 @@
 #define call_malloc dlmalloc
 #define call_realloc dlrealloc
 #define call_free dlfree
+#define MEMORY_HOOK_MALLOC_LOCK 1
 
-#elif defined(USE_MEMORY_PTMALLOC2) && !defined(linux)
+#elif defined(USE_MEMORY_PTMALLOC2)
 // This doesn't appear to work in Linux; perhaps it is clashing with
-// the system library.  On Linux, fall through to the next case
-// instead.
+// the system library.  It also doesn't appear to be thread-safe on
+// OSX.
 
 /////////////////////////////////////////////////////////////////////
 //
 // Memory manager: PTMALLOC2
 //
-// Ptmalloc2 is a derivative of Doug Lea's memory manager that was 
+// Ptmalloc2 is a derivative of Doug Lea's memory manager that was
 // made thread-safe by Wolfram Gloger, then was ported to windows by
 // Niall Douglas.  It is not quite as fast as dlmalloc (because the
 // thread-safety constructs take a certain amount of CPU time), but
@@ -88,6 +86,7 @@
 #define call_malloc dlmalloc
 #define call_realloc dlrealloc
 #define call_free dlfree
+#undef MEMORY_HOOK_MALLOC_LOCK
 
 #else
 
@@ -103,6 +102,7 @@
 #define call_malloc malloc
 #define call_realloc realloc
 #define call_free free
+#undef MEMORY_HOOK_MALLOC_LOCK
 
 #endif  // USE_MEMORY_*
 
@@ -152,6 +152,10 @@ MemoryHook(const MemoryHook &copy) :
   _requested_heap_size = copy._requested_heap_size;
   _total_mmap_size = copy._total_mmap_size;
 #endif
+
+  ((MutexImpl &)copy._lock).lock();
+  _deleted_chains = copy._deleted_chains;
+  ((MutexImpl &)copy._lock).release();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -184,7 +188,14 @@ heap_alloc_single(size_t size) {
   AtomicAdjust::add(_total_heap_single_size, (PN_int32)size);
 #endif  // DO_MEMORY_USAGE
 
+#ifdef MEMORY_HOOK_MALLOC_LOCK
+  _lock.lock();
   void *alloc = call_malloc(inflate_size(size));
+  _lock.release();
+#else
+  void *alloc = call_malloc(inflate_size(size));
+#endif
+
   if (alloc == (void *)NULL) {
     cerr << "Out of memory!\n";
     abort();
@@ -209,7 +220,13 @@ heap_free_single(void *ptr) {
   AtomicAdjust::add(_total_heap_single_size, -(PN_int32)size);
 #endif  // DO_MEMORY_USAGE
 
+#ifdef MEMORY_HOOK_MALLOC_LOCK
+  _lock.lock();
   call_free(alloc);
+  _lock.release();
+#else
+  call_free(alloc);
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -232,7 +249,14 @@ heap_alloc_array(size_t size) {
   AtomicAdjust::add(_total_heap_array_size, (PN_int32)size);
 #endif  // DO_MEMORY_USAGE
 
+#ifdef MEMORY_HOOK_MALLOC_LOCK
+  _lock.lock();
   void *alloc = call_malloc(inflate_size(size));
+  _lock.release();
+#else
+  void *alloc = call_malloc(inflate_size(size));
+#endif
+
   if (alloc == (void *)NULL) {
     cerr << "Out of memory!\n";
     abort();
@@ -257,7 +281,14 @@ heap_realloc_array(void *ptr, size_t size) {
   AtomicAdjust::add(_total_heap_array_size, (PN_int32)size-(PN_int32)orig_size);
 #endif  // DO_MEMORY_USAGE
 
+#ifdef MEMORY_HOOK_MALLOC_LOCK
+  _lock.lock();
   alloc = call_realloc(alloc, inflate_size(size));
+  _lock.release();
+#else
+  alloc = call_realloc(alloc, inflate_size(size));
+#endif
+
   if (alloc == (void *)NULL) {
     cerr << "Out of memory!\n";
     abort();
@@ -282,7 +313,13 @@ heap_free_array(void *ptr) {
   AtomicAdjust::add(_total_heap_array_size, -(PN_int32)size);
 #endif  // DO_MEMORY_USAGE
 
+#ifdef MEMORY_HOOK_MALLOC_LOCK
+  _lock.lock();
   call_free(alloc);
+  _lock.release();
+#else
+  call_free(alloc);
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -303,13 +340,15 @@ bool MemoryHook::
 heap_trim(size_t pad) {
   bool trimmed = false;
 
-#if defined(USE_MEMORY_DLMALLOC) || (defined(USE_MEMORY_PTMALLOC2) && !defined(linux))
+#if defined(USE_MEMORY_DLMALLOC) || defined(USE_MEMORY_PTMALLOC2)
   // Since malloc_trim() isn't standard C, we can't be sure it exists
-  // on a given platform.  But if we're using ALTERNATIVE_MALLOC, we
-  // know we have dlmalloc_trim.
+  // on a given platform.  But if we're using dlmalloc, we know we
+  // have dlmalloc_trim.
+  _lock.lock();
   if (dlmalloc_trim(pad)) {
     trimmed = true;
   }
+  _lock.release();
 #endif
 
 #ifdef WIN32
@@ -424,3 +463,29 @@ mmap_free(void *ptr, size_t size) {
 void MemoryHook::
 mark_pointer(void *, size_t, ReferenceCount *) {
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: MemoryHook::get_deleted_chain
+//       Access: Public
+//  Description: Returns a pointer to a global DeletedBufferChain
+//               object suitable for allocating arrays of the
+//               indicated size.  There is one unique
+//               DeletedBufferChain object for every different size.
+////////////////////////////////////////////////////////////////////
+DeletedBufferChain *MemoryHook::
+get_deleted_chain(size_t buffer_size) {
+  DeletedBufferChain *chain;
+
+  _lock.lock();
+  DeletedChains::iterator dci = _deleted_chains.find(buffer_size);
+  if (dci != _deleted_chains.end()) {
+    chain = (*dci).second;
+  } else {
+    // Once allocated, this DeletedBufferChain object is never deleted.
+    chain = new DeletedBufferChain(buffer_size);
+    _deleted_chains.insert(DeletedChains::value_type(buffer_size, chain));
+  }
+  
+  _lock.release();
+  return chain;
+}

+ 14 - 2
dtool/src/dtoolbase/memoryHook.h

@@ -22,6 +22,10 @@
 #include "dtoolbase.h"
 #include "numeric_types.h"
 #include "atomicAdjust.h"
+#include "mutexImpl.h"
+#include <map>
+
+class DeletedBufferChain;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : MemoryHook
@@ -65,9 +69,9 @@ public:
 
   virtual void mark_pointer(void *ptr, size_t orig_size, ReferenceCount *ref_ptr);
 
-private:
-  size_t _page_size;
+  DeletedBufferChain *get_deleted_chain(size_t buffer_size);
 
+private:
   INLINE static size_t inflate_size(size_t size);
   INLINE static void *alloc_to_ptr(void *alloc, size_t size);
   INLINE static void *ptr_to_alloc(void *ptr, size_t &size);
@@ -79,6 +83,14 @@ protected:
   TVOLATILE PN_int32 _requested_heap_size;
   TVOLATILE PN_int32 _total_mmap_size;
 #endif
+
+private:
+  size_t _page_size;
+
+  typedef map<size_t, DeletedBufferChain *> DeletedChains;
+  DeletedChains _deleted_chains;
+
+  MutexImpl _lock;
 };
 
 #include "memoryHook.I"

+ 33 - 0
dtool/src/dtoolbase/stl_compares.I

@@ -50,6 +50,17 @@ operator () (const Key &a, const Key &b) const {
   return (a.compare_to(b) < 0);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: compare_to::is_equal
+//       Access: Public
+//  Description: Returns true if a is equivalent to b, false otherwise.
+////////////////////////////////////////////////////////////////////
+template<class Key>
+INLINE bool compare_to<Key>::
+is_equal(const Key &a, const Key &b) const {
+  return (a.compare_to(b) == 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: indirect_less::operator ()
 //       Access: Public
@@ -72,6 +83,17 @@ operator () (const Key &a, const Key &b) const {
   return (a != b && (*a).compare_to(*b) < 0);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: indirect_compare_to::is_equal
+//       Access: Public
+//  Description: Returns true if a is equivalent to b, false otherwise.
+////////////////////////////////////////////////////////////////////
+template<class Key>
+INLINE bool indirect_compare_to<Key>::
+is_equal(const Key &a, const Key &b) const {
+  return (a == b || (*a).compare_to(*b) == 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: indirect_compare_names::operator ()
 //       Access: Public
@@ -83,6 +105,17 @@ operator () (const Key &a, const Key &b) const {
   return (a != b && (*a).get_name() < (*b).get_name());
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: indirect_compare_names::is_equal
+//       Access: Public
+//  Description: Returns true if a is equivalent to b, false otherwise.
+////////////////////////////////////////////////////////////////////
+template<class Key>
+INLINE bool indirect_compare_names<Key>::
+is_equal(const Key &a, const Key &b) const {
+  return (a == b || (*a).get_name() == (*b).get_name());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: integer_hash::add_hash
 //       Access: Public, Static

+ 22 - 4
dtool/src/dtoolbase/stl_compares.h

@@ -29,18 +29,33 @@
 #ifdef HAVE_STL_HASH
 #include <hash_map>  // for hash_compare
 
-#define stl_hash_compare stdext::hash_compare
+template<class Key, class Compare = less<Key> >
+class stl_hash_compare : public stdext::hash_compare<Key, Compare> {
+public:
+  INLINE bool is_equal(const Key &a, const Key &b) const {
+    return !operator()(a, b) && !operator()(b, a);
+  }
+};
+
 
 #else
 
 #include <map>  // for less
 
 // This is declared for the cases in which we don't have STL_HASH
-// available--it's just a name to inherit from, but there's no need to
-// provide much functionality in the base class (since it won't
-// actually be used for hashing, just for comparing).
+// available.
 template<class Key, class Compare = less<Key> >
 class stl_hash_compare : public Compare {
+public:
+  INLINE size_t operator () (const Key &key) const {
+    return (size_t)key;
+  }
+  INLINE bool operator () (const Key &a, const Key &b) const {
+    return Compare::operator ()(a, b);
+  }
+  INLINE bool is_equal(const Key &a, const Key &b) const {
+    return !operator()(a, b) && !operator()(b, a);
+  }
 };
 
 #endif  // HAVE_STL_HASH
@@ -69,6 +84,7 @@ template<class Key>
 class compare_to {
 public:
   INLINE bool operator () (const Key &a, const Key &b) const;
+  INLINE bool is_equal(const Key &a, const Key &b) const;
 };
 
 ////////////////////////////////////////////////////////////////////
@@ -95,6 +111,7 @@ template<class Key>
 class indirect_compare_to {
 public:
   INLINE bool operator () (const Key &a, const Key &b) const;
+  INLINE bool is_equal(const Key &a, const Key &b) const;
 };
 
 ////////////////////////////////////////////////////////////////////
@@ -110,6 +127,7 @@ template<class Key>
 class indirect_compare_names {
 public:
   INLINE bool operator () (const Key &a, const Key &b) const;
+  INLINE bool is_equal(const Key &a, const Key &b) const;
 };
 
 ////////////////////////////////////////////////////////////////////