ruby-changes:72556
From: Takashi <ko1@a...>
Date: Fri, 15 Jul 2022 12:35:14 +0900 (JST)
Subject: [ruby-changes:72556] 439d31bc77 (master): MJIT: Merge mjit_worker.c back to mjit.c (#6138)
https://git.ruby-lang.org/ruby.git/commit/?id=439d31bc77 From 439d31bc777a6f9c9354dbe9a82495a9b4fa04ae Mon Sep 17 00:00:00 2001 From: Takashi Kokubun <takashikkbn@g...> Date: Thu, 14 Jul 2022 20:34:46 -0700 Subject: MJIT: Merge mjit_worker.c back to mjit.c (#6138) Since #6006, we no longer avoid executing GC on mjit_worker.c and thus there's no need to carefully change how we write code whether you're in mjit.c or mjit_worker.c anymore. --- common.mk | 1 - mjit.c | 1269 ++++++++++++++++++++++++++++++++++++++++++++++- mjit_worker.c | 1290 ------------------------------------------------ test/ruby/test_mjit.rb | 2 +- 4 files changed, 1265 insertions(+), 1297 deletions(-) delete mode 100644 mjit_worker.c diff --git a/common.mk b/common.mk index fa6a6e9faf..d99d52f357 100644 --- a/common.mk +++ b/common.mk @@ -9658,7 +9658,6 @@ mjit.$(OBJEXT): {$(VPATH)}mjit.h https://github.com/ruby/ruby/blob/trunk/common.mk#L9658 mjit.$(OBJEXT): {$(VPATH)}mjit.rb mjit.$(OBJEXT): {$(VPATH)}mjit.rbinc mjit.$(OBJEXT): {$(VPATH)}mjit_config.h -mjit.$(OBJEXT): {$(VPATH)}mjit_worker.c mjit.$(OBJEXT): {$(VPATH)}node.h mjit.$(OBJEXT): {$(VPATH)}onigmo.h mjit.$(OBJEXT): {$(VPATH)}oniguruma.h diff --git a/mjit.c b/mjit.c index a5c89a1ced..b52c5fad9f 100644 --- a/mjit.c +++ b/mjit.c @@ -6,10 +6,63 @@ https://github.com/ruby/ruby/blob/trunk/mjit.c#L6 **********************************************************************/ -// Functions in this file are never executed on MJIT worker thread. -// So you can safely use Ruby methods and GC in this file. - -// To share variables privately, include mjit_worker.c instead of linking. +/* We utilize widely used C compilers (GCC and LLVM Clang) to + implement MJIT. We feed them a C code generated from ISEQ. The + industrial C compilers are slower than regular JIT engines. + Generated code performance of the used C compilers has a higher + priority over the compilation speed. + + So our major goal is to minimize the ISEQ compilation time when we + use widely optimization level (-O2). It is achieved by + + o Using a precompiled version of the header + o Keeping all files in `/tmp`. On modern Linux `/tmp` is a file + system in memory. So it is pretty fast + o Implementing MJIT as a multi-threaded code because we want to + compile ISEQs in parallel with iseq execution to speed up Ruby + code execution. MJIT has one thread (*worker*) to do + parallel compilations: + o It prepares a precompiled code of the minimized header. + It starts at the MRI execution start + o It generates PIC object files of ISEQs + o It takes one JIT unit from a priority queue unless it is empty. + o It translates the JIT unit ISEQ into C-code using the precompiled + header, calls CC and load PIC code when it is ready + o Currently MJIT put ISEQ in the queue when ISEQ is called + o MJIT can reorder ISEQs in the queue if some ISEQ has been called + many times and its compilation did not start yet + o MRI reuses the machine code if it already exists for ISEQ + o The machine code we generate can stop and switch to the ISEQ + interpretation if some condition is not satisfied as the machine + code can be speculative or some exception raises + o Speculative machine code can be canceled. + + Here is a diagram showing the MJIT organization: + + _______ + |header | + |_______| + | MRI building + --------------|---------------------------------------- + | MRI execution + | + _____________|_____ + | | | + | ___V__ | CC ____________________ + | | |----------->| precompiled header | + | | | | |____________________| + | | | | | + | | MJIT | | | + | | | | | + | | | | ____V___ CC __________ + | |______|----------->| C code |--->| .so file | + | | |________| |__________| + | | | + | | | + | MRI machine code |<----------------------------- + |___________________| loading + +*/ #include "ruby/internal/config.h" // defines USE_MJIT @@ -27,7 +80,1213 @@ https://github.com/ruby/ruby/blob/trunk/mjit.c#L80 #include "vm_sync.h" #include "ractor_core.h" -#include "mjit_worker.c" +#ifdef __sun +#define __EXTENSIONS__ 1 +#endif + +#include "vm_core.h" +#include "vm_callinfo.h" +#include "mjit.h" +#include "gc.h" +#include "ruby_assert.h" +#include "ruby/debug.h" +#include "ruby/thread.h" +#include "ruby/version.h" +#include "builtin.h" +#include "insns.inc" +#include "insns_info.inc" +#include "internal/compile.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#else +#include <sys/wait.h> +#include <sys/time.h> +#include <dlfcn.h> +#endif +#include <errno.h> +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif +#include "dln.h" + +#include "ruby/util.h" +#undef strdup // ruby_strdup may trigger GC + +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + +#ifdef _WIN32 +#define dlopen(name,flag) ((void*)LoadLibrary(name)) +#define dlerror() strerror(rb_w32_map_errno(GetLastError())) +#define dlsym(handle,name) ((void*)GetProcAddress((handle),(name))) +#define dlclose(handle) (!FreeLibrary(handle)) +#define RTLD_NOW -1 + +#define waitpid(pid,stat_loc,options) (WaitForSingleObject((HANDLE)(pid), INFINITE), GetExitCodeProcess((HANDLE)(pid), (LPDWORD)(stat_loc)), CloseHandle((HANDLE)pid), (pid)) +#define WIFEXITED(S) ((S) != STILL_ACTIVE) +#define WEXITSTATUS(S) (S) +#define WIFSIGNALED(S) (0) +typedef intptr_t pid_t; +#endif + +// Atomically set function pointer if possible. +#define MJIT_ATOMIC_SET(var, val) (void)ATOMIC_PTR_EXCHANGE(var, val) + +#define MJIT_TMP_PREFIX "_ruby_mjit_" + +// JIT compaction requires the header transformation because linking multiple .o files +// doesn't work without having `static` in the same function definitions. We currently +// don't support transforming the MJIT header on Windows. +#ifdef _WIN32 +# define USE_JIT_COMPACTION 0 +#else +# define USE_JIT_COMPACTION 1 +#endif + +// The unit structure that holds metadata of ISeq for MJIT. +struct rb_mjit_unit { + struct ccan_list_node unode; + // Unique order number of unit. + int id; + // Dlopen handle of the loaded object file. + void *handle; + rb_iseq_t *iseq; +#if defined(_WIN32) + // DLL cannot be removed while loaded on Windows. If this is set, it'll be lazily deleted. + char *so_file; +#endif + // Only used by unload_units. Flag to check this unit is currently on stack or not. + bool used_code_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. + const struct rb_callcache **cc_entries; + unsigned int cc_entries_size; // ISEQ_BODY(iseq)->ci_size + ones of inlined iseqs +}; + +// Linked list of struct rb_mjit_unit. +struct rb_mjit_unit_list { + struct ccan_list_head head; + int length; // the list length +}; + +extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock); +extern void rb_native_mutex_unlock(rb_nativethread_lock_t *lock); +extern void rb_native_mutex_initialize(rb_nativethread_lock_t *lock); +extern void rb_native_mutex_destroy(rb_nativethread_lock_t *lock); + +extern void rb_native_cond_initialize(rb_nativethread_cond_t *cond); +extern void rb_native_cond_destroy(rb_nativethread_cond_t *cond); +extern void rb_native_cond_signal(rb_nativethread_cond_t *cond); +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 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 +// freed. +struct mjit_options mjit_opts; + +// true if MJIT is enabled. +bool mjit_enabled = false; +// true if JIT-ed code should be called. When `ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS` +// and `mjit_call_p == false`, any JIT-ed code execution is cancelled as soon as possible. +bool mjit_call_p = false; + +// Priority queue of iseqs waiting for JIT compilation. +// This variable is a pointer to head unit of the queue. +static struct rb_mjit_unit_list unit_queue = { CCAN_LIST_HEAD_INIT(unit_queue.head) }; +// List of units which are successfully compiled. +static struct rb_mjit_unit_list active_units = { CCAN_LIST_HEAD_INIT(active_units.head) }; +// List of compacted so files which will be cleaned up by `free_list()` in `mjit_finish()`. +static struct rb_mjit_unit_list compact_units = { CCAN_LIST_HEAD_INIT(compact_units.head) }; +// List of units before recompilation and just waiting for dlclose(). +static struct rb_mjit_unit_list stale_units = { CCAN_LIST_HEAD_INIT(stale_units.head) }; +// The number of so far processed ISEQs, used to generate unique id. +static int current_unit_num; +// A mutex for conitionals and critical sections. +static rb_nativethread_lock_t mjit_engine_mutex; +// A thread conditional to wake up `mjit_finish` at the end of PCH thread. +static rb_nativethread_cond_t mjit_pch_wakeup; +// A thread conditional to wake up the client if there is a change in +// executed unit status. +static rb_nativethread_cond_t mjit_client_wakeup; +// A thread conditional to wake up a worker if there we have something +// to add or we need to stop (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/