[svn:parrot] r49345 - branches/gc_ms2_tuning/src/gc

nwellnhof at svn.parrot.org nwellnhof at svn.parrot.org
Sun Sep 26 17:18:51 UTC 2010


Author: nwellnhof
Date: Sun Sep 26 17:18:51 2010
New Revision: 49345
URL: https://trac.parrot.org/parrot/changeset/49345

Log:
[gc] Mark stack

Do away with all the double linked lists in GC MS2. Use a simple array
as GC mark stack and iterate sequentially over the memory pools like
in GC MS1. This is faster and saves a ton of memory.

Modified:
   branches/gc_ms2_tuning/src/gc/fixed_allocator.c
   branches/gc_ms2_tuning/src/gc/fixed_allocator.h
   branches/gc_ms2_tuning/src/gc/gc_ms2.c

Modified: branches/gc_ms2_tuning/src/gc/fixed_allocator.c
==============================================================================
--- branches/gc_ms2_tuning/src/gc/fixed_allocator.c	Sun Sep 26 17:18:29 2010	(r49344)
+++ branches/gc_ms2_tuning/src/gc/fixed_allocator.c	Sun Sep 26 17:18:51 2010	(r49345)
@@ -143,10 +143,14 @@
     ASSERT_ARGS(Parrot_gc_fixed_allocator_allocate)
 
     /* We always align size to 4/8 bytes. */
-    const size_t index = (size - 1) / sizeof (void *);
+    size_t  index = (size - 1) / sizeof (void *);
     void   *ret;
     PARROT_ASSERT(size);
 
+    /* Hack: allocate at least two words for Pool_Allocator_Free_List */
+    if (index == 0)
+        index = 1;
+
     if (index >= allocator->num_pools) {
         size_t new_size = index + 1;
         /* (re)allocate pools */
@@ -184,6 +188,10 @@
     /* We always align size to 4/8 bytes. */
     size_t index = (size - 1) / sizeof (void*);
 
+    /* Hack: allocate at least two words for Pool_Allocator_Free_List */
+    if (index == 0)
+        index = 1;
+
     PARROT_ASSERT(allocator->pools[index]);
 
     pool_free(interp, allocator->pools[index], data);

Modified: branches/gc_ms2_tuning/src/gc/fixed_allocator.h
==============================================================================
--- branches/gc_ms2_tuning/src/gc/fixed_allocator.h	Sun Sep 26 17:18:29 2010	(r49344)
+++ branches/gc_ms2_tuning/src/gc/fixed_allocator.h	Sun Sep 26 17:18:51 2010	(r49345)
@@ -25,6 +25,8 @@
    enough to satisfy most startup costs. */
 
 typedef struct Pool_Allocator_Free_List {
+    /* We have to leave the PObj_on_free_list flag untouched */
+    Parrot_UInt flags;
     struct Pool_Allocator_Free_List * next;
 } Pool_Allocator_Free_List;
 

Modified: branches/gc_ms2_tuning/src/gc/gc_ms2.c
==============================================================================
--- branches/gc_ms2_tuning/src/gc/gc_ms2.c	Sun Sep 26 17:18:29 2010	(r49344)
+++ branches/gc_ms2_tuning/src/gc/gc_ms2.c	Sun Sep 26 17:18:51 2010	(r49345)
@@ -24,18 +24,18 @@
 typedef struct MarkSweep_GC {
     /* Allocator for PMC headers */
     struct Pool_Allocator *pmc_allocator;
-    /* Currently allocate objects */
-    struct Linked_List    *objects;
-    /* During M&S gather new live objects in this list */
-    struct Linked_List    *new_objects;
 
     /* Allocator for strings */
     struct Pool_Allocator *string_allocator;
-    struct Linked_List    *strings;
 
     /* Fixed-size allocator */
     struct Fixed_Allocator *fixed_size_allocator;
 
+    /* Mark stack */
+    PMC                   **mark_stack;
+    size_t                  mark_stack_pos;
+    size_t                  mark_stack_size;
+
     /* String GC */
     struct String_GC        string_gc;
 
@@ -116,16 +116,6 @@
 static void gc_ms2_compact_memory_pool(PARROT_INTERP)
         __attribute__nonnull__(1);
 
-static size_t gc_ms2_count_used_pmc_memory(PARROT_INTERP,
-    ARGIN(Linked_List *list))
-        __attribute__nonnull__(1)
-        __attribute__nonnull__(2);
-
-static size_t gc_ms2_count_used_string_memory(PARROT_INTERP,
-    ARGIN(Linked_List *list))
-        __attribute__nonnull__(1)
-        __attribute__nonnull__(2);
-
 static void gc_ms2_finalize(PARROT_INTERP)
         __attribute__nonnull__(1);
 
@@ -167,11 +157,9 @@
 
 static int gc_ms2_is_ptr_owned(PARROT_INTERP,
     ARGIN_NULLOK(void *ptr),
-    ARGIN(Pool_Allocator *pool),
-    ARGIN(Linked_List *list))
+    ARGIN(Pool_Allocator *pool))
         __attribute__nonnull__(1)
-        __attribute__nonnull__(3)
-        __attribute__nonnull__(4);
+        __attribute__nonnull__(3);
 
 static int gc_ms2_is_string_ptr(PARROT_INTERP, ARGIN_NULLOK(void *ptr))
         __attribute__nonnull__(1);
@@ -228,12 +216,10 @@
 
 static void gc_ms2_sweep_pool(PARROT_INTERP,
     ARGIN(Pool_Allocator *pool),
-    ARGIN(Linked_List *list),
     ARGIN(sweep_cb callback))
         __attribute__nonnull__(1)
         __attribute__nonnull__(2)
-        __attribute__nonnull__(3)
-        __attribute__nonnull__(4);
+        __attribute__nonnull__(3);
 
 static void gc_ms2_sweep_string_cb(PARROT_INTERP, ARGIN(PObj *obj))
         __attribute__nonnull__(1)
@@ -276,13 +262,6 @@
        PARROT_ASSERT_ARG(interp))
 #define ASSERT_ARGS_gc_ms2_compact_memory_pool __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp))
