On Tue, Apr 29, 2014 at 02:58:11PM +0200, Richard Biener wrote: > On Tue, Apr 29, 2014 at 1:08 PM, <tsaund...@mozilla.com> wrote: > > From: Trevor Saunders <tsaund...@mozilla.com> > > > > Hi, > > > > This implements finalizers by keeping a list of registered finalizers > > and after every mark but before sweeping check to see if any of them are > > for unmarked blocks. > > > > bootstrapped + regtested on x86_64-unknown-linux-gnu, ok? > > > > Trev > > > > gcc/ChangeLog: > > > > * ggc-common.c (ggc_internal_cleared_alloc): Adjust. > > * ggc-none.c (ggc_internal_alloc): Assert if a finalizer is passed. > > (ggc_internal_cleared_alloc): Likewise. > > * ggc-page.c (finalizer): New class. > > (globals::finalizer_lists): New member. > > (ggc_internal_alloc): Record the finalizer if any for the block > > being > > allocated. > > (ggc_handle_finalizers): New function. > > (ggc_collect): Call ggc_handle_finalizers. > > * ggc.h (ggc_internal_alloc):Add arguments to allow installing a > > finalizer. > > (ggc_internal_cleared_alloc): Likewise. > > (finalize): New function. > > (need_finalization_p): Likewise. > > (ggc_alloc): Install the type's destructor as the finalizer if it > > might do something. > > (ggc_cleared_alloc): Likewise. > > (ggc_vec_alloc): Likewise. > > (ggc_cleared_vec_alloc): Likewise. > > --- > > gcc/ggc-common.c | 5 +++-- > > gcc/ggc-none.c | 8 ++++++-- > > gcc/ggc-page.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- > > gcc/ggc.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++------ > > 4 files changed, 104 insertions(+), 11 deletions(-) > > > > diff --git a/gcc/ggc-common.c b/gcc/ggc-common.c > > index e89cc64..b11a10c 100644 > > --- a/gcc/ggc-common.c > > +++ b/gcc/ggc-common.c > > @@ -174,9 +174,10 @@ ggc_mark_roots (void) > > > > /* Allocate a block of memory, then clear it. */ > > void * > > -ggc_internal_cleared_alloc (size_t size MEM_STAT_DECL) > > +ggc_internal_cleared_alloc (size_t size, void (*f)(void *), size_t s, > > size_t n > > + MEM_STAT_DECL) > > { > > - void *buf = ggc_internal_alloc (size PASS_MEM_STAT); > > + void *buf = ggc_internal_alloc (size, f, s, n PASS_MEM_STAT); > > memset (buf, 0, size); > > return buf; > > } > > diff --git a/gcc/ggc-none.c b/gcc/ggc-none.c > > index aad89bf..97d3566 100644 > > --- a/gcc/ggc-none.c > > +++ b/gcc/ggc-none.c > > @@ -41,14 +41,18 @@ ggc_round_alloc_size (size_t requested_size) > > } > > > > void * > > -ggc_internal_alloc (size_t size MEM_STAT_DECL) > > +ggc_internal_alloc (size_t size, void (*f)(void *), size_t, size_t > > + MEM_STAT_DECL) > > { > > + gcc_assert (!f); // ggc-none doesn't support finalizers > > return xmalloc (size); > > } > > > > void * > > -ggc_internal_cleared_alloc (size_t size MEM_STAT_DECL) > > +ggc_internal_cleared_alloc (size_t size, void (*f)(void *), size_t, size_t > > + MEM_STAT_DECL) > > { > > + gcc_assert (!f); // ggc-none doesn't support finalizers > > return xcalloc (size, 1); > > } > > > > diff --git a/gcc/ggc-page.c b/gcc/ggc-page.c > > index ae5e88a..208b526 100644 > > --- a/gcc/ggc-page.c > > +++ b/gcc/ggc-page.c > > @@ -332,6 +332,27 @@ typedef struct page_table_chain > > > > #endif > > > > +class finalizer > > +{ > > +public: > > + finalizer (uintptr_t addr, void (*f)(void *), size_t s, size_t n) : > > + m_addr (addr), m_function (f), m_object_size (s), m_n_objects (n) {} > > + > > + void call () const > > + { > > + for (size_t i = 0; i < m_n_objects; i++) > > + m_function (reinterpret_cast<void *> (m_addr + (i * > > m_object_size))); > > + } > > + > > + uintptr_t addr () const { return m_addr; } > > + > > +private: > > + uintptr_t m_addr; > > + void (*m_function)(void *); > > + size_t m_object_size; > > + size_t m_n_objects; > > +}; > > + > > #ifdef ENABLE_GC_ALWAYS_COLLECT > > /* List of free objects to be verified as actually free on the > > next collection. */ > > @@ -425,6 +446,9 @@ static struct globals > > better runtime data access pattern. */ > > unsigned long **save_in_use; > > > > + /* finalizer_lists[i] is the list of finalizers for blocks of order i. > > */ > > + vec<finalizer> finalizer_lists[NUM_ORDERS]; > > + > > #ifdef ENABLE_GC_ALWAYS_COLLECT > > /* List of free objects to be verified as actually free on the > > next collection. */ > > @@ -1202,7 +1226,8 @@ ggc_round_alloc_size (size_t requested_size) > > /* Allocate a chunk of memory of SIZE bytes. Its contents are undefined. > > */ > > > > void * > > -ggc_internal_alloc (size_t size MEM_STAT_DECL) > > +ggc_internal_alloc (size_t size, void (*f)(void *), size_t s, size_t n > > + MEM_STAT_DECL) > > { > > size_t order, word, bit, object_offset, object_size; > > struct page_entry *entry; > > @@ -1345,6 +1370,10 @@ ggc_internal_alloc (size_t size MEM_STAT_DECL) > > /* For timevar statistics. */ > > timevar_ggc_mem_total += object_size; > > > > + if (f) > > + G.finalizer_lists[order] > > + .safe_push (finalizer (reinterpret_cast<uintptr_t> (result), f, s, > > n)); > > + > > if (GATHER_STATISTICS) > > { > > size_t overhead = object_size - size; > > @@ -1811,6 +1840,24 @@ clear_marks (void) > > } > > } > > > > +static void > > +ggc_handle_finalizers () > > +{ > > + for (unsigned int order = 2; order < NUM_ORDERS; order++) > > + { > > + unsigned int length = G.finalizer_lists[order].length (); > > + for (unsigned int i = length - 1; i < length; i--) > > I suppose you are walking backwards here to handle dependences > properly.
I only did it because it seemed epsilon easier than iterating forward and dealing with removals from the list. I suppose dependancies between objects could be a reason to do it backwards, but I tend to think getting dependancies of that sort right is impossible and you shouldn't rely on any given ordering. So just write itempotent dtors and all's good ;) > But it doesn't address dependencies across different allocation > orders (what's the reason to even have one vector per order?). I think I did it for locality, but otherwise I don't think there's a great reason other than copying what other stuff does with orders. > > + { > > + finalizer &f = G.finalizer_lists[order][i]; > > + if (!ggc_marked_p (reinterpret_cast<void *> (f.addr ()))) > > + { > > + f.call (); > > + G.finalizer_lists[order].ordered_remove (i); > > But this clearly will be an efficiency issue. Can you delay > removing the element from the list and instead do it in > a second forward run over all finalizers of that order (if there > were any finalizers run in the first)? Or if we agree to not care about order we can just use the non ordered remove. > I suppose that the overall complexity of walking the finalizers isn't > that bad as we walked all reachable ggc memory anyway. yeah, its bad but not as bad as gc itself is my thought. > So the only issue would be memory inefficiencies for a lot > of small individual objects that need finalization. Thus, > > > +private: > > + uintptr_t m_addr; > > + void (*m_function)(void *); > > + size_t m_object_size; > > + size_t m_n_objects; > > could be improved by noting that m_function and m_object_size > are somewhat redundant (and m_object_size and m_n_objects > are unused for non-vectors). So, instead of m_function and > m_object_size you could store an index into a global vector > of m_function/m_object_size pairs (eventually even statically > compute it by gengtype.c to avoid some kind of lookup > at allocation time). deciding if a type needs finalization would mean teaching gengtype about a lot of C++ :/ Well actually maybe not, if we don't maybe we could just have gengtype emit a blob of C++ that does it if we're really clever, but that would take some thought. > As finalization order is currently broken (see above) you could > also have two vectors of finalizers, one for the vector case > and one for the non-vector case. that certainly seems doable. > An optimization would also be to look at the last added > finalizer and see if the current object is adjacent to it > and only adjust m_n_objects. (not sure if that would trigger > often) What happens if they aren't freed at the same time though? > I suppose given we don't have many uses of finalizers (yet) > this all isn't much of an issue. Still please eventually > reduce to a single finalizer vector and avoid the ordered remove. both of those certainly seem doable. thanks! Trev > > Thanks, > Richard. > > > + } > > + } > > + } > > +} > > + > > /* Free all empty pages. Partially empty pages need no attention > > because the `mark' bit doubles as an `unused' bit. */ > > > > @@ -2075,6 +2122,7 @@ ggc_collect (void) > > > > clear_marks (); > > ggc_mark_roots (); > > + ggc_handle_finalizers (); > > > > if (GATHER_STATISTICS) > > ggc_prune_overhead_list (); > > diff --git a/gcc/ggc.h b/gcc/ggc.h > > index 50fb199..4c59597 100644 > > --- a/gcc/ggc.h > > +++ b/gcc/ggc.h > > @@ -136,13 +136,16 @@ extern void gt_pch_save (FILE *f); > > /* Allocation. */ > > > > /* The internal primitive. */ > > -extern void *ggc_internal_alloc (size_t CXX_MEM_STAT_INFO) > > ATTRIBUTE_MALLOC; > > +extern void *ggc_internal_alloc (size_t, void (*)(void *) = NULL, size_t = > > 0, > > + size_t = 1 CXX_MEM_STAT_INFO) > > + ATTRIBUTE_MALLOC; > > > > extern size_t ggc_round_alloc_size (size_t requested_size); > > > > /* Allocates cleared memory. */ > > -extern void *ggc_internal_cleared_alloc (size_t CXX_MEM_STAT_INFO) > > - ATTRIBUTE_MALLOC; > > +extern void *ggc_internal_cleared_alloc (size_t, void (*)(void *) = NULL, > > + size_t = 0, size_t = 1 > > + CXX_MEM_STAT_INFO) > > ATTRIBUTE_MALLOC; > > > > /* Resize a block. */ > > extern void *ggc_realloc (void *, size_t CXX_MEM_STAT_INFO); > > @@ -157,24 +160,55 @@ extern void dump_ggc_loc_statistics (bool); > > ((T *) ggc_realloc ((P), (N) * sizeof (T) MEM_STAT_INFO)) > > > > template<typename T> > > +void > > +finalize (void *p) > > +{ > > + static_cast<T *> (p)->~T (); > > +} > > + > > +template<typename T> > > +static inline bool > > +need_finalization_p () > > +{ > > +#if GCC_VERSION >= 4003 > > + return !__has_trivial_destructor (T); > > +#else > > + return true; > > +#endif > > +} > > + > > +template<typename T> > > static inline T * > > ggc_alloc (ALONE_CXX_MEM_STAT_INFO) > > { > > - return static_cast<T *> (ggc_internal_alloc (sizeof (T) PASS_MEM_STAT)); > > + if (need_finalization_p<T> ()) > > + return static_cast<T *> (ggc_internal_alloc (sizeof (T), finalize<T> > > + PASS_MEM_STAT)); > > + else > > + return static_cast<T *> (ggc_internal_alloc (sizeof (T) > > PASS_MEM_STAT)); > > } > > > > template<typename T> > > static inline T * > > ggc_cleared_alloc (ALONE_CXX_MEM_STAT_INFO) > > { > > - return static_cast<T *> (ggc_internal_cleared_alloc (sizeof (T) > > - PASS_MEM_STAT)); > > + if (need_finalization_p<T> ()) > > + return static_cast<T *> (ggc_internal_cleared_alloc (sizeof (T), > > + finalize<T> > > + PASS_MEM_STAT)); > > + else > > + return static_cast<T *> (ggc_internal_cleared_alloc (sizeof (T) > > + PASS_MEM_STAT)); > > } > > > > template<typename T> > > static inline T * > > ggc_vec_alloc (size_t c CXX_MEM_STAT_INFO) > > { > > + if (need_finalization_p<T> ()) > > + return static_cast<T *> (ggc_internal_alloc (c * sizeof (T), > > finalize<T>, > > + sizeof (T), c > > PASS_MEM_STAT)); > > + else > > return static_cast<T *> (ggc_internal_alloc (c * sizeof (T) > > PASS_MEM_STAT)); > > } > > @@ -183,6 +217,12 @@ template<typename T> > > static inline T * > > ggc_cleared_vec_alloc (size_t c CXX_MEM_STAT_INFO) > > { > > + if (need_finalization_p<T> ()) > > + return static_cast<T *> (ggc_internal_cleared_alloc (c * sizeof (T), > > + finalize<T>, > > + sizeof (T), c > > + PASS_MEM_STAT)); > > + else > > return static_cast<T *> (ggc_internal_cleared_alloc (c * sizeof (T) > > PASS_MEM_STAT)); > > } > > -- > > 2.0.0.rc0 > >
signature.asc
Description: Digital signature