Refactor memory management (#2954)

This PR is a general cleanup for garbage collection and memory
allocation code paths.

Changes:
  * Removed an unnecesary local variable from 'ecma_gc_mark'.
  * Refactored 'ecma_gc_run' to have an implicit list head during
    iteration, which results in one less condition in the loops,
    and changed the loops to use compressed pointers to reduce the
    overall amount of compression/decompression.
  * Renamed 'jmem_free_unused_memory_severity_t' to 'jmem_pressure_t',
    and added additional values.
  * Removed 'jmem_free_unused_memory_callback', instead
    'ecma_free_unused_memory' is now called directly.
  * Reworked 'ecma_free_unused_memory' to handle all code paths related
    to 'jmem_pressure_t', and moved all relevant code paths into this
    function. This simplifies the code paths in other places.
  * Reworked 'jmem_heap_gc_and_alloc_block' to be more streamlined.
  * Changed mem-stats to not report unused pool chunks as allocated
    memory.
  * Created an allocator internal API for allocating/freeing memory blocks
    that are not reported as used memory in mem-stats.
  * Removed iteration statistics for the jerry allocator from mem-stats,
    as they don't provide any actually useful information.

Co-authored-by: Marko Fabo <mfabo@inf.u-szeged.hu>
JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
This commit is contained in:
Dániel Bátyai
2019-07-17 14:12:23 +02:00
committed by GitHub
parent a44d584842
commit ff22634e27
27 changed files with 297 additions and 538 deletions
+114 -120
View File
@@ -308,8 +308,6 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */
JERRY_ASSERT (object_p != NULL);
JERRY_ASSERT (ecma_gc_is_object_visited (object_p));
bool traverse_properties = true;
if (ecma_is_lexical_environment (object_p))
{
ecma_object_t *lex_env_p = ecma_get_lex_env_outer_reference (object_p);
@@ -323,7 +321,7 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */
ecma_object_t *binding_object_p = ecma_get_lex_env_binding_object (object_p);
ecma_gc_set_object_visited (binding_object_p);
traverse_properties = false;
return;
}
}
else
@@ -492,26 +490,23 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */
}
}
if (traverse_properties)
ecma_property_header_t *prop_iter_p = ecma_get_property_list (object_p);
if (prop_iter_p != NULL && prop_iter_p->types[0] == ECMA_PROPERTY_TYPE_HASHMAP)
{
ecma_property_header_t *prop_iter_p = ecma_get_property_list (object_p);
prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t,
prop_iter_p->next_property_cp);
}
if (prop_iter_p != NULL && prop_iter_p->types[0] == ECMA_PROPERTY_TYPE_HASHMAP)
{
prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t,
prop_iter_p->next_property_cp);
}
while (prop_iter_p != NULL)
{
JERRY_ASSERT (ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p));
while (prop_iter_p != NULL)
{
JERRY_ASSERT (ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p));
ecma_gc_mark_property ((ecma_property_pair_t *) prop_iter_p, 0);
ecma_gc_mark_property ((ecma_property_pair_t *) prop_iter_p, 1);
ecma_gc_mark_property ((ecma_property_pair_t *) prop_iter_p, 0);
ecma_gc_mark_property ((ecma_property_pair_t *) prop_iter_p, 1);
prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t,
prop_iter_p->next_property_cp);
}
prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t,
prop_iter_p->next_property_cp);
}
} /* ecma_gc_mark */
@@ -905,23 +900,26 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */
} /* ecma_gc_free_object */
/**
* Run garbage collection
* Run garbage collection, freeing objects that are no longer referenced.
*/
void
ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
ecma_gc_run (void)
{
JERRY_CONTEXT (ecma_gc_new_objects) = 0;
ecma_object_t *white_gray_objects_p = JERRY_CONTEXT (ecma_gc_objects_p);
ecma_object_t *black_objects_p = NULL;
ecma_object_t white_gray_list_head;
ecma_gc_set_object_next (&white_gray_list_head, JERRY_CONTEXT (ecma_gc_objects_p));
jmem_cpointer_t black_objects_cp = JMEM_CP_NULL;
ecma_object_t *obj_iter_p = white_gray_objects_p;
ecma_object_t *obj_prev_p = NULL;
ecma_object_t *obj_prev_p = &white_gray_list_head;
jmem_cpointer_t obj_iter_cp = obj_prev_p->gc_next_cp;
ecma_object_t *obj_iter_p;
/* Move root objects (i.e. they have global or stack references) to the black list. */
while (obj_iter_p != NULL)
while (obj_iter_cp != JMEM_CP_NULL)
{
ecma_object_t *obj_next_p = ecma_gc_get_object_next (obj_iter_p);
obj_iter_p = JMEM_CP_GET_NON_NULL_POINTER (ecma_object_t, obj_iter_cp);
const jmem_cpointer_t obj_next_cp = obj_iter_p->gc_next_cp;
JERRY_ASSERT (obj_prev_p == NULL
|| ecma_gc_get_object_next (obj_prev_p) == obj_iter_p);
@@ -929,36 +927,29 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
if (ecma_gc_is_object_visited (obj_iter_p))
{
/* Moving the object to list of marked objects. */
if (JERRY_LIKELY (obj_prev_p != NULL))
{
obj_prev_p->gc_next_cp = obj_iter_p->gc_next_cp;
}
else
{
white_gray_objects_p = obj_next_p;
}
obj_prev_p->gc_next_cp = obj_next_cp;
ecma_gc_set_object_next (obj_iter_p, black_objects_p);
black_objects_p = obj_iter_p;
obj_iter_p->gc_next_cp = black_objects_cp;
black_objects_cp = obj_iter_cp;
}
else
{
obj_prev_p = obj_iter_p;
}
obj_iter_p = obj_next_p;
obj_iter_cp = obj_next_cp;
}
ecma_object_t *first_root_object_p = JMEM_CP_GET_POINTER (ecma_object_t, black_objects_cp);
/* Mark root objects. */
obj_iter_p = black_objects_p;
obj_iter_p = first_root_object_p;
while (obj_iter_p != NULL)
{
ecma_gc_mark (obj_iter_p);
obj_iter_p = ecma_gc_get_object_next (obj_iter_p);
}
ecma_object_t *first_root_object_p = black_objects_p;
/* Mark non-root objects. */
bool marked_anything_during_current_iteration;
@@ -966,12 +957,13 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
{
marked_anything_during_current_iteration = false;
obj_prev_p = NULL;
obj_iter_p = white_gray_objects_p;
obj_prev_p = &white_gray_list_head;
obj_iter_cp = obj_prev_p->gc_next_cp;
while (obj_iter_p != NULL)
while (obj_iter_cp != JMEM_CP_NULL)
{
ecma_object_t *obj_next_p = ecma_gc_get_object_next (obj_iter_p);
obj_iter_p = JMEM_CP_GET_NON_NULL_POINTER (ecma_object_t, obj_iter_cp);
const jmem_cpointer_t obj_next_cp = obj_iter_p->gc_next_cp;
JERRY_ASSERT (obj_prev_p == NULL
|| ecma_gc_get_object_next (obj_prev_p) == obj_iter_p);
@@ -979,17 +971,10 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
if (ecma_gc_is_object_visited (obj_iter_p))
{
/* Moving the object to list of marked objects */
if (JERRY_LIKELY (obj_prev_p != NULL))
{
obj_prev_p->gc_next_cp = obj_iter_p->gc_next_cp;
}
else
{
white_gray_objects_p = obj_next_p;
}
obj_prev_p->gc_next_cp = obj_next_cp;
ecma_gc_set_object_next (obj_iter_p, black_objects_p);
black_objects_p = obj_iter_p;
obj_iter_p->gc_next_cp = black_objects_cp;
black_objects_cp = obj_iter_cp;
ecma_gc_mark (obj_iter_p);
marked_anything_during_current_iteration = true;
@@ -999,13 +984,13 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
obj_prev_p = obj_iter_p;
}
obj_iter_p = obj_next_p;
obj_iter_cp = obj_next_cp;
}
}
while (marked_anything_during_current_iteration);
/* Sweep objects that are currently unmarked. */
obj_iter_p = white_gray_objects_p;
obj_iter_p = ecma_gc_get_object_next (&white_gray_list_head);
while (obj_iter_p != NULL)
{
@@ -1018,7 +1003,8 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
}
/* Reset the reference counter of non-root black objects. */
obj_iter_p = black_objects_p;
obj_iter_p = JMEM_CP_GET_POINTER (ecma_object_t, black_objects_cp);
JERRY_CONTEXT (ecma_gc_objects_p) = obj_iter_p;
while (obj_iter_p != first_root_object_p)
{
@@ -1029,10 +1015,70 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
obj_iter_p = ecma_gc_get_object_next (obj_iter_p);
}
if (severity == JMEM_FREE_UNUSED_MEMORY_SEVERITY_HIGH)
{
obj_iter_p = black_objects_p;
#if ENABLED (JERRY_BUILTIN_REGEXP)
/* Free RegExp bytecodes stored in cache */
re_cache_gc_run ();
#endif /* ENABLED (JERRY_BUILTIN_REGEXP) */
} /* ecma_gc_run */
/**
* Try to free some memory (depending on memory pressure).
*
* When called with JMEM_PRESSURE_FULL, the engine will be terminated with ERR_OUT_OF_MEMORY.
*/
void
ecma_free_unused_memory (jmem_pressure_t pressure) /**< current pressure */
{
#if ENABLED (JERRY_DEBUGGER)
while ((JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED)
&& JERRY_CONTEXT (debugger_byte_code_free_tail) != ECMA_NULL_POINTER)
{
/* Wait until all byte code is freed or the connection is aborted. */
jerry_debugger_receive (NULL);
}
#endif /* ENABLED (JERRY_DEBUGGER) */
if (JERRY_LIKELY (pressure == JMEM_PRESSURE_LOW))
{
#if ENABLED (JERRY_PROPRETY_HASHMAP)
if (JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) > ECMA_PROP_HASHMAP_ALLOC_ON)
{
--JERRY_CONTEXT (ecma_prop_hashmap_alloc_state);
}
JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_PRESSURE_GC;
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
/*
* If there is enough newly allocated objects since last GC, probably it is worthwhile to start GC now.
* Otherwise, probability to free sufficient space is considered to be low.
*/
size_t new_objects_fraction = CONFIG_ECMA_GC_NEW_OBJECTS_FRACTION;
if (JERRY_CONTEXT (ecma_gc_new_objects) * new_objects_fraction > JERRY_CONTEXT (ecma_gc_objects_number))
{
ecma_gc_run ();
}
return;
}
else if (pressure == JMEM_PRESSURE_HIGH)
{
/* Freeing as much memory as we currently can */
#if ENABLED (JERRY_PROPRETY_HASHMAP)
if (JERRY_CONTEXT (status_flags) & ECMA_STATUS_HIGH_PRESSURE_GC)
{
JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) = ECMA_PROP_HASHMAP_ALLOC_MAX;
}
else if (JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) < ECMA_PROP_HASHMAP_ALLOC_MAX)
{
++JERRY_CONTEXT (ecma_prop_hashmap_alloc_state);
JERRY_CONTEXT (status_flags) |= ECMA_STATUS_HIGH_PRESSURE_GC;
}
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
ecma_gc_run ();
/* Free hashmaps of remaining objects. */
ecma_object_t *obj_iter_p = JERRY_CONTEXT (ecma_gc_objects_p);
while (obj_iter_p != NULL)
{
if (!ecma_is_lexical_environment (obj_iter_p)
@@ -1048,70 +1094,18 @@ ecma_gc_run (jmem_free_unused_memory_severity_t severity) /**< gc severity */
obj_iter_p = ecma_gc_get_object_next (obj_iter_p);
}
jmem_pools_collect_empty ();
return;
}
JERRY_CONTEXT (ecma_gc_objects_p) = black_objects_p;
#if ENABLED (JERRY_BUILTIN_REGEXP)
/* Free RegExp bytecodes stored in cache */
re_cache_gc_run ();
#endif /* ENABLED (JERRY_BUILTIN_REGEXP) */
} /* ecma_gc_run */
/**
* Try to free some memory (depending on severity).
*/
void
ecma_free_unused_memory (jmem_free_unused_memory_severity_t severity) /**< severity of the request */
{
#if ENABLED (JERRY_DEBUGGER)
while ((JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED)
&& JERRY_CONTEXT (debugger_byte_code_free_tail) != ECMA_NULL_POINTER)
else if (JERRY_UNLIKELY (pressure == JMEM_PRESSURE_FULL))
{
/* Wait until all byte code is freed or the connection is aborted. */
jerry_debugger_receive (NULL);
}
#endif /* ENABLED (JERRY_DEBUGGER) */
if (severity == JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW)
{
#if ENABLED (JERRY_PROPRETY_HASHMAP)
if (JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) > ECMA_PROP_HASHMAP_ALLOC_ON)
{
--JERRY_CONTEXT (ecma_prop_hashmap_alloc_state);
}
JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_SEV_GC;
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
/*
* If there is enough newly allocated objects since last GC, probably it is worthwhile to start GC now.
* Otherwise, probability to free sufficient space is considered to be low.
*/
size_t new_objects_share = CONFIG_ECMA_GC_NEW_OBJECTS_SHARE_TO_START_GC;
if (JERRY_CONTEXT (ecma_gc_new_objects) * new_objects_share > JERRY_CONTEXT (ecma_gc_objects_number))
{
ecma_gc_run (severity);
}
jerry_fatal (ERR_OUT_OF_MEMORY);
}
else
{
JERRY_ASSERT (severity == JMEM_FREE_UNUSED_MEMORY_SEVERITY_HIGH);
#if ENABLED (JERRY_PROPRETY_HASHMAP)
if (JERRY_CONTEXT (status_flags) & ECMA_STATUS_HIGH_SEV_GC)
{
JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) = ECMA_PROP_HASHMAP_ALLOC_MAX;
}
else if (JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) < ECMA_PROP_HASHMAP_ALLOC_MAX)
{
++JERRY_CONTEXT (ecma_prop_hashmap_alloc_state);
JERRY_CONTEXT (status_flags) |= ECMA_STATUS_HIGH_SEV_GC;
}
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
/* Freeing as much memory as we currently can */
ecma_gc_run (severity);
JERRY_ASSERT (pressure == JMEM_PRESSURE_NONE);
JERRY_UNREACHABLE ();
}
} /* ecma_free_unused_memory */
+2 -2
View File
@@ -29,8 +29,8 @@
void ecma_init_gc_info (ecma_object_t *object_p);
void ecma_ref_object (ecma_object_t *object_p);
void ecma_deref_object (ecma_object_t *object_p);
void ecma_gc_run (jmem_free_unused_memory_severity_t severity);
void ecma_free_unused_memory (jmem_free_unused_memory_severity_t severity);
void ecma_gc_run (void);
void ecma_free_unused_memory (jmem_pressure_t pressure);
/**
* @}
+1 -1
View File
@@ -59,7 +59,7 @@ typedef enum
ECMA_STATUS_API_AVAILABLE = (1u << 0), /**< api available */
ECMA_STATUS_DIRECT_EVAL = (1u << 1), /**< eval is called directly */
#if ENABLED (JERRY_PROPRETY_HASHMAP)
ECMA_STATUS_HIGH_SEV_GC = (1u << 2), /**< last gc run was a high severity run */
ECMA_STATUS_HIGH_PRESSURE_GC = (1u << 2), /**< last gc was under high pressure */
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
ECMA_STATUS_EXCEPTION = (1u << 3), /**< last exception is a normal exception */
} ecma_status_flag_t;
+2 -5
View File
@@ -37,11 +37,9 @@ ecma_init (void)
{
ecma_init_global_lex_env ();
jmem_register_free_unused_memory_callback (ecma_free_unused_memory);
#if ENABLED (JERRY_PROPRETY_HASHMAP)
JERRY_CONTEXT (ecma_prop_hashmap_alloc_state) = ECMA_PROP_HASHMAP_ALLOC_ON;
JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_SEV_GC;
JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_PRESSURE_GC;
#endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */
#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0)
@@ -59,10 +57,9 @@ ecma_init (void)
void
ecma_finalize (void)
{
jmem_unregister_free_unused_memory_callback (ecma_free_unused_memory);
ecma_finalize_global_lex_env ();
ecma_finalize_builtins ();
ecma_gc_run (JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW);
ecma_gc_run ();
ecma_finalize_lit_storage ();
} /* ecma_finalize */