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:
+114
-120
@@ -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 */
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user