-#define ASSERT_ARGS_gc_ms2_count_used_pmc_memory __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
-       PARROT_ASSERT_ARG(interp) \
-    , PARROT_ASSERT_ARG(list))
-#define ASSERT_ARGS_gc_ms2_count_used_string_memory \
-     __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
-       PARROT_ASSERT_ARG(interp) \
-    , PARROT_ASSERT_ARG(list))
 #define ASSERT_ARGS_gc_ms2_finalize __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp))
 #define ASSERT_ARGS_gc_ms2_free_buffer_header __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
@@ -309,8 +288,7 @@
        PARROT_ASSERT_ARG(interp))
 #define ASSERT_ARGS_gc_ms2_is_ptr_owned __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp) \
-    , PARROT_ASSERT_ARG(pool) \
-    , PARROT_ASSERT_ARG(list))
+    , PARROT_ASSERT_ARG(pool))
 #define ASSERT_ARGS_gc_ms2_is_string_ptr __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp))
 #define ASSERT_ARGS_gc_ms2_iterate_live_strings __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
@@ -344,7 +322,6 @@
 #define ASSERT_ARGS_gc_ms2_sweep_pool __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp) \
     , PARROT_ASSERT_ARG(pool) \
-    , PARROT_ASSERT_ARG(list) \
     , PARROT_ASSERT_ARG(callback))
 #define ASSERT_ARGS_gc_ms2_sweep_string_cb __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
        PARROT_ASSERT_ARG(interp) \
@@ -602,13 +579,9 @@
     else {
         self = mem_allocate_zeroed_typed(MarkSweep_GC);
 
-        self->pmc_allocator = Parrot_gc_pool_new(interp,
-            sizeof (List_Item_Header) + sizeof (PMC));
-        self->objects = Parrot_list_new(interp);
-
-        self->string_allocator = Parrot_gc_pool_new(interp,
-            sizeof (List_Item_Header) + sizeof (STRING));
-        self->strings = Parrot_list_new(interp);
+        self->pmc_allocator = Parrot_gc_pool_new(interp, sizeof (PMC));
+
+        self->string_allocator = Parrot_gc_pool_new(interp, sizeof (STRING));
 
         self->fixed_size_allocator = Parrot_gc_fixed_allocator_new(interp);
     }
