ruby-changes:72171
From: Takashi <ko1@a...>
Date: Thu, 16 Jun 2022 01:41:14 +0900 (JST)
Subject: [ruby-changes:72171] 1162523bae (master): Remove MJIT worker thread (#6006)
https://git.ruby-lang.org/ruby.git/commit/?id=1162523bae From 1162523bae926cfa6128043b635e28c14b732754 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun <takashikkbn@g...> Date: Wed, 15 Jun 2022 09:40:54 -0700 Subject: Remove MJIT worker thread (#6006) [Misc #18830] --- common.mk | 5 + gc.c | 3 - mjit.c | 316 +++++++++++++++++++--------------- mjit.h | 5 +- mjit_worker.c | 382 +++++++++++++----------------------------- process.c | 84 +++------- test/ruby/test_rubyvm_mjit.rb | 6 +- thread.c | 15 ++ 8 files changed, 351 insertions(+), 465 deletions(-) diff --git a/common.mk b/common.mk index db4a283602..56139211de 100644 --- a/common.mk +++ b/common.mk @@ -9657,6 +9657,8 @@ mjit.$(OBJEXT): {$(VPATH)}mjit_worker.c https://github.com/ruby/ruby/blob/trunk/common.mk#L9657 mjit.$(OBJEXT): {$(VPATH)}node.h mjit.$(OBJEXT): {$(VPATH)}onigmo.h mjit.$(OBJEXT): {$(VPATH)}oniguruma.h +mjit.$(OBJEXT): {$(VPATH)}ractor.h +mjit.$(OBJEXT): {$(VPATH)}ractor_core.h mjit.$(OBJEXT): {$(VPATH)}ruby_assert.h mjit.$(OBJEXT): {$(VPATH)}ruby_atomic.h mjit.$(OBJEXT): {$(VPATH)}st.h @@ -13800,6 +13802,7 @@ signal.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h https://github.com/ruby/ruby/blob/trunk/common.mk#L13802 signal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h signal.$(OBJEXT): $(CCAN_DIR)/list/list.h signal.$(OBJEXT): $(CCAN_DIR)/str/str.h +signal.$(OBJEXT): $(hdrdir)/ruby.h signal.$(OBJEXT): $(hdrdir)/ruby/ruby.h signal.$(OBJEXT): $(top_srcdir)/internal/array.h signal.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -13985,6 +13988,7 @@ signal.$(OBJEXT): {$(VPATH)}internal/warning_push.h https://github.com/ruby/ruby/blob/trunk/common.mk#L13988 signal.$(OBJEXT): {$(VPATH)}internal/xmalloc.h signal.$(OBJEXT): {$(VPATH)}method.h signal.$(OBJEXT): {$(VPATH)}missing.h +signal.$(OBJEXT): {$(VPATH)}mjit.h signal.$(OBJEXT): {$(VPATH)}node.h signal.$(OBJEXT): {$(VPATH)}onigmo.h signal.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -14000,6 +14004,7 @@ signal.$(OBJEXT): {$(VPATH)}thread_native.h https://github.com/ruby/ruby/blob/trunk/common.mk#L14004 signal.$(OBJEXT): {$(VPATH)}vm_core.h signal.$(OBJEXT): {$(VPATH)}vm_debug.h signal.$(OBJEXT): {$(VPATH)}vm_opts.h +signal.$(OBJEXT): {$(VPATH)}yjit.h sprintf.$(OBJEXT): $(hdrdir)/ruby/ruby.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bignum.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bits.h diff --git a/gc.c b/gc.c index b3b44e87a1..106f7bb2b3 100644 --- a/gc.c +++ b/gc.c @@ -9497,8 +9497,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ https://github.com/ruby/ruby/blob/trunk/gc.c#L9497 if (UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0"); if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace); - mjit_gc_start_hook(); - during_gc = TRUE; RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace)); gc_report(1, objspace, "gc_enter: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace)); @@ -9517,7 +9515,6 @@ gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_l https://github.com/ruby/ruby/blob/trunk/gc.c#L9515 gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace)); during_gc = FALSE; - mjit_gc_exit_hook(); gc_exit_clock(objspace, event); RB_VM_LOCK_LEAVE_LEV(lock_lev); diff --git a/mjit.c b/mjit.c index b36331e72e..93e3d823e4 100644 --- a/mjit.c +++ b/mjit.c @@ -25,6 +25,7 @@ https://github.com/ruby/ruby/blob/trunk/mjit.c#L25 #include "internal/hash.h" #include "internal/warnings.h" #include "vm_sync.h" +#include "ractor_core.h" #include "mjit_worker.c" @@ -50,40 +51,6 @@ get_uniq_filename(unsigned long id, const char *prefix, const char *suffix) https://github.com/ruby/ruby/blob/trunk/mjit.c#L51 return str; } -// Wait until workers don't compile any iseq. It is called at the -// start of GC. -void -mjit_gc_start_hook(void) -{ - if (!mjit_enabled) - return; - CRITICAL_SECTION_START(4, "mjit_gc_start_hook"); - while (in_jit) { - verbose(4, "Waiting wakeup from a worker for GC"); - rb_native_cond_wait(&mjit_client_wakeup, &mjit_engine_mutex); - verbose(4, "Getting wakeup from a worker for GC"); - } - in_gc++; - CRITICAL_SECTION_FINISH(4, "mjit_gc_start_hook"); -} - -// Send a signal to workers to continue iseq compilations. It is -// called at the end of GC. -void -mjit_gc_exit_hook(void) -{ - if (!mjit_enabled) - return; - CRITICAL_SECTION_START(4, "mjit_gc_exit_hook"); - in_gc--; - RUBY_ASSERT_ALWAYS(in_gc >= 0); - if (!in_gc) { - verbose(4, "Sending wakeup signal to workers after GC"); - rb_native_cond_broadcast(&mjit_gc_wakeup); - } - CRITICAL_SECTION_FINISH(4, "mjit_gc_exit_hook"); -} - // Prohibit calling JIT-ed code and let existing JIT-ed frames exit before the next insn. void mjit_cancel_all(const char *reason) @@ -133,9 +100,6 @@ mjit_free_iseq(const rb_iseq_t *iseq) https://github.com/ruby/ruby/blob/trunk/mjit.c#L100 if (!mjit_enabled) return; - CRITICAL_SECTION_START(4, "mjit_free_iseq"); - RUBY_ASSERT_ALWAYS(in_gc); - RUBY_ASSERT_ALWAYS(!in_jit); if (ISEQ_BODY(iseq)->jit_unit) { // jit_unit is not freed here because it may be referred by multiple // lists of units. `get_from_list` and `mjit_finish` do the job. @@ -150,7 +114,6 @@ mjit_free_iseq(const rb_iseq_t *iseq) https://github.com/ruby/ruby/blob/trunk/mjit.c#L114 unit->iseq = NULL; } } - CRITICAL_SECTION_FINISH(4, "mjit_free_iseq"); } // Free unit list. This should be called only when worker is finished @@ -245,19 +208,169 @@ finish_conts(void) https://github.com/ruby/ruby/blob/trunk/mjit.c#L208 } } -// Create unit for `iseq`. This function may be called from an MJIT worker. +static void mjit_wait(struct rb_iseq_constant_body *body); + +// Check the unit queue and start mjit_compile if nothing is in progress. static void -create_unit(const rb_iseq_t *iseq) +check_unit_queue(void) { - struct rb_mjit_unit *unit; + if (worker_stopped) return; + if (current_cc_pid != 0) return; // still compiling + + // Run unload_units after it's requested `max_cache_size / 10` (default: 10) times. + // This throttles the call to mitigate locking in unload_units. It also throttles JIT compaction. + int throttle_threshold = mjit_opts.max_cache_size / 10; + if (unload_requests >= throttle_threshold) { + unload_units(); + unload_requests = 0; + if (active_units.length == mjit_opts.max_cache_size && mjit_opts.wait) { // Sometimes all methods may be in use + mjit_opts.max_cache_size++; // avoid infinite loop on `rb_mjit_wait_call`. Note that --jit-wait is just for testing. + verbose(1, "No units can be unloaded -- incremented max-cache-size to %d for --jit-wait", mjit_opts.max_cache_size); + } + } + if (active_units.length >= mjit_opts.max_cache_size) return; // wait until unload_units makes a progress - unit = calloc(1, sizeof(struct rb_mjit_unit)); + // Dequeue a unit + struct rb_mjit_unit *unit = get_from_list(&unit_queue); + if (unit == NULL) return; + +#ifdef _WIN32 + // Synchronously compile methods on Windows. + // mswin: No SIGCHLD, MinGW: directly compiling .c to .so doesn't work + mjit_func_t func = convert_unit_to_func(unit); + if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) { + add_to_list(unit, &active_units); + MJIT_ATOMIC_SET(ISEQ_BODY(unit->iseq)->jit_func, func); + } +#else + current_cc_ms = real_ms_time(); + current_cc_unit = unit; + current_cc_pid = start_mjit_compile(unit); + // TODO: handle -1 + if (mjit_opts.wait) { + mjit_wait(unit->iseq->body); + } +#endif +} + +// Create unit for `iseq`. This function may be called from an MJIT worker. +static struct rb_mjit_unit* +create_unit(const rb_iseq_t *iseq) +{ + // To prevent GC, don't use ZALLOC // TODO: just use ZALLOC + struct rb_mjit_unit *unit = calloc(1, sizeof(struct rb_mjit_unit)); if (unit == NULL) - return; + return NULL; unit->id = current_unit_num++; - unit->iseq = (rb_iseq_t *)iseq; - ISEQ_BODY(iseq)->jit_unit = unit; + if (iseq == NULL) { // Compact unit + unit->compact_p = true; + } else { // Normal unit + unit->iseq = (rb_iseq_t *)iseq; + ISEQ_BODY(iseq)->jit_unit = unit; + } + return unit; +} + +// Check if it should compact all JIT code and start it as needed +static void +check_compaction(void) +{ +#if USE_JIT_COMPACTION + // Allow only `max_cache_size / 100` times (default: 100) of compaction. + // Note: GC of compacted code has not been implemented yet. + int max_compact_size = mjit_opts.max_cache_size / 100; + if (max_compact_size < 10) max_compact_size = 10; + + // Run unload_units after it's requested `max_cache_size / 10` (default: 10) times. + // This throttles the call to mitigate locking in unload_units. It also throttles JIT compaction. + int throttle_threshold = mjit_opts.max_cache_size / 10; + + if (compact_units.length < max_compact_size + && ((!mjit_opts.wait && unit_queue.length == 0 && active_units.length > 1) + || (active_units.length == mjit_opts.max_cache_size && compact_units.length * throttle_threshold <= total_unloads))) { // throttle compaction by total_unloads + struct rb_mjit_unit *unit = create_unit(NULL); + if (unit != NULL) { + // TODO: assert unit is null + current_cc_ms = real_ms_time(); + current_cc_unit = unit; + current_cc_pid = start_mjit_compact(unit); + // TODO: check -1 + } + } +#endif +} + +// Check the current CC process if any, and start a next C c (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/