[wasm] Implement GC support. Embedder must provide main loop pumping function request...
[mono.git] / mono / sgen / sgen-gc.c
index ea67897afca98cbae0e5ec460916cdab92894ee1..4c3454823fdd42c2f5e0073a87f650601d6b84bf 100644 (file)
 #include <errno.h>
 #include <assert.h>
 #include <stdlib.h>
+#include <glib.h>
 
 #include "mono/sgen/sgen-gc.h"
 #include "mono/sgen/sgen-cardtable.h"
 
 #include <mono/utils/memcheck.h>
 #include <mono/utils/mono-mmap-internals.h>
+#include <mono/utils/unlocked.h>
 
 #undef pthread_create
 #undef pthread_join
@@ -335,13 +337,6 @@ nursery_canaries_enabled (void)
 
 #define safe_object_get_size   sgen_safe_object_get_size
 
-#if defined(HAVE_CONC_GC_AS_DEFAULT)
-/* Use concurrent major on deskstop platforms */
-#define DEFAULT_MAJOR SGEN_MAJOR_CONCURRENT
-#else
-#define DEFAULT_MAJOR SGEN_MAJOR_SERIAL
-#endif
-
 typedef enum {
        SGEN_MAJOR_DEFAULT,
        SGEN_MAJOR_SERIAL,
@@ -1363,6 +1358,7 @@ typedef struct {
 typedef struct {
        ScanJob scan_job;
        int job_index, job_split_count;
+       int data;
 } ParallelScanJob;
 
 static ScanCopyContext
@@ -1446,7 +1442,7 @@ job_scan_major_card_table (void *worker_data_untyped, SgenThreadPoolJob *job)
        ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
 
        SGEN_TV_GETTIME (atv);
-       major_collector.scan_card_table (CARDTABLE_SCAN_GLOBAL, ctx, job_data->job_index, job_data->job_split_count);
+       major_collector.scan_card_table (CARDTABLE_SCAN_GLOBAL, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
        SGEN_TV_GETTIME (btv);
        time_minor_scan_major_blocks += SGEN_TV_ELAPSED (atv, btv);
 
@@ -1481,7 +1477,7 @@ job_scan_major_mod_union_card_table (void *worker_data_untyped, SgenThreadPoolJo
 
        g_assert (concurrent_collection_in_progress);
        SGEN_TV_GETTIME (atv);
-       major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION, ctx, job_data->job_index, job_data->job_split_count);
+       major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
        SGEN_TV_GETTIME (btv);
        time_major_scan_mod_union_blocks += SGEN_TV_ELAPSED (atv, btv);
 
@@ -1517,7 +1513,7 @@ job_major_mod_union_preclean (void *worker_data_untyped, SgenThreadPoolJob *job)
 
        g_assert (concurrent_collection_in_progress);
        SGEN_TV_GETTIME (atv);
-       major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx, job_data->job_index, job_data->job_split_count);
+       major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
        SGEN_TV_GETTIME (btv);
 
        g_assert (worker_data_untyped);
@@ -1557,6 +1553,7 @@ workers_finish_callback (void)
 {
        ParallelScanJob *psj;
        ScanJob *sj;
+       size_t num_major_sections = major_collector.get_num_major_sections ();
        int split_count = sgen_workers_get_job_split_count (GENERATION_OLD);
        int i;
        /* Mod union preclean jobs */
@@ -1565,6 +1562,7 @@ workers_finish_callback (void)
                psj->scan_job.gc_thread_gray_queue = NULL;
                psj->job_index = i;
                psj->job_split_count = split_count;
+               psj->data = num_major_sections / split_count;
                sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, TRUE);
        }
 
@@ -1591,6 +1589,7 @@ static void
 enqueue_scan_remembered_set_jobs (SgenGrayQueue *gc_thread_gray_queue, SgenObjectOperations *ops, gboolean enqueue)
 {
        int i, split_count = sgen_workers_get_job_split_count (GENERATION_NURSERY);
+       size_t num_major_sections = major_collector.get_num_major_sections ();
        ScanJob *sj;
 
        sj = (ScanJob*)sgen_thread_pool_job_alloc ("scan wbroots", job_scan_wbroots, sizeof (ScanJob));
@@ -1606,6 +1605,7 @@ enqueue_scan_remembered_set_jobs (SgenGrayQueue *gc_thread_gray_queue, SgenObjec
                psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
                psj->job_index = i;
                psj->job_split_count = split_count;
+               psj->data = num_major_sections / split_count;
                sgen_workers_enqueue_job (GENERATION_NURSERY, &psj->scan_job.job, enqueue);
 
                psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan LOS remsets", job_scan_los_card_table, sizeof (ParallelScanJob));
@@ -1696,7 +1696,7 @@ collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_
        TV_GETTIME (last_minor_collection_start_tv);
        atv = last_minor_collection_start_tv;
 
-       binary_protocol_collection_begin (gc_stats.minor_gc_count, GENERATION_NURSERY);
+       binary_protocol_collection_begin (InterlockedRead (&gc_stats.minor_gc_count), GENERATION_NURSERY);
 
        object_ops_nopar = sgen_concurrent_collection_in_progress ()
                                ? &sgen_minor_collector.serial_ops_with_concurrent_major
@@ -1724,7 +1724,7 @@ collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_
        degraded_mode = 0;
        objects_pinned = 0;
 
-       SGEN_LOG (1, "Start nursery collection %d %p-%p, size: %d", gc_stats.minor_gc_count, nursery_section->data, nursery_section->end_data, (int)(nursery_section->end_data - nursery_section->data));
+       SGEN_LOG (1, "Start nursery collection %" G_GINT32_FORMAT " %p-%p, size: %d", InterlockedRead (&gc_stats.minor_gc_count), nursery_section->data, nursery_section->end_data, (int)(nursery_section->end_data - nursery_section->data));
 
        /* world must be stopped already */
        TV_GETTIME (btv);
@@ -1739,7 +1739,7 @@ collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_
        init_gray_queue (&gc_thread_gray_queue);
        ctx = CONTEXT_FROM_OBJECT_OPERATIONS (object_ops_nopar, &gc_thread_gray_queue);
 
-       gc_stats.minor_gc_count ++;
+       InterlockedIncrement (&gc_stats.minor_gc_count);
 
        sgen_process_fin_stage_entries ();
 
@@ -1856,9 +1856,9 @@ collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_
        major_collector.finish_nursery_collection ();
 
        TV_GETTIME (last_minor_collection_end_tv);
-       gc_stats.minor_gc_time += TV_ELAPSED (last_minor_collection_start_tv, last_minor_collection_end_tv);
+       UnlockedAdd64 (&gc_stats.minor_gc_time, TV_ELAPSED (last_minor_collection_start_tv, last_minor_collection_end_tv));
 
-       sgen_debug_dump_heap ("minor", gc_stats.minor_gc_count - 1, NULL);
+       sgen_debug_dump_heap ("minor", InterlockedRead (&gc_stats.minor_gc_count) - 1, NULL);
 
        /* prepare the pin queue for the next collection */
        sgen_finish_pinning ();
@@ -1891,7 +1891,7 @@ collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_
                        time_minor_scan_los - los_scan_start,
                        time_minor_finish_gray_stack - finish_gray_start);
 
-       binary_protocol_collection_end (gc_stats.minor_gc_count - 1, GENERATION_NURSERY, 0, 0);
+       binary_protocol_collection_end (InterlockedRead (&gc_stats.minor_gc_count) - 1, GENERATION_NURSERY, 0, 0);
 
        if (check_nursery_objects_pinned && !sgen_minor_collector.is_split)
                sgen_check_nursery_objects_pinned (unpin_queue != NULL);
@@ -2037,10 +2037,13 @@ major_copy_or_mark_from_roots (SgenGrayQueue *gc_thread_gray_queue, size_t *old_
        if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
                if (object_ops_par != NULL)
                        sgen_workers_set_num_active_workers (GENERATION_OLD, 0);
-               if (sgen_workers_have_idle_work (GENERATION_OLD)) {
+               if (object_ops_par == NULL && sgen_workers_have_idle_work (GENERATION_OLD)) {
                        /*
                         * We force the finish of the worker with the new object ops context
-                        * which can also do copying. We need to have finished pinning.
+                        * which can also do copying. We need to have finished pinning. On the
+                        * parallel collector, there is no need to drain the private queues
+                        * here, since we can do it as part of the finishing work, achieving
+                        * better work distribution.
                         */
                        sgen_workers_start_all_workers (GENERATION_OLD, object_ops_nopar, object_ops_par, NULL);
 
@@ -2081,6 +2084,7 @@ major_copy_or_mark_from_roots (SgenGrayQueue *gc_thread_gray_queue, size_t *old_
 
        if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
                int i, split_count = sgen_workers_get_job_split_count (GENERATION_OLD);
+               size_t num_major_sections = major_collector.get_num_major_sections ();
                gboolean parallel = object_ops_par != NULL;
 
                /* If we're not parallel we finish the collection on the gc thread */
@@ -2096,6 +2100,7 @@ major_copy_or_mark_from_roots (SgenGrayQueue *gc_thread_gray_queue, size_t *old_
                        psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
                        psj->job_index = i;
                        psj->job_split_count = split_count;
+                       psj->data = num_major_sections / split_count;
                        sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, parallel);
 
                        psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan LOS mod union cardtable", job_scan_los_mod_union_card_table, sizeof (ParallelScanJob));
@@ -2137,7 +2142,7 @@ major_start_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason,
 {
        SgenObjectOperations *object_ops_nopar, *object_ops_par = NULL;
 
-       binary_protocol_collection_begin (gc_stats.major_gc_count, GENERATION_OLD);
+       binary_protocol_collection_begin (InterlockedRead (&gc_stats.major_gc_count), GENERATION_OLD);
 
        current_collection_generation = GENERATION_OLD;
 
@@ -2168,8 +2173,8 @@ major_start_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason,
        check_scan_starts ();
 
        degraded_mode = 0;
-       SGEN_LOG (1, "Start major collection %d", gc_stats.major_gc_count);
-       gc_stats.major_gc_count ++;
+       SGEN_LOG (1, "Start major collection %" G_GINT32_FORMAT, InterlockedRead (&gc_stats.major_gc_count));
+       InterlockedIncrement (&gc_stats.major_gc_count);
 
        if (major_collector.start_major_collection)
                major_collector.start_major_collection ();
@@ -2285,7 +2290,7 @@ major_finish_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason
        TV_GETTIME (atv);
        time_major_sweep += TV_ELAPSED (btv, atv);
 
-       sgen_debug_dump_heap ("major", gc_stats.major_gc_count - 1, reason);
+       sgen_debug_dump_heap ("major", InterlockedRead (&gc_stats.major_gc_count) - 1, reason);
 
        if (sgen_have_pending_finalizers ()) {
                SGEN_LOG (4, "Finalizer-thread wakeup");
@@ -2317,7 +2322,7 @@ major_finish_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason
                         time_major_scan_mod_union_los - los_scan_start,
                         time_major_finish_gray_stack - finish_gray_start);
 
-       binary_protocol_collection_end (gc_stats.major_gc_count - 1, GENERATION_OLD, counts.num_scanned_objects, counts.num_unique_scanned_objects);
+       binary_protocol_collection_end (InterlockedRead (&gc_stats.major_gc_count) - 1, GENERATION_OLD, counts.num_scanned_objects, counts.num_unique_scanned_objects);
 }
 
 static gboolean
@@ -2345,7 +2350,7 @@ major_do_collection (const char *reason, gboolean is_overflow, gboolean forced)
        sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
 
        TV_GETTIME (time_end);
-       gc_stats.major_gc_time += TV_ELAPSED (time_start, time_end);
+       UnlockedAdd64 (&gc_stats.major_gc_time, TV_ELAPSED (time_start, time_end));
 
        /* FIXME: also report this to the user, preferably in gc-end. */
        if (major_collector.get_and_reset_num_major_objects_marked)
@@ -2381,7 +2386,7 @@ major_start_concurrent_collection (const char *reason)
        num_objects_marked = major_collector.get_and_reset_num_major_objects_marked ();
 
        TV_GETTIME (time_end);
-       gc_stats.major_gc_time += TV_ELAPSED (time_start, time_end);
+       UnlockedAdd64 (&gc_stats.major_gc_time, TV_ELAPSED (time_start, time_end));
 
        current_collection_generation = -1;
 }
@@ -2409,7 +2414,7 @@ major_update_concurrent_collection (void)
        sgen_los_update_cardtable_mod_union ();
 
        TV_GETTIME (total_end);
-       gc_stats.major_gc_time += TV_ELAPSED (total_start, total_end);
+       UnlockedAdd64 (&gc_stats.major_gc_time, TV_ELAPSED (total_start, total_end));
 }
 
 static void
@@ -2431,7 +2436,7 @@ major_finish_concurrent_collection (gboolean forced)
        sgen_workers_stop_all_workers (GENERATION_OLD);
 
        SGEN_TV_GETTIME (time_major_conc_collection_end);
-       gc_stats.major_gc_time_concurrent += SGEN_TV_ELAPSED (time_major_conc_collection_start, time_major_conc_collection_end);
+       UnlockedAdd64 (&gc_stats.major_gc_time_concurrent, SGEN_TV_ELAPSED (time_major_conc_collection_start, time_major_conc_collection_end));
 
        major_collector.update_cardtable_mod_union ();
        sgen_los_update_cardtable_mod_union ();
@@ -2446,7 +2451,7 @@ major_finish_concurrent_collection (gboolean forced)
        sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
 
        TV_GETTIME (total_end);
-       gc_stats.major_gc_time += TV_ELAPSED (total_start, total_end);
+       UnlockedAdd64 (&gc_stats.major_gc_time, TV_ELAPSED (total_start, total_end));
 
        current_collection_generation = -1;
 }
@@ -2498,7 +2503,7 @@ sgen_ensure_free_space (size_t size, int generation)
  * LOCKING: Assumes the GC lock is held.
  */
 void
-sgen_perform_collection (size_t requested_size, int generation_to_collect, const char *reason, gboolean wait_to_finish, gboolean stw)
+sgen_perform_collection_inner (size_t requested_size, int generation_to_collect, const char *reason, gboolean wait_to_finish, gboolean stw)
 {
        TV_DECLARE (gc_total_start);
        TV_DECLARE (gc_total_end);
@@ -2577,6 +2582,52 @@ sgen_perform_collection (size_t requested_size, int generation_to_collect, const
                sgen_restart_world (oldest_generation_collected);
 }
 
+#ifdef HOST_WASM
+
+typedef struct {
+       size_t requested_size;
+       int generation_to_collect;
+       const char *reason;
+} SgenGcRequest;
+
+static SgenGcRequest gc_request;
+static gboolean pending_request;
+
+extern void request_gc_cycle (void);
+
+#include <emscripten.h>
+
+EMSCRIPTEN_KEEPALIVE void
+mono_gc_pump_callback (void)
+{
+       if (!pending_request)
+               return;
+       pending_request = FALSE;
+       sgen_perform_collection_inner (gc_request.requested_size, gc_request.generation_to_collect, gc_request.reason, TRUE, TRUE);     
+}
+#endif
+
+void
+sgen_perform_collection (size_t requested_size, int generation_to_collect, const char *reason, gboolean wait_to_finish, gboolean stw)
+{
+#ifdef HOST_WASM
+       g_assert (stw); //can't handle non-stw mode (IE, domain unload)
+       //we ignore wait_to_finish
+       if (!pending_request || gc_request.generation_to_collect <= generation_to_collect) { //no active request or request was for a smaller part of the heap
+               gc_request.requested_size = requested_size;
+               gc_request.generation_to_collect = generation_to_collect;
+               gc_request.reason = reason;
+               if (!pending_request) {
+                       request_gc_cycle ();
+                       pending_request = TRUE;
+               }
+       }
+
+       degraded_mode = 1; //enable degraded mode so allocation can continue
+#else
+       sgen_perform_collection_inner (requested_size, generation_to_collect, reason, wait_to_finish, stw);
+#endif
+}
 /*
  * ######################################################################
  * ########  Memory allocation from the OS
@@ -3043,9 +3094,7 @@ sgen_gc_collect (int generation)
 int
 sgen_gc_collection_count (int generation)
 {
-       if (generation == 0)
-               return gc_stats.minor_gc_count;
-       return gc_stats.major_gc_count;
+       return InterlockedRead (generation == GENERATION_NURSERY ? &gc_stats.minor_gc_count : &gc_stats.major_gc_count);
 }
 
 size_t