@@ -632,8 +605,6 @@
 
     Parrot_gc_str_finalize(interp, &self->string_gc);
 
-    Parrot_list_destroy(interp, self->objects);
-    Parrot_list_destroy(interp, self->strings);
     Parrot_gc_pool_destroy(interp, self->pmc_allocator);
     Parrot_gc_pool_destroy(interp, self->string_allocator);
     Parrot_gc_fixed_allocator_destroy(interp, self->fixed_size_allocator);
@@ -646,19 +617,12 @@
 {
     ASSERT_ARGS(gc_ms2_allocate_pmc_header)
     MarkSweep_GC      *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header *ptr;
     PMC              *ret;
 
     if (!(flags & PObj_constant_FLAG))
         interp->gc_sys->stats.memory_used += sizeof (PMC);
 
-    ptr = (List_Item_Header *)Parrot_gc_pool_allocate(interp,
-            self->pmc_allocator);
-    LIST_APPEND(self->objects, ptr);
-
-    ret = LLH2Obj_typed(ptr, PMC);
-
-    return ret;
+    return (PMC *)Parrot_gc_pool_allocate(interp, self->pmc_allocator);
 }
 
 static void
@@ -669,12 +633,11 @@
     if (pmc) {
         if (PObj_on_free_list_TEST(pmc))
             return;
-        Parrot_list_remove(interp, self->objects, Obj2LLH(pmc));
         PObj_on_free_list_SET(pmc);
 
         Parrot_pmc_destroy(interp, pmc);
 
-        Parrot_gc_pool_free(interp, self->pmc_allocator, Obj2LLH(pmc));
+        Parrot_gc_pool_free(interp, self->pmc_allocator, pmc);
 
         if (!PObj_constant_TEST(pmc))
             interp->gc_sys->stats.memory_used -= sizeof (PMC);
@@ -696,7 +659,6 @@
 {
     ASSERT_ARGS(gc_ms2_mark_pmc_header)
     MarkSweep_GC      *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header  *item = Obj2LLH(pmc);
 
     /* Object was already marked as grey. Or live. Or dead. Skip it */
     if (PObj_is_live_or_free_TESTALL(pmc) || PObj_constant_TEST(pmc))
@@ -705,9 +667,28 @@
     /* mark it live */
     PObj_live_SET(pmc);
 
-    LIST_REMOVE(self->objects, item);
-    LIST_APPEND(self->new_objects, item);
+    if (self->mark_stack_pos >= self->mark_stack_size) {
+        /* No space on mark stack */
+        size_t new_size;
+
+        if (self->mark_stack_size == 0) {
+            /* Initial allocation */
+            new_size = 1024;
+            self->mark_stack = (PMC **)mem_sys_allocate(
+                    new_size * sizeof (PMC *));
+        }
+        else {
+            /* Reallocate double the previous size */
+            new_size = self->mark_stack_size * 2;
+            self->mark_stack = (PMC **)mem_sys_realloc(self->mark_stack,
+                    new_size * sizeof (PMC *));
+        }
+
+        self->mark_stack_size = new_size;
+    }
 
+    /* Push PMC onto mark stack */
+    self->mark_stack[self->mark_stack_pos++] = pmc;
 }
 
 /*
@@ -725,7 +706,7 @@
 {
     ASSERT_ARGS(gc_ms2_is_pmc_ptr)
     MarkSweep_GC      *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    return gc_ms2_is_ptr_owned(interp, ptr, self->pmc_allocator, self->objects);
+    return gc_ms2_is_ptr_owned(interp, ptr, self->pmc_allocator);
 }
 
 /*
@@ -769,17 +750,13 @@
 {
     ASSERT_ARGS(gc_ms2_allocate_string_header)
     MarkSweep_GC     *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header *ptr;
     STRING           *ret;
 
     if (!(flags & PObj_constant_FLAG))
         interp->gc_sys->stats.memory_used += sizeof (STRING);
 
-    ptr = (List_Item_Header *)Parrot_gc_pool_allocate(interp,
-            self->string_allocator);
-    LIST_APPEND(self->strings, ptr);
+    ret = (STRING *)Parrot_gc_pool_allocate(interp, self->string_allocator);
 
-    ret = LLH2Obj_typed(ptr, STRING);
     memset(ret, 0, sizeof (STRING));
     return ret;
 }
@@ -792,13 +769,12 @@
     if (s) {
         if (PObj_on_free_list_TEST(s))
             return;
-        Parrot_list_remove(interp, self->strings, Obj2LLH(s));
         PObj_on_free_list_SET(s);
 
-        if (Buffer_bufstart(s) && !PObj_external_TEST(s))
+        if (Buffer_bufstart(s) && !PObj_external_TEST(s) && !PObj_sysmem_TEST(s))
             Parrot_gc_str_free_buffer_storage(interp, &self->string_gc, (Buffer *)s);
 
-        Parrot_gc_pool_free(interp, self->string_allocator, Obj2LLH(s));
+        Parrot_gc_pool_free(interp, self->string_allocator, s);
 
         if (!PObj_constant_TEST(s))
             interp->gc_sys->stats.memory_used -= sizeof (STRING);
@@ -836,7 +812,7 @@
 {
     ASSERT_ARGS(gc_ms2_is_string_ptr)
     MarkSweep_GC      *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    return gc_ms2_is_ptr_owned(interp, ptr, self->string_allocator, self->strings);
+    return gc_ms2_is_ptr_owned(interp, ptr, self->string_allocator);
 }
 
 /*
@@ -929,7 +905,7 @@
     MarkSweep_GC *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
     Buffer       *str  = (Buffer *)obj;
     /* Compact string pool here. Or get rid of "shared buffers" and just free storage */
-    if (Buffer_bufstart(str) && !PObj_external_TEST(str))
+    if (Buffer_bufstart(str) && !PObj_external_TEST(str) && !PObj_sysmem_TEST(str))
         Parrot_gc_str_free_buffer_storage(interp, &self->string_gc, str);
     interp->gc_sys->stats.memory_used -= sizeof (STRING);
 }
