diff options
author | Takashi Kokubun <takashikkbn@gmail.com> | 2022-06-15 09:40:54 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-15 09:40:54 -0700 |
commit | 1162523bae926cfa6128043b635e28c14b732754 (patch) | |
tree | 5bdc6c91d8cb361f3b6ab3b8f6e7246e18b8a6f0 | |
parent | 64fb3279d2063ea39280e9d9dc9154b6788c61e1 (diff) | |
download | ruby-1162523bae926cfa6128043b635e28c14b732754.tar.gz |
Remove MJIT worker thread (#6006)
[Misc #18830]
-rw-r--r-- | common.mk | 5 | ||||
-rw-r--r-- | gc.c | 3 | ||||
-rw-r--r-- | mjit.c | 316 | ||||
-rw-r--r-- | mjit.h | 5 | ||||
-rw-r--r-- | mjit_worker.c | 382 | ||||
-rw-r--r-- | process.c | 84 | ||||
-rw-r--r-- | test/ruby/test_rubyvm_mjit.rb | 6 | ||||
-rw-r--r-- | thread.c | 15 |
8 files changed, 351 insertions, 465 deletions
@@ -9657,6 +9657,8 @@ mjit.$(OBJEXT): {$(VPATH)}mjit_worker.c 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 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 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 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 @@ -9497,8 +9497,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ 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 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); @@ -25,6 +25,7 @@ #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) 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) 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) 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) } } -// 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 compiler process as needed. +void +mjit_notify_waitpid(int status) +{ + // TODO: check current_cc_pid? + current_cc_pid = 0; + + // Delete .c file + char c_file[MAXPATHLEN]; + sprint_uniq_filename(c_file, (int)sizeof(c_file), current_cc_unit->id, MJIT_TMP_PREFIX, ".c"); + if (!mjit_opts.save_temps) + remove_file(c_file); + + // Check the result + bool success = false; + if (WIFEXITED(status)) { + success = (WEXITSTATUS(status) == 0); + } + if (!success) { + verbose(2, "Failed to generate so"); + // TODO: free unit? + // TODO: set NOT_COMPILED_JIT_ISEQ_FUNC? + return; + } + + // Load .so file + char so_file[MAXPATHLEN]; + sprint_uniq_filename(so_file, (int)sizeof(so_file), current_cc_unit->id, MJIT_TMP_PREFIX, DLEXT); + if (current_cc_unit->compact_p) { // Compact unit +#if USE_JIT_COMPACTION + load_compact_funcs_from_so(current_cc_unit, c_file, so_file); + current_cc_unit = NULL; +#else + RUBY_ASSERT(!current_cc_unit->compact_p); +#endif + } + else { // Normal unit + // Load the function from so + char funcname[MAXPATHLEN]; + sprint_funcname(funcname, current_cc_unit); + void *func = load_func_from_so(so_file, funcname, current_cc_unit); + + // Delete .so file + if (!mjit_opts.save_temps) + remove_file(so_file); + + // Set the jit_func if successful + if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) { + rb_iseq_t *iseq = current_cc_unit->iseq; + double end_time = real_ms_time(); + verbose(1, "JIT success (%.1fms): %s@%s:%ld -> %s", + end_time - current_cc_ms, RSTRING_PTR(ISEQ_BODY(iseq)->location.label), + RSTRING_PTR(rb_iseq_path(iseq)), FIX2LONG(ISEQ_BODY(iseq)->location.first_lineno), c_file); + + add_to_list(current_cc_unit, &active_units); + MJIT_ATOMIC_SET(ISEQ_BODY(iseq)->jit_func, func); + } + current_cc_unit = NULL; + + // Run compaction if it should + if (!stop_worker_p) { + check_compaction(); + } + } + + // Skip further compilation if mjit_finish is trying to stop it + if (!stop_worker_p) { + // Start the next one as needed + check_unit_queue(); + } } // Return true if given ISeq body should be compiled by MJIT @@ -273,23 +386,14 @@ mjit_target_iseq_p(struct rb_iseq_constant_body *body) static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info, bool recompile_p) { - if (!mjit_enabled || pch_status == PCH_FAILED) + // TODO: Support non-main Ractors + if (!mjit_enabled || pch_status == PCH_FAILED || !rb_ractor_main_p()) return; if (!mjit_target_iseq_p(ISEQ_BODY(iseq))) { ISEQ_BODY(iseq)->jit_func = (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC; // skip mjit_wait return; } - if (!recompile_p) { - CRITICAL_SECTION_START(3, "in add_iseq_to_process"); - - // This prevents multiple Ractors from enqueueing the same ISeq twice. - if (rb_multi_ractor_p() && (uintptr_t)ISEQ_BODY(iseq)->jit_func != NOT_ADDED_JIT_ISEQ_FUNC) { - CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process"); - return; - } - } - RB_DEBUG_COUNTER_INC(mjit_add_iseq_to_process); ISEQ_BODY(iseq)->jit_func = (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC; create_unit(iseq); @@ -302,12 +406,6 @@ mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_inf if (active_units.length >= mjit_opts.max_cache_size) { unload_requests++; } - - if (!recompile_p) { - verbose(3, "Sending wakeup signal to workers in mjit_add_iseq_to_process"); - rb_native_cond_broadcast(&mjit_worker_wakeup); - CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process"); - } } // Add ISEQ to be JITed in parallel with the current thread. @@ -316,6 +414,7 @@ void rb_mjit_add_iseq_to_process(const rb_iseq_t *iseq) { mjit_add_iseq_to_process(iseq, NULL, false); + check_unit_queue(); } // For this timeout seconds, --jit-wait will wait for JIT compilation finish. @@ -324,23 +423,21 @@ rb_mjit_add_iseq_to_process(const rb_iseq_t *iseq) static void mjit_wait(struct rb_iseq_constant_body *body) { + pid_t initial_pid = current_cc_pid; struct timeval tv; int tries = 0; tv.tv_sec = 0; tv.tv_usec = 1000; - while (body->jit_func == (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC) { + while (body == NULL ? current_cc_pid == initial_pid : body->jit_func == (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC) { // TODO: refactor this tries++; if (tries / 1000 > MJIT_WAIT_TIMEOUT_SECONDS || pch_status == PCH_FAILED) { - CRITICAL_SECTION_START(3, "in rb_mjit_wait_call to set jit_func"); - body->jit_func = (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC; // JIT worker seems dead. Give up. - CRITICAL_SECTION_FINISH(3, "in rb_mjit_wait_call to set jit_func"); + if (body != NULL) { + body->jit_func = (mjit_func_t) NOT_COMPILED_JIT_ISEQ_FUNC; // JIT worker seems dead. Give up. + } mjit_warning("timed out to wait for JIT finish"); break; } - CRITICAL_SECTION_START(3, "in rb_mjit_wait_call for a client wakeup"); - rb_native_cond_broadcast(&mjit_worker_wakeup); - CRITICAL_SECTION_FINISH(3, "in rb_mjit_wait_call for a client wakeup"); rb_thread_wait_for(tv); } } @@ -377,24 +474,8 @@ mjit_recompile(const rb_iseq_t *iseq) RSTRING_PTR(rb_iseq_path(iseq)), FIX2INT(ISEQ_BODY(iseq)->location.first_lineno)); assert(ISEQ_BODY(iseq)->jit_unit != NULL); - if (UNLIKELY(mjit_opts.wait)) { - CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq"); - remove_from_list(ISEQ_BODY(iseq)->jit_unit, &active_units); - add_to_list(ISEQ_BODY(iseq)->jit_unit, &stale_units); - mjit_add_iseq_to_process(iseq, &ISEQ_BODY(iseq)->jit_unit->compile_info, true); - CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq"); - mjit_wait(ISEQ_BODY(iseq)); - } - else { - // Lazily move active_units to stale_units to avoid race conditions around active_units with compaction. - // Also, it's lazily moved to unit_queue as well because otherwise it won't be added to stale_units properly. - // It's good to avoid a race condition between mjit_add_iseq_to_process and mjit_compile around jit_unit as well. - CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq"); - ISEQ_BODY(iseq)->jit_unit->stale_p = true; - ISEQ_BODY(iseq)->jit_func = (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC; - pending_stale_p = true; - CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq"); - } + mjit_add_iseq_to_process(iseq, &ISEQ_BODY(iseq)->jit_unit->compile_info, true); + check_unit_queue(); } // Recompile iseq, disabling send optimization @@ -642,17 +723,6 @@ start_worker(void) { stop_worker_p = false; worker_stopped = false; - - if (!rb_thread_create_mjit_thread(mjit_worker)) { - mjit_enabled = false; - rb_native_mutex_destroy(&mjit_engine_mutex); - rb_native_cond_destroy(&mjit_pch_wakeup); - rb_native_cond_destroy(&mjit_client_wakeup); - rb_native_cond_destroy(&mjit_worker_wakeup); - rb_native_cond_destroy(&mjit_gc_wakeup); - verbose(1, "Failure in MJIT thread initialization\n"); - return false; - } return true; } @@ -816,21 +886,21 @@ mjit_init(const struct mjit_options *opts) // Initialize worker thread start_worker(); + +#ifndef _MSC_VER + // TODO: Consider running C compiler asynchronously + make_pch(); +#endif } static void stop_worker(void) { - rb_execution_context_t *ec = GET_EC(); - - while (!worker_stopped) { - verbose(3, "Sending cancel signal to worker"); - CRITICAL_SECTION_START(3, "in stop_worker"); - stop_worker_p = true; // Setting this inside loop because RUBY_VM_CHECK_INTS may make this false. - rb_native_cond_broadcast(&mjit_worker_wakeup); - CRITICAL_SECTION_FINISH(3, "in stop_worker"); - RUBY_VM_CHECK_INTS(ec); + stop_worker_p = true; + if (current_cc_unit != NULL) { + mjit_wait(current_cc_unit->iseq->body); } + worker_stopped = true; } // Stop JIT-compiling methods but compiled code is kept available. @@ -846,15 +916,12 @@ mjit_pause(bool wait_p) // Flush all queued units with no option or `wait: true` if (wait_p) { - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 1000; - - while (unit_queue.length > 0 && active_units.length < mjit_opts.max_cache_size) { // inverse of condition that waits for mjit_worker_wakeup - CRITICAL_SECTION_START(3, "in mjit_pause for a worker wakeup"); - rb_native_cond_broadcast(&mjit_worker_wakeup); - CRITICAL_SECTION_FINISH(3, "in mjit_pause for a worker wakeup"); - rb_thread_wait_for(tv); + while (current_cc_unit != NULL) { + if (current_cc_unit->compact_p) { + mjit_wait(NULL); + } else { + mjit_wait(current_cc_unit->iseq->body); + } } } @@ -932,21 +999,8 @@ mjit_finish(bool close_handle_p) if (!mjit_enabled) return; - // Wait for pch finish - verbose(2, "Stopping worker thread"); - CRITICAL_SECTION_START(3, "in mjit_finish to wakeup from pch"); - // As our threads are detached, we could just cancel them. But it - // is a bad idea because OS processes (C compiler) started by - // threads can produce temp files. And even if the temp files are - // removed, the used C compiler still complaint about their - // absence. So wait for a clean finish of the threads. - while (pch_status == PCH_NOT_READY) { - verbose(3, "Waiting wakeup from make_pch"); - rb_native_cond_wait(&mjit_pch_wakeup, &mjit_engine_mutex); - } - CRITICAL_SECTION_FINISH(3, "in mjit_finish to wakeup from pch"); - // Stop worker + verbose(2, "Stopping worker thread"); stop_worker(); rb_native_mutex_destroy(&mjit_engine_mutex); @@ -1002,7 +1056,6 @@ mjit_mark(void) // // Because an MJIT worker may modify active_units anytime, we need to convert // the linked list to an array to safely loop its ISeqs without keeping a lock. - CRITICAL_SECTION_START(4, "mjit_mark"); int length = 0; if (compiling_iseqs != NULL) { while (compiling_iseqs[length]) length++; @@ -1023,7 +1076,6 @@ mjit_mark(void) i++; } assert(i == length); - CRITICAL_SECTION_FINISH(4, "mjit_mark"); for (i = 0; i < length; i++) { if (iseqs[i] == NULL) // ISeq is GC-ed @@ -95,14 +95,13 @@ RUBY_SYMBOL_EXPORT_END extern void mjit_cancel_all(const char *reason); extern bool mjit_compile(FILE *f, const rb_iseq_t *iseq, const char *funcname, int id); extern void mjit_init(const struct mjit_options *opts); -extern void mjit_gc_start_hook(void); -extern void mjit_gc_exit_hook(void); extern void mjit_free_iseq(const rb_iseq_t *iseq); extern void mjit_update_references(const rb_iseq_t *iseq); extern void mjit_mark(void); extern struct mjit_cont *mjit_cont_new(rb_execution_context_t *ec); extern void mjit_cont_free(struct mjit_cont *cont); extern void mjit_mark_cc_entries(const struct rb_iseq_constant_body *const body); +extern void mjit_notify_waitpid(int status); # ifdef MJIT_HEADER NOINLINE(static COLDFUNC VALUE mjit_exec_slowpath(rb_execution_context_t *ec, const rb_iseq_t *iseq, struct rb_iseq_constant_body *body)); @@ -215,8 +214,6 @@ void mjit_finish(bool close_handle_p); static inline void mjit_cancel_all(const char *reason){} static inline struct mjit_cont *mjit_cont_new(rb_execution_context_t *ec){return NULL;} static inline void mjit_cont_free(struct mjit_cont *cont){} -static inline void mjit_gc_start_hook(void){} -static inline void mjit_gc_exit_hook(void){} static inline void mjit_free_iseq(const rb_iseq_t *iseq){} static inline void mjit_mark(void){} static inline VALUE mjit_exec(rb_execution_context_t *ec) { return Qundef; /* unreachable */ } diff --git a/mjit_worker.c b/mjit_worker.c index 1d0150a2c8..6228031696 100644 --- a/mjit_worker.c +++ b/mjit_worker.c @@ -164,8 +164,8 @@ struct rb_mjit_unit { #endif // Only used by unload_units. Flag to check this unit is currently on stack or not. bool used_code_p; - // True if this is still in active_units but it's to be lazily removed - bool stale_p; + // True if it's a unit for JIT compaction + bool compact_p; // mjit_compile's optimization switches struct rb_mjit_compile_info compile_info; // captured CC values, they should be marked with iseq. @@ -191,7 +191,7 @@ extern void rb_native_cond_broadcast(rb_nativethread_cond_t *cond); extern void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex); // process.c -extern rb_pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options, rb_nativethread_cond_t *cond); +extern void mjit_add_waiting_pid(rb_vm_t *vm, rb_pid_t pid); // A copy of MJIT portion of MRI options since MJIT initialization. We // need them as MJIT threads still can work when the most MRI data were @@ -227,12 +227,6 @@ static rb_nativethread_cond_t mjit_client_wakeup; static rb_nativethread_cond_t mjit_worker_wakeup; // A thread conditional to wake up workers if at the end of GC. static rb_nativethread_cond_t mjit_gc_wakeup; -// Greater than 0 when GC is working. -static int in_gc = 0; -// True when JIT is working. -static bool in_jit = false; -// True when active_units has at least one stale_p=true unit. -static bool pending_stale_p = false; // The times when unload_units is requested. unload_units is called after some requests. static int unload_requests = 0; // The total number of unloaded units. @@ -259,6 +253,13 @@ static rb_pid_t pch_owner_pid; // shared by the workers and the pch thread. static enum {PCH_NOT_READY, PCH_FAILED, PCH_SUCCESS} pch_status; +// The start timestamp of current compilation +static double current_cc_ms = 0.0; // TODO: make this part of unit? +// Currently compiling MJIT unit +static struct rb_mjit_unit *current_cc_unit = NULL; +// PID of currently running C compiler process. 0 if nothing is running. +static pid_t current_cc_pid = 0; // TODO: make this part of unit? + #ifndef _MSC_VER // Name of the header file. static char *header_file; @@ -492,12 +493,6 @@ real_ms_time(void) static struct rb_mjit_unit * get_from_list(struct rb_mjit_unit_list *list) { - while (in_gc) { - verbose(3, "Waiting wakeup from GC"); - rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex); - } - in_jit = true; // Lock GC - // Find iseq with max total_calls struct rb_mjit_unit *unit = NULL, *next, *best = NULL; ccan_list_for_each_safe(&list->head, unit, next, unode) { @@ -512,10 +507,6 @@ get_from_list(struct rb_mjit_unit_list *list) } } - in_jit = false; // Unlock GC - verbose(3, "Sending wakeup signal to client in a mjit-worker for GC"); - rb_native_cond_signal(&mjit_client_wakeup); - if (best) { remove_from_list(best, list); } @@ -632,18 +623,9 @@ static int exec_process(const char *path, char *const argv[]) { int stat, exit_code = -2; - rb_vm_t *vm = WAITPID_USE_SIGCHLD ? GET_VM() : 0; - rb_nativethread_cond_t cond; - - if (vm) { - rb_native_cond_initialize(&cond); - rb_native_mutex_lock(&vm->waitpid_lock); - } - pid_t pid = start_process(path, argv); for (;pid > 0;) { - pid_t r = vm ? ruby_waitpid_locked(vm, pid, &stat, 0, &cond) - : waitpid(pid, &stat, 0); + pid_t r = waitpid(pid, &stat, 0); if (r == -1) { if (errno == EINTR) continue; fprintf(stderr, "[%"PRI_PIDT_PREFIX"d] waitpid(%lu): %s (SIGCHLD=%d,%u)\n", @@ -662,11 +644,6 @@ exec_process(const char *path, char *const argv[]) } } } - - if (vm) { - rb_native_mutex_unlock(&vm->waitpid_lock); - rb_native_cond_destroy(&cond); - } return exit_code; } @@ -881,16 +858,13 @@ make_pch(void) char **args = form_args(4, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, rest_args); if (args == NULL) { mjit_warning("making precompiled header failed on forming args"); - CRITICAL_SECTION_START(3, "in make_pch"); pch_status = PCH_FAILED; - CRITICAL_SECTION_FINISH(3, "in make_pch"); return; } int exit_code = exec_process(cc_path, args); free(args); - CRITICAL_SECTION_START(3, "in make_pch"); if (exit_code == 0) { pch_status = PCH_SUCCESS; } @@ -898,14 +872,10 @@ make_pch(void) mjit_warning("Making precompiled header failed on compilation. Stopping MJIT worker..."); pch_status = PCH_FAILED; } - /* wakeup `mjit_finish` */ - rb_native_cond_broadcast(&mjit_pch_wakeup); - CRITICAL_SECTION_FINISH(3, "in make_pch"); } -// Compile .c file to .so file. It returns true if it succeeds. (non-mswin) -static bool -compile_c_to_so(const char *c_file, const char *so_file) +static pid_t +start_compiling_c_to_so(const char *c_file, const char *so_file) { const char *so_args[] = { "-o", so_file, @@ -919,21 +889,27 @@ compile_c_to_so(const char *c_file, const char *so_file) }; char **args = form_args(7, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS, cc_added_args, so_args, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS); - if (args == NULL) return false; - int exit_code = exec_process(cc_path, args); + if (args == NULL) return -1; + + rb_vm_t *vm = GET_VM(); + rb_native_mutex_lock(&vm->waitpid_lock); + + pid_t pid = start_process(cc_path, args); + mjit_add_waiting_pid(vm, pid); + + rb_native_mutex_unlock(&vm->waitpid_lock); + free(args); - if (exit_code != 0) { - verbose(2, "compile_c_to_so: failed to compile .c to .so: %d", exit_code); - } - return exit_code == 0; + return pid; } #endif // _MSC_VER #if USE_JIT_COMPACTION static void compile_prelude(FILE *f); +// Compile all JIT code into a single .c file static bool -compile_compact_jit_code(char* c_file) +mjit_compact(char* c_file) { FILE *f; int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600); @@ -946,32 +922,6 @@ compile_compact_jit_code(char* c_file) compile_prelude(f); - // wait until mjit_gc_exit_hook is called - CRITICAL_SECTION_START(3, "before mjit_compile to wait GC finish"); - while (in_gc) { - verbose(3, "Waiting wakeup from GC"); - rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex); - } - // We need to check again here because we could've waited on GC above - bool iseq_gced = false; - struct rb_mjit_unit *child_unit = 0, *next; - ccan_list_for_each_safe(&active_units.head, child_unit, next, unode) { - if (child_unit->iseq == NULL) { // ISeq is GC-ed - iseq_gced = true; - verbose(1, "JIT compaction: A method for JIT code u%d is obsoleted. Compaction will be skipped.", child_unit->id); - remove_from_list(child_unit, &active_units); - free_unit(child_unit); // unload it without waiting for throttled unload_units to retry compaction quickly - } - } - in_jit = !iseq_gced; - CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish"); - if (!in_jit) { - fclose(f); - if (!mjit_opts.save_temps) - remove_file(c_file); - return false; - } - // This entire loop lock GC so that we do not need to consider a case that // ISeq is GC-ed in a middle of re-compilation. It takes 3~4ms with 100 methods // on my machine. It's not too bad compared to compilation time of C (7200~8000ms), @@ -980,10 +930,9 @@ compile_compact_jit_code(char* c_file) // TODO: Consider using a more granular lock after we implement inlining across // compacted functions (not done yet). bool success = true; + struct rb_mjit_unit *child_unit = 0; ccan_list_for_each(&active_units.head, child_unit, unode) { - CRITICAL_SECTION_START(3, "before set_compiling_iseqs"); success &= set_compiling_iseqs(child_unit->iseq); - CRITICAL_SECTION_FINISH(3, "after set_compiling_iseqs"); if (!success) continue; char funcname[MAXPATHLEN]; @@ -1000,86 +949,68 @@ compile_compact_jit_code(char* c_file) fprintf(f, "\n/* %s%s%s:%ld */\n", iseq_label, sep, iseq_path, iseq_lineno); success &= mjit_compile(f, child_unit->iseq, funcname, child_unit->id); - CRITICAL_SECTION_START(3, "before compiling_iseqs free"); free_compiling_iseqs(); - CRITICAL_SECTION_FINISH(3, "after compiling_iseqs free"); } - // release blocking mjit_gc_start_hook - CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC"); - in_jit = false; - verbose(3, "Sending wakeup signal to client in a mjit-worker for GC"); - rb_native_cond_signal(&mjit_client_wakeup); - CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC"); - fclose(f); return success; } // Compile all cached .c files and build a single .so file. Reload all JIT func from it. // This improves the code locality for better performance in terms of iTLB and iCache. -static void -compact_all_jit_code(void) +static pid_t +start_mjit_compact(struct rb_mjit_unit *unit) { - struct rb_mjit_unit *unit, *cur = 0; static const char c_ext[] = ".c"; static const char so_ext[] = DLEXT; char c_file[MAXPATHLEN], so_file[MAXPATHLEN]; - // Abnormal use case of rb_mjit_unit that doesn't have ISeq - unit = calloc(1, sizeof(struct rb_mjit_unit)); // To prevent GC, don't use ZALLOC - if (unit == NULL) return; - unit->id = current_unit_num++; sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext); sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext); - bool success = compile_compact_jit_code(c_file); - double start_time = real_ms_time(); + bool success = mjit_compact(c_file); if (success) { - success = compile_c_to_so(c_file, so_file); - if (!mjit_opts.save_temps) - remove_file(c_file); + return start_compiling_c_to_so(c_file, so_file); } + return -1; +} + +static void +load_compact_funcs_from_so(struct rb_mjit_unit *unit, char *c_file, char *so_file) +{ + struct rb_mjit_unit *cur = 0; double end_time = real_ms_time(); - if (success) { - void *handle = dlopen(so_file, RTLD_NOW); - if (handle == NULL) { - mjit_warning("failure in loading code from compacted '%s': %s", so_file, dlerror()); - free(unit); - return; - } - unit->handle = handle; + void *handle = dlopen(so_file, RTLD_NOW); + if (handle == NULL) { + mjit_warning("failure in loading code from compacted '%s': %s", so_file, dlerror()); + free(unit); + return; + } + unit->handle = handle; - // lazily dlclose handle (and .so file for win32) on `mjit_finish()`. - add_to_list(unit, &compact_units); + // lazily dlclose handle (and .so file for win32) on `mjit_finish()`. + add_to_list(unit, &compact_units); - if (!mjit_opts.save_temps) - remove_so_file(so_file, unit); + if (!mjit_opts.save_temps) + remove_so_file(so_file, unit); - CRITICAL_SECTION_START(3, "in compact_all_jit_code to read list"); - ccan_list_for_each(&active_units.head, cur, unode) { - void *func; - char funcname[MAXPATHLEN]; - sprint_funcname(funcname, cur); + ccan_list_for_each(&active_units.head, cur, unode) { + void *func; + char funcname[MAXPATHLEN]; + sprint_funcname(funcname, cur); - if ((func = dlsym(handle, funcname)) == NULL) { - mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror()); - continue; - } + if ((func = dlsym(handle, funcname)) == NULL) { + mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror()); + continue; + } - if (cur->iseq) { // Check whether GCed or not - // Usage of jit_code might be not in a critical section. - MJIT_ATOMIC_SET(ISEQ_BODY(cur->iseq)->jit_func, (mjit_func_t)func); - } + if (cur->iseq) { // Check whether GCed or not + // Usage of jit_code might be not in a critical section. + MJIT_ATOMIC_SET(ISEQ_BODY(cur->iseq)->jit_func, (mjit_func_t)func); } - CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to read list"); - verbose(1, "JIT compaction (%.1fms): Compacted %d methods %s -> %s", end_time - start_time, active_units.length, c_file, so_file); - } - else { - free(unit); - verbose(1, "JIT compaction failure (%.1fms): Failed to compact methods", end_time - start_time); } + verbose(1, "JIT compaction (%.1fms): Compacted %d methods %s -> %s", end_time - current_cc_ms, active_units.length, c_file, so_file); } #endif // USE_JIT_COMPACTION @@ -1144,6 +1075,62 @@ compile_prelude(FILE *f) // Compile ISeq in UNIT and return function pointer of JIT-ed code. // It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong. +static pid_t +start_mjit_compile(struct rb_mjit_unit *unit) +{ + static const char c_ext[] = ".c"; + static const char so_ext[] = DLEXT; + char c_file[MAXPATHLEN], so_file[MAXPATHLEN], funcname[MAXPATHLEN]; + + sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext); + sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext); + sprint_funcname(funcname, unit); + + FILE *f; + int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600); + if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { + int e = errno; + if (fd >= 0) (void)close(fd); + verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e)); + return -1; + } + + // print #include of MJIT header, etc. + compile_prelude(f); + + // This is no longer necessary. TODO: Just reference the ISeq directly in the compiler. + if (!set_compiling_iseqs(unit->iseq)) return -1; + + // To make MJIT worker thread-safe against GC.compact, copy ISeq values while `in_jit` is true. + long iseq_lineno = 0; + if (FIXNUM_P(ISEQ_BODY(unit->iseq)->location.first_lineno)) + // FIX2INT may fallback to rb_num2long(), which is a method call and dangerous in MJIT worker. So using only FIX2LONG. + iseq_lineno = FIX2LONG(ISEQ_BODY(unit->iseq)->location.first_lineno); + char *iseq_label = alloca(RSTRING_LEN(ISEQ_BODY(unit->iseq)->location.label) + 1); + char *iseq_path = alloca(RSTRING_LEN(rb_iseq_path(unit->iseq)) + 1); + strcpy(iseq_label, RSTRING_PTR(ISEQ_BODY(unit->iseq)->location.label)); + strcpy(iseq_path, RSTRING_PTR(rb_iseq_path(unit->iseq))); + + verbose(2, "start compilation: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file); + fprintf(f, "/* %s@%s:%ld */\n\n", iseq_label, iseq_path, iseq_lineno); + bool success = mjit_compile(f, unit->iseq, funcname, unit->id); + + free_compiling_iseqs(); + + fclose(f); + if (!success) { + if (!mjit_opts.save_temps) + remove_file(c_file); + verbose(1, "JIT failure: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file); + return -1; + } + + return start_compiling_c_to_so(c_file, so_file); +} + +#ifdef _WIN32 +// Compile ISeq in UNIT and return function pointer of JIT-ed code. +// It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong. static mjit_func_t convert_unit_to_func(struct rb_mjit_unit *unit) { @@ -1167,18 +1154,7 @@ convert_unit_to_func(struct rb_mjit_unit *unit) // print #include of MJIT header, etc. compile_prelude(f); - // wait until mjit_gc_exit_hook is called - CRITICAL_SECTION_START(3, "before mjit_compile to wait GC finish"); - while (in_gc) { - verbose(3, "Waiting wakeup from GC"); - rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex); - } - // We need to check again here because we could've waited on GC above - in_jit = (unit->iseq != NULL); - if (in_jit) - in_jit &= set_compiling_iseqs(unit->iseq); - CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish"); - if (!in_jit) { + if (!set_compiling_iseqs(unit->iseq)) { fclose(f); if (!mjit_opts.save_temps) remove_file(c_file); @@ -1200,12 +1176,7 @@ convert_unit_to_func(struct rb_mjit_unit *unit) bool success = mjit_compile(f, unit->iseq, funcname, unit->id); // release blocking mjit_gc_start_hook - CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC"); free_compiling_iseqs(); - in_jit = false; - verbose(3, "Sending wakeup signal to client in a mjit-worker for GC"); - rb_native_cond_signal(&mjit_client_wakeup); - CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC"); fclose(f); if (!success) { @@ -1236,6 +1207,7 @@ convert_unit_to_func(struct rb_mjit_unit *unit) } return (mjit_func_t)func; } +#endif // To see cc_entries using index returned by `mjit_capture_cc_entries` in mjit_compile.c const struct rb_callcache ** @@ -1382,119 +1354,3 @@ unload_units(void) } static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info, bool worker_p); - -// The function implementing a worker. It is executed in a separate -// thread by rb_thread_create_mjit_thread. It compiles precompiled header -// and then compiles requested ISeqs. -void -mjit_worker(void) -{ - // 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; - -#ifndef _MSC_VER - if (pch_status == PCH_NOT_READY) { - make_pch(); - } -#endif - if (pch_status == PCH_FAILED) { - mjit_enabled = false; - CRITICAL_SECTION_START(3, "in worker to update worker_stopped"); - worker_stopped = true; - verbose(3, "Sending wakeup signal to client in a mjit-worker"); - rb_native_cond_signal(&mjit_client_wakeup); - CRITICAL_SECTION_FINISH(3, "in worker to update worker_stopped"); - return; // TODO: do the same thing in the latter half of mjit_finish - } - - // main worker loop - while (!stop_worker_p) { - struct rb_mjit_unit *unit; - - // Wait until a unit becomes available - CRITICAL_SECTION_START(3, "in worker dequeue"); - while ((ccan_list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) { - rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex); - verbose(3, "Getting wakeup from client"); - - // Lazily move active_units to stale_units to avoid race conditions around active_units with compaction - if (pending_stale_p) { - pending_stale_p = false; - struct rb_mjit_unit *next; - ccan_list_for_each_safe(&active_units.head, unit, next, unode) { - if (unit->stale_p) { - unit->stale_p = false; - remove_from_list(unit, &active_units); - add_to_list(unit, &stale_units); - // Lazily put it to unit_queue as well to avoid race conditions on jit_unit with mjit_compile. - mjit_add_iseq_to_process(unit->iseq, &ISEQ_BODY(unit->iseq)->jit_unit->compile_info, true); - } - } - } - - // Unload some units as needed - if (unload_requests >= throttle_threshold) { - while (in_gc) { - verbose(3, "Waiting wakeup from GC"); - rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex); - } - in_jit = true; // Lock GC - - RB_DEBUG_COUNTER_INC(mjit_unload_units); - unload_units(); - unload_requests = 0; - - in_jit = false; // Unlock GC - verbose(3, "Sending wakeup signal to client in a mjit-worker for GC"); - rb_native_cond_signal(&mjit_client_wakeup); - } - 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); - } - } - unit = get_from_list(&unit_queue); - CRITICAL_SECTION_FINISH(3, "in worker dequeue"); - - if (unit) { - // JIT compile - mjit_func_t func = convert_unit_to_func(unit); - (void)RB_DEBUG_COUNTER_INC_IF(mjit_compile_failures, func == (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC); - - CRITICAL_SECTION_START(3, "in jit func replace"); - while (in_gc) { // Make sure we're not GC-ing when touching ISeq - verbose(3, "Waiting wakeup from GC"); - rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex); - } - if (unit->iseq) { // Check whether GCed or not - if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) { - add_to_list(unit, &active_units); - } - // Usage of jit_code might be not in a critical section. - MJIT_ATOMIC_SET(ISEQ_BODY(unit->iseq)->jit_func, func); - } - else { - free_unit(unit); - } - CRITICAL_SECTION_FINISH(3, "in jit func replace"); - -#if USE_JIT_COMPACTION - // Combine .o files to one .so and reload all jit_func to improve memory locality. - 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 - compact_all_jit_code(); - } -#endif - } - } - - // To keep mutex unlocked when it is destroyed by mjit_finish, don't wrap CRITICAL_SECTION here. - worker_stopped = true; -} @@ -1091,6 +1091,15 @@ void rb_sigwait_sleep(const rb_thread_t *, int fd, const rb_hrtime_t *); void rb_sigwait_fd_put(const rb_thread_t *, int fd); void rb_thread_sleep_interruptible(void); +#if USE_MJIT +static struct waitpid_state mjit_waitpid_state; + +// variables shared with thread.c +// TODO: implement the same thing with postponed_job and obviate these variables +bool mjit_waitpid_finished = false; +int mjit_waitpid_status = 0; +#endif + static int waitpid_signal(struct waitpid_state *w) { @@ -1098,12 +1107,13 @@ waitpid_signal(struct waitpid_state *w) rb_threadptr_interrupt(rb_ec_thread_ptr(w->ec)); return TRUE; } - else { /* ruby_waitpid_locked */ - if (w->cond) { - rb_native_cond_signal(w->cond); - return TRUE; - } +#if USE_MJIT + else if (w == &mjit_waitpid_state && w->ret) { /* mjit_add_waiting_pid */ + mjit_waitpid_finished = true; + mjit_waitpid_status = w->status; + return TRUE; } +#endif return FALSE; } @@ -1199,68 +1209,18 @@ waitpid_state_init(struct waitpid_state *w, rb_pid_t pid, int options) w->status = 0; } -static const rb_hrtime_t * -sigwait_sleep_time(void) -{ - if (SIGCHLD_LOSSY) { - static const rb_hrtime_t busy_wait = 100 * RB_HRTIME_PER_MSEC; - - return &busy_wait; - } - return 0; -} - +#if USE_MJIT /* * must be called with vm->waitpid_lock held, this is not interruptible */ -rb_pid_t -ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options, - rb_nativethread_cond_t *cond) +void +mjit_add_waiting_pid(rb_vm_t *vm, rb_pid_t pid) { - struct waitpid_state w; - - assert(!ruby_thread_has_gvl_p() && "must not have GVL"); - - waitpid_state_init(&w, pid, options); - if (w.pid > 0 || ccan_list_empty(&vm->waiting_pids)) - w.ret = do_waitpid(w.pid, &w.status, w.options | WNOHANG); - if (w.ret) { - if (w.ret == -1) w.errnum = errno; - } - else { - int sigwait_fd = -1; - - w.ec = 0; - ccan_list_add(w.pid > 0 ? &vm->waiting_pids : &vm->waiting_grps, &w.wnode); - do { - if (sigwait_fd < 0) - sigwait_fd = rb_sigwait_fd_get(0); - - if (sigwait_fd >= 0) { - w.cond = 0; - rb_native_mutex_unlock(&vm->waitpid_lock); - rb_sigwait_sleep(0, sigwait_fd, sigwait_sleep_time()); - rb_native_mutex_lock(&vm->waitpid_lock); - } - else { - w.cond = cond; - rb_native_cond_wait(w.cond, &vm->waitpid_lock); - } - } while (!w.ret); - ccan_list_del(&w.wnode); - - /* we're done, maybe other waitpid callers are not: */ - if (sigwait_fd >= 0) { - rb_sigwait_fd_put(0, sigwait_fd); - sigwait_fd_migrate_sleeper(vm); - } - } - if (status) { - *status = w.status; - } - if (w.ret == -1) errno = w.errnum; - return w.ret; + waitpid_state_init(&mjit_waitpid_state, pid, 0); + mjit_waitpid_state.ec = 0; // switch the behavior of waitpid_signal + ccan_list_add(&vm->waiting_pids, &mjit_waitpid_state.wnode); } +#endif static VALUE waitpid_sleep(VALUE x) diff --git a/test/ruby/test_rubyvm_mjit.rb b/test/ruby/test_rubyvm_mjit.rb index 92733a2b15..8ca0fb9ef2 100644 --- a/test/ruby/test_rubyvm_mjit.rb +++ b/test/ruby/test_rubyvm_mjit.rb @@ -87,7 +87,11 @@ class TestRubyVMMJIT < Test::Unit::TestCase print RubyVM::MJIT.pause(wait: false) EOS assert_equal('truefalse', out) - assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10) + if RUBY_PLATFORM.match?(/mswin|mingw/) # MJIT synchronously compiles methods on Windows + assert_equal(10, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size) + else + assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10) + end end def test_resume @@ -2233,6 +2233,12 @@ threadptr_get_interrupts(rb_thread_t *th) return interrupt & (rb_atomic_t)~ec->interrupt_mask; } +#if USE_MJIT +// process.c +extern bool mjit_waitpid_finished; +extern int mjit_waitpid_status; +#endif + MJIT_FUNC_EXPORTED int rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing) { @@ -2280,6 +2286,15 @@ rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing) ret |= rb_signal_exec(th, sig); } th->status = prev_status; + +#if USE_MJIT + // Handle waitpid_signal for MJIT issued by ruby_sigchld_handler. This needs to be done + // outside ruby_sigchld_handler to avoid recursively relying on the SIGCHLD handler. + if (mjit_waitpid_finished) { + mjit_waitpid_finished = false; + mjit_notify_waitpid(mjit_waitpid_status); + } +#endif } /* exception from another thread */ |