@@ -951,14 +927,22 @@
         ARGIN_NULLOK(void *data))
 {
     ASSERT_ARGS(gc_ms2_iterate_live_strings)
+    MarkSweep_GC         *self  = (MarkSweep_GC *)interp->gc_sys->gc_private;
+    Pool_Allocator       *pool  = self->string_allocator;
+    Pool_Allocator_Arena *arena = pool->top_arena;
 
-    MarkSweep_GC *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header *tmp = self->strings->first;
+    for (arena = pool->top_arena; arena; arena = arena->next) {
+        char   *ptr = (char *)(arena + 1);
+        size_t  i;
+
+        for (i = 0; i < pool->objects_per_alloc; ++i) {
+            Buffer *str = (Buffer *)ptr;
 
-    while (tmp) {
-        Buffer *b = LLH2Obj_typed(tmp, Buffer);
-        callback(interp, b, data);
-        tmp = tmp->next;
+            if (str->flags && !PObj_on_free_list_TEST(str))
+                callback(interp, str, data);
+
+            ptr += pool->object_size;
+        }
     }
 }
 
@@ -968,8 +952,6 @@
 {
     ASSERT_ARGS(gc_ms2_mark_and_sweep)
     MarkSweep_GC      *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header *tmp;
-    Linked_List      *list;
     GC_Statistics    *stats;
     size_t            counter;
     UNUSED(flags);
@@ -985,9 +967,6 @@
 
     ++self->gc_mark_block_level;
 
-    /* Allocate list for gray objects */
-    self->new_objects = Parrot_list_new(interp);
-
     /* Trace "roots" into new_objects */
     gc_ms2_mark_pmc_header(interp, PMCNULL);
 
@@ -996,37 +975,25 @@
         Parrot_gc_trace_root(interp->pdb->debugger, NULL, (Parrot_gc_trace_type)0);
     }
 
-    /* new_objects are "gray" untill fully marked */
-    /* Additional gray objects will appened to new_objects list */
-    /* So, iterate over them in one go */
-    tmp = self->new_objects->first;
-    while (tmp) {
-        PMC *pmc = LLH2Obj_typed(tmp, PMC);
-        /* if object is a PMC and contains buffers or PMCs, then attach the PMC
-         * to the chained mark list. */
-        if (PObj_is_special_PMC_TEST(pmc)) {
-            if (PObj_custom_mark_TEST(pmc))
-                VTABLE_mark(interp, pmc);
-        }
+    /* New objects are "gray" until fully marked. The gray objects will be
+       put on the mark stack. So, iterate until the mark stack is empty. */
+    while (self->mark_stack_pos > 0) {
+        PMC *pmc = self->mark_stack[--self->mark_stack_pos];
+
+        /* If object is a PMC and contains buffers or PMCs, then put these
+           objects on the mark stack. This can make mark_stack_pos advance
+           again. */
+        if (PObj_custom_mark_TEST(pmc))
+            VTABLE_mark(interp, pmc);
 
         if (PMC_metadata(pmc))
             Parrot_gc_mark_PMC_alive(interp, PMC_metadata(pmc));
-
-        tmp = tmp->next;
     }
 
-    /* At this point of time new_objects contains only live PMCs */
-    /* objects contains "dead" or "constant" PMCs */
-    /* sweep of new_objects will repaint them white */
-    /* sweep of objects will destroy dead objects leaving only "constant" */
-    gc_ms2_sweep_pool(interp, self->pmc_allocator, self->new_objects, gc_ms2_sweep_pmc_cb);
-    gc_ms2_sweep_pool(interp, self->pmc_allocator, self->objects, gc_ms2_sweep_pmc_cb);
-    gc_ms2_sweep_pool(interp, self->string_allocator, self->strings, gc_ms2_sweep_string_cb);
-
-    /* Replace objects with new_objects. Ignoring "constant" one */
-    list = self->objects;
-    self->objects = self->new_objects;
-    Parrot_list_destroy(interp, list);
+    /* Now, all live objects are marked. Walk through the pools, sweep all
+       dead objects and clear the live flag of live objects. */
+    gc_ms2_sweep_pool(interp, self->pmc_allocator, gc_ms2_sweep_pmc_cb);
+    gc_ms2_sweep_pool(interp, self->string_allocator, gc_ms2_sweep_string_cb);
 
     gc_ms2_compact_memory_pool(interp);
 
@@ -1043,7 +1010,7 @@
 
 /*
 =item C<static void gc_ms2_sweep_pool(PARROT_INTERP, Pool_Allocator *pool,
-Linked_List *list, sweep_cb callback)>
+sweep_cb callback)>
 
 Helper function to sweep pool.
 
@@ -1052,27 +1019,32 @@
 static void
 gc_ms2_sweep_pool(PARROT_INTERP,
         ARGIN(Pool_Allocator *pool),
-        ARGIN(Linked_List *list),
         ARGIN(sweep_cb callback))
 {
     ASSERT_ARGS(gc_ms2_sweep_pool)
-    List_Item_Header *tmp = list->first;
-    while (tmp) {
-        List_Item_Header *next = tmp->next;
-        PObj             *obj  = LLH2Obj_typed(tmp, PObj);
-        if (PObj_live_TEST(obj)) {
-            /* Paint live objects white */
-            PObj_live_CLEAR(obj);
-        }
-        else if (!PObj_constant_TEST(obj)) {
-            PObj_on_free_list_SET(obj);
-            LIST_REMOVE(list, tmp);
+    Pool_Allocator_Arena *arena = pool->top_arena;
+
+    for (arena = pool->top_arena; arena; arena = arena->next) {
+        char   *ptr = (char *)(arena + 1);
+        size_t  i;
+
+        for (i = 0; i < pool->objects_per_alloc; ++i) {
+            PObj *obj = (PObj *)ptr;
+
+            if (PObj_live_TEST(obj)) {
+                /* Paint live objects white */
+                PObj_live_CLEAR(obj);
+            }
+            else if (obj->flags && !PObj_on_free_list_TEST(obj) && !PObj_constant_TEST(obj)) {
+                PObj_on_free_list_SET(obj);
 
-            callback(interp, obj);
+                callback(interp, obj);
 
-            Parrot_gc_pool_free(interp, pool, tmp);
+                Parrot_gc_pool_free(interp, pool, obj);
+            }
+
+            ptr += pool->object_size;
         }
-        tmp = next;
     }
 }
 
@@ -1080,7 +1052,7 @@
 
 /*
 =item C<static int gc_ms2_is_ptr_owned(PARROT_INTERP, void *ptr, Pool_Allocator
-*pool, Linked_List *list)>
+*pool)>
 
 Helper function to check that we own PObj
 
@@ -1089,28 +1061,19 @@
 
 static int
 gc_ms2_is_ptr_owned(PARROT_INTERP, ARGIN_NULLOK(void *ptr),
-    ARGIN(Pool_Allocator *pool), ARGIN(Linked_List *list))
+    ARGIN(Pool_Allocator *pool))
 {
     ASSERT_ARGS(gc_ms2_is_ptr_owned)
     MarkSweep_GC     *self = (MarkSweep_GC *)interp->gc_sys->gc_private;
-    List_Item_Header *item = Obj2LLH(ptr);
     PObj             *obj  = (PObj *)ptr;
 
-    if (!obj || !item)
-        return 0;
-
-    if (!Parrot_gc_pool_is_owned(interp, pool, item))
+    if (!obj)
         return 0;
 
-    /* black or white objects marked already. */
-    if (PObj_is_live_or_free_TESTALL(obj))
+    if (!Parrot_gc_pool_is_owned(interp, pool, obj))
         return 0;
 
-    /* Pool.is_owned isn't precise enough (yet) */
-    if (Parrot_list_contains(interp, list, item))
-        return 1;
-
-    return 0;
+    return obj->flags && !PObj_on_free_list_TEST(obj);
 }
 
 
@@ -1326,75 +1289,6 @@
 
 /*
 
-=item C<static size_t gc_ms2_count_used_string_memory(PARROT_INTERP, Linked_List
-*list)>
-
-find amount of used string memory
-
-=cut
-
-*/
-
-static size_t
-gc_ms2_count_used_string_memory(PARROT_INTERP, ARGIN(Linked_List *list))
-{
-    ASSERT_ARGS(gc_ms2_count_used_string_memory)
-
-    size_t total_amount = 0;
-
-    List_Item_Header *tmp = list->first;
-    while (tmp) {
-        List_Item_Header *next = tmp->next;
-        PObj             *obj  = LLH2Obj_typed(tmp, PObj);
-        STRING           *str  = (STRING*)obj;
-
-        /* Header size */
-        total_amount += sizeof (List_Item_Header)
-                        + sizeof (STRING*);
-        total_amount += str->bufused;
-
-        tmp = next;
-    }
-
-    return total_amount;
-}
-
-/*
-
-=item C<static size_t gc_ms2_count_used_pmc_memory(PARROT_INTERP, Linked_List
-*list)>
-
-find amount of used pmc memory
-
-=cut
-
-*/
-
-static size_t
-gc_ms2_count_used_pmc_memory(PARROT_INTERP, ARGIN(Linked_List *list))
-{
-    ASSERT_ARGS(gc_ms2_count_used_pmc_memory)
-
-    size_t total_amount = 0;
-
-    List_Item_Header *tmp = list->first;
-    while (tmp) {
-        List_Item_Header *next = tmp->next;
-        PMC              *obj  = LLH2Obj_typed(tmp, PMC);
-
-        /* Header size */
-        total_amount += sizeof (List_Item_Header)
-                        + sizeof (PMC*);
-        total_amount += obj->vtable->attr_size;
-
-        tmp = next;
-    }
-
-    return total_amount;
-}
-
-/*
-
 =back
 
 =cut


More information about the parrot-commits mailing list