diff options
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | common.mk | 17 | ||||
-rw-r--r-- | cont.c | 9 | ||||
-rw-r--r-- | eval.c | 3 | ||||
-rw-r--r-- | gc.c | 5 | ||||
-rw-r--r-- | iseq.c | 2 | ||||
-rw-r--r-- | mjit.c | 1219 | ||||
-rw-r--r-- | mjit.h | 138 | ||||
-rw-r--r-- | mjit_compile.c | 18 | ||||
-rw-r--r-- | ruby.c | 75 | ||||
-rw-r--r-- | test/lib/zombie_hunter.rb | 4 | ||||
-rw-r--r-- | test/ruby/test_io.rb | 3 | ||||
-rw-r--r-- | test/ruby/test_rubyoptions.rb | 2 | ||||
-rw-r--r-- | thread.c | 42 | ||||
-rw-r--r-- | thread_pthread.c | 189 | ||||
-rw-r--r-- | thread_win32.c | 85 | ||||
-rw-r--r-- | vm.c | 22 | ||||
-rw-r--r-- | vm_core.h | 8 | ||||
-rw-r--r-- | vm_insnhelper.h | 2 | ||||
-rw-r--r-- | win32/Makefile.sub | 2 |
20 files changed, 1713 insertions, 134 deletions
diff --git a/Makefile.in b/Makefile.in index bca6696c9c..b91f4d2ee4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -69,7 +69,7 @@ debugflags = @debugflags@ warnflags = @warnflags@ @strict_warnflags@ cppflags = @cppflags@ XCFLAGS = @XCFLAGS@ -CPPFLAGS = @CPPFLAGS@ $(INCFLAGS) +CPPFLAGS = @CPPFLAGS@ $(INCFLAGS) -DMJIT_HEADER_BUILD_DIR=\""$(EXTOUT)/include/$(arch)"\" -DLIBRUBYARG_SHARED=\""$(LIBRUBYARG_SHARED)"\" -DLIBRUBY_LIBDIR=\""$(prefix)/lib"\" -DMJIT_HEADER_INSTALL_DIR=\""$(prefix)/include/$(RUBY_BASE_NAME)-$(ruby_version)/$(arch)"\" LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ EXTLDFLAGS = @EXTLDFLAGS@ XLDFLAGS = @XLDFLAGS@ $(EXTLDFLAGS) @@ -96,6 +96,8 @@ COMMONOBJS = array.$(OBJEXT) \ load.$(OBJEXT) \ marshal.$(OBJEXT) \ math.$(OBJEXT) \ + mjit.$(OBJEXT) \ + mjit_compile.$(OBJEXT) \ node.$(OBJEXT) \ numeric.$(OBJEXT) \ object.$(OBJEXT) \ @@ -1522,6 +1524,7 @@ cont.$(OBJEXT): {$(VPATH)}internal.h cont.$(OBJEXT): {$(VPATH)}io.h cont.$(OBJEXT): {$(VPATH)}method.h cont.$(OBJEXT): {$(VPATH)}missing.h +cont.$(OBJEXT): {$(VPATH)}mjit.h cont.$(OBJEXT): {$(VPATH)}node.h cont.$(OBJEXT): {$(VPATH)}onigmo.h cont.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -1773,6 +1776,7 @@ eval.$(OBJEXT): {$(VPATH)}io.h eval.$(OBJEXT): {$(VPATH)}iseq.h eval.$(OBJEXT): {$(VPATH)}method.h eval.$(OBJEXT): {$(VPATH)}missing.h +eval.$(OBJEXT): {$(VPATH)}mjit.h eval.$(OBJEXT): {$(VPATH)}node.h eval.$(OBJEXT): {$(VPATH)}onigmo.h eval.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -1833,6 +1837,7 @@ gc.$(OBJEXT): {$(VPATH)}internal.h gc.$(OBJEXT): {$(VPATH)}io.h gc.$(OBJEXT): {$(VPATH)}method.h gc.$(OBJEXT): {$(VPATH)}missing.h +gc.$(OBJEXT): {$(VPATH)}mjit.h gc.$(OBJEXT): {$(VPATH)}node.h gc.$(OBJEXT): {$(VPATH)}onigmo.h gc.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -1973,6 +1978,7 @@ iseq.$(OBJEXT): {$(VPATH)}iseq.c iseq.$(OBJEXT): {$(VPATH)}iseq.h iseq.$(OBJEXT): {$(VPATH)}method.h iseq.$(OBJEXT): {$(VPATH)}missing.h +iseq.$(OBJEXT): {$(VPATH)}mjit.h iseq.$(OBJEXT): {$(VPATH)}node.h iseq.$(OBJEXT): {$(VPATH)}node_name.inc iseq.$(OBJEXT): {$(VPATH)}onigmo.h @@ -1987,6 +1993,15 @@ iseq.$(OBJEXT): {$(VPATH)}util.h iseq.$(OBJEXT): {$(VPATH)}vm_core.h iseq.$(OBJEXT): {$(VPATH)}vm_debug.h iseq.$(OBJEXT): {$(VPATH)}vm_opts.h +mjit.$(OBJEXT): $(top_srcdir)/revision.h +mjit.$(OBJEXT): {$(VPATH)}mjit.c +mjit.$(OBJEXT): {$(VPATH)}mjit.h +mjit.$(OBJEXT): {$(VPATH)}ruby_assert.h +mjit.$(OBJEXT): {$(VPATH)}version.h +mjit.$(OBJEXT): {$(VPATH)}vm_core.h +mjit_compile.$(OBJEXT): {$(VPATH)}internal.h +mjit_compile.$(OBJEXT): {$(VPATH)}mjit_compile.c +mjit_compile.$(OBJEXT): {$(VPATH)}vm_core.h load.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h load.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h load.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -2460,6 +2475,7 @@ ruby.$(OBJEXT): {$(VPATH)}internal.h ruby.$(OBJEXT): {$(VPATH)}io.h ruby.$(OBJEXT): {$(VPATH)}method.h ruby.$(OBJEXT): {$(VPATH)}missing.h +ruby.$(OBJEXT): {$(VPATH)}mjit.h ruby.$(OBJEXT): {$(VPATH)}node.h ruby.$(OBJEXT): {$(VPATH)}onigmo.h ruby.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -2817,6 +2833,7 @@ vm.$(OBJEXT): {$(VPATH)}io.h vm.$(OBJEXT): {$(VPATH)}iseq.h vm.$(OBJEXT): {$(VPATH)}method.h vm.$(OBJEXT): {$(VPATH)}missing.h +vm.$(OBJEXT): {$(VPATH)}mjit.h vm.$(OBJEXT): {$(VPATH)}node.h vm.$(OBJEXT): {$(VPATH)}onigmo.h vm.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -13,6 +13,7 @@ #include "vm_core.h" #include "gc.h" #include "eval_intern.h" +#include "mjit.h" /* FIBER_USE_NATIVE enables Fiber performance improvement using system * dependent method such as make/setcontext on POSIX system or @@ -110,6 +111,8 @@ typedef struct rb_context_struct { rb_jmpbuf_t jmpbuf; rb_ensure_entry_t *ensure_array; rb_ensure_list_t *ensure_list; + /* Pointer to MJIT info about the continuation. */ + struct mjit_cont *mjit_cont; } rb_context_t; @@ -363,6 +366,9 @@ cont_free(void *ptr) #endif RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr); + if (mjit_init_p && cont->mjit_cont != NULL) { + mjit_cont_free(cont->mjit_cont); + } /* free rb_cont_t or rb_fiber_t */ ruby_xfree(ptr); RUBY_FREE_LEAVE("cont"); @@ -547,6 +553,9 @@ cont_init(rb_context_t *cont, rb_thread_t *th) cont->saved_ec.local_storage = NULL; cont->saved_ec.local_storage_recursive_hash = Qnil; cont->saved_ec.local_storage_recursive_hash_for_trace = Qnil; + if (mjit_init_p) { + cont->mjit_cont = mjit_cont_new(&cont->saved_ec); + } } static rb_context_t * @@ -17,6 +17,7 @@ #include "gc.h" #include "ruby/vm.h" #include "vm_core.h" +#include "mjit.h" #include "probes_helper.h" NORETURN(void rb_raise_jump(VALUE, VALUE)); @@ -218,6 +219,8 @@ ruby_cleanup(volatile int ex) } } + mjit_finish(); /* We still need ISeqs here. */ + ruby_finalize_1(); /* unlock again if finalizer took mutexes. */ @@ -35,6 +35,7 @@ #include <sys/types.h> #include "ruby_assert.h" #include "debug_counter.h" +#include "mjit.h" #undef rb_data_object_wrap @@ -6613,6 +6614,8 @@ gc_enter(rb_objspace_t *objspace, const char *event) GC_ASSERT(during_gc == 0); if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(Qnil); + mjit_gc_start_hook(); + during_gc = TRUE; gc_report(1, objspace, "gc_entr: %s [%s]\n", event, gc_current_status(objspace)); gc_record(objspace, 0, event); @@ -6628,6 +6631,8 @@ gc_exit(rb_objspace_t *objspace, const char *event) gc_record(objspace, 1, event); gc_report(1, objspace, "gc_exit: %s [%s]\n", event, gc_current_status(objspace)); during_gc = FALSE; + + mjit_gc_finish_hook(); } static void * @@ -26,6 +26,7 @@ #include "insns.inc" #include "insns_info.inc" +#include "mjit.h" VALUE rb_cISeq; static VALUE iseqw_new(const rb_iseq_t *iseq); @@ -79,6 +80,7 @@ rb_iseq_free(const rb_iseq_t *iseq) RUBY_FREE_ENTER("iseq"); if (iseq) { + mjit_free_iseq(iseq); /* Notify MJIT */ if (iseq->body) { ruby_xfree((void *)iseq->body->iseq_encoded); ruby_xfree((void *)iseq->body->insns_info.body); diff --git a/mjit.c b/mjit.c new file mode 100644 index 0000000000..1a2ad321c0 --- /dev/null +++ b/mjit.c @@ -0,0 +1,1219 @@ +/********************************************************************** + + mjit.c - Infrastructure for MRI method JIT compiler + + Copyright (C) 2017 Vladimir Makarov <[email protected]>. + +**********************************************************************/ + +/* 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 + + + We don't use SIGCHLD signal and WNOHANG waitpid in MJIT as it + might mess with ruby code dealing with signals. Also as SIGCHLD + signal can be delivered to non-main thread, the stack might have a + constraint. So the correct version of code based on SIGCHLD and + WNOHANG waitpid would be very complicated. */ + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#else +#include <sys/wait.h> +#include <sys/time.h> +#include <dlfcn.h> +#endif + +#include "vm_core.h" +#include "mjit.h" +#include "version.h" +#include "gc.h" +#include "ruby_assert.h" + +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, int flags); +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); + +extern int rb_thread_create_mjit_thread(void (*child_hook)(void), void (*worker_func)(void)); + +#define RB_CONDATTR_CLOCK_MONOTONIC 1 + +#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) (CloseHandle(handle)) +#define RTLD_NOW -1 + +#define waitpid(pid,stat_loc,options) (WaitForSingleObject((HANDLE)(pid), INFINITE), GetExitCodeProcess((HANDLE)(pid), (LPDWORD)(stat_loc))) +#define WIFEXITED(S) ((S) != STILL_ACTIVE) +#define WEXITSTATUS(S) (S) +#define WIFSIGNALED(S) (0) +typedef intptr_t pid_t; +#endif + +/* 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; + +/* The unit structure that holds metadata of ISeq for MJIT. */ +struct rb_mjit_unit { + /* Unique order number of unit. */ + int id; + /* Dlopen handle of the loaded object file. */ + void *handle; + const rb_iseq_t *iseq; + /* Only used by unload_units. Flag to check this unit is currently on stack or not. */ + char used_code_p; +}; + +/* Node of linked list in struct rb_mjit_unit_list. + TODO: use ccan/list for this */ +struct rb_mjit_unit_node { + struct rb_mjit_unit *unit; + struct rb_mjit_unit_node *next, *prev; +}; + +/* Linked list of struct rb_mjit_unit. */ +struct rb_mjit_unit_list { + struct rb_mjit_unit_node *head; + int length; /* the list length */ +}; + +/* TRUE if MJIT is initialized and will be used. */ +int mjit_init_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; +/* List of units which are successfully compiled. */ +static struct rb_mjit_unit_list active_units; +/* 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 MJIT engine. */ +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; +/* True when GC is working. */ +static int in_gc; +/* True when JIT is working. */ +static int in_jit; + +/* Defined in the client thread before starting MJIT threads: */ +/* Used C compiler path. */ +static const char *cc_path; +/* Name of the header file. */ +static char *header_file; +/* Name of the precompiled header file. */ +static char *pch_file; +/* Path of "/tmp", which can be changed to $TMP in MinGW. */ +static char *tmp_dir; +/* Ruby level interface module. */ +VALUE rb_mMJIT; + +/* Return time in milliseconds as a double. */ +static double +real_ms_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return tv.tv_usec / 1000.0 + tv.tv_sec * 1000.0; +} + +/* Make and return copy of STR in the heap. Return NULL in case of a + failure. */ +static char * +get_string(const char *str) +{ + char *res; + + if ((res = xmalloc(strlen(str) + 1)) != NULL) + strcpy(res, str); + return res; +} + +static void +sprint_uniq_filename(char *str, unsigned long id, const char *prefix, const char *suffix) +{ + sprintf(str, "%s/%sp%luu%lu%s", tmp_dir, prefix, (unsigned long) getpid(), id, suffix); +} + +/* Return an unique file name in /tmp with PREFIX and SUFFIX and + number ID. Use getpid if ID == 0. The return file name exists + until the next function call. */ +static char * +get_uniq_filename(unsigned long id, const char *prefix, const char *suffix) +{ + char str[70]; + sprint_uniq_filename(str, id, prefix, suffix); + return get_string(str); +} + +/* Print the arguments according to FORMAT to stderr only if MJIT + verbose option value is more or equal to LEVEL. */ +PRINTF_ARGS(static void, 2, 3) +verbose(int level, const char *format, ...) +{ + va_list args; + + va_start(args, format); + if (mjit_opts.verbose >= level) + vfprintf(stderr, format, args); + va_end(args); + if (mjit_opts.verbose >= level) + fprintf(stderr, "\n"); +} + +/* Return length of NULL-terminated array ARGS excluding the NULL + marker. */ +static size_t +args_len(char *const *args) +{ + size_t i; + + for (i = 0; (args[i]) != NULL;i++) + ; + return i; +} + +/* Concatenate NUM passed NULL-terminated arrays of strings, put the + result (with NULL end marker) into the heap, and return the + result. */ +static char ** +form_args(int num, ...) +{ + va_list argp, argp2; + size_t len, disp; + int i; + char **args, **res; + + va_start(argp, num); + va_copy(argp2, argp); + for (i = len = 0; i < num; i++) { + args = va_arg(argp, char **); + len += args_len(args); + } + va_end(argp); + if ((res = xmalloc((len + 1) * sizeof(char *))) == NULL) + return NULL; + for (i = disp = 0; i < num; i++) { + args = va_arg(argp2, char **); + len = args_len(args); + memmove(res + disp, args, len * sizeof(char *)); + disp += len; + } + res[disp] = NULL; + va_end(argp2); + return res; +} + +/* Start an OS process of executable PATH with arguments ARGV. Return + PID of the process. + TODO: Use the same function in process.c */ +static pid_t +start_process(const char *path, char *const *argv) +{ + pid_t pid; + + if (mjit_opts.verbose >= 2) { + int i; + const char *arg; + + fprintf(stderr, "Starting process: %s", path); + for (i = 0; (arg = argv[i]) != NULL; i++) + fprintf(stderr, " %s", arg); + fprintf(stderr, "\n"); + } +#ifdef _WIN32 + pid = spawnvp(_P_NOWAIT, path, argv); +#else + { + /* Not calling IO functions between fork and exec for safety */ + FILE *f = fopen("/dev/null", "w"); + int dev_null = fileno(f); + fclose(f); + + if ((pid = vfork()) == 0) { + if (mjit_opts.verbose == 0) { + /* CC can be started in a thread using a file which has been + already removed while MJIT is finishing. Discard the + messages about missing files. */ + dup2(dev_null, STDERR_FILENO); + dup2(dev_null, STDOUT_FILENO); + } + pid = execvp(path, argv); /* Pid will be negative on an error */ + /* Even if we successfully found CC to compile PCH we still can + fail with loading the CC in very rare cases for some reasons. + Stop the forked process in this case. */ + verbose(1, "MJIT: Error in execvp: %s\n", path); + _exit(1); + } + } +#endif + return pid; +} + +/* Execute an OS process of executable PATH with arguments ARGV. + Return -1 or -2 if failed to execute, otherwise exit code of the process. + TODO: Use the same function in process.c */ +static int +exec_process(const char *path, char *const argv[]) +{ + int stat, exit_code; + pid_t pid; + + pid = start_process(path, argv); + if (pid <= 0) + return -2; + + for (;;) { + waitpid(pid, &stat, 0); + if (WIFEXITED(stat)) { + exit_code = WEXITSTATUS(stat); + break; + } else if (WIFSIGNALED(stat)) { + exit_code = -1; + break; + } + } + return exit_code; +} + +/* Start a critical section. Use message MSG to print debug info at + LEVEL. */ +static inline void +CRITICAL_SECTION_START(int level, const char *msg) +{ + verbose(level, "Locking %s", msg); + rb_native_mutex_lock(&mjit_engine_mutex); + verbose(level, "Locked %s", msg); +} + +/* Finish the current critical section. Use message MSG to print + debug info at LEVEL. */ +static inline void +CRITICAL_SECTION_FINISH(int level, const char *msg) +{ + verbose(level, "Unlocked %s", msg); + rb_native_mutex_unlock(&mjit_engine_mutex); +} + +/* Wait until workers don't compile any iseq. It is called at the + start of GC. */ +void +mjit_gc_start_hook(void) +{ + if (!mjit_init_p) + 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 = TRUE; + 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_finish_hook(void) +{ + if (!mjit_init_p) + return; + CRITICAL_SECTION_START(4, "mjit_gc_finish_hook"); + in_gc = FALSE; + verbose(4, "Sending wakeup signal to workers after GC"); + rb_native_cond_broadcast(&mjit_gc_wakeup); + CRITICAL_SECTION_FINISH(4, "mjit_gc_finish_hook"); +} + +/* Iseqs can be garbage collected. This function should call when it + happens. It removes iseq from the unit. */ +void +mjit_free_iseq(const rb_iseq_t *iseq) +{ + if (!mjit_init_p) + return; + CRITICAL_SECTION_START(4, "mjit_free_iseq"); + if (iseq->body->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. */ + iseq->body->jit_unit->iseq = NULL; + } + CRITICAL_SECTION_FINISH(4, "mjit_free_iseq"); +} + +static void +free_unit(struct rb_mjit_unit *unit) +{ + if (unit->iseq) /* ISeq is not GCed */ + unit->iseq->body->jit_func = NULL; + if (unit->handle) /* handle is NULL if it's in queue */ + dlclose(unit->handle); + xfree(unit); +} + +static void +init_list(struct rb_mjit_unit_list *list) +{ + list->head = NULL; + list->length = 0; +} + +/* Allocate struct rb_mjit_unit_node and return it. This MUST NOT be + called inside critical section because that causes deadlock. ZALLOC + may fire GC and GC hooks mjit_gc_start_hook that starts critical section. */ +static struct rb_mjit_unit_node * +create_list_node(struct rb_mjit_unit *unit) +{ + struct rb_mjit_unit_node *node = ZALLOC(struct rb_mjit_unit_node); + node->unit = unit; + return node; +} + +/* Add unit node to the tail of doubly linked LIST. It should be not in + the list before. */ +static void +add_to_list(struct rb_mjit_unit_node *node, struct rb_mjit_unit_list *list) +{ + /* Append iseq to list */ + if (list->head == NULL) { + list->head = node; + } + else { + struct rb_mjit_unit_node *tail = list->head; + while (tail->next != NULL) { + tail = tail->next; + } + tail->next = node; + node->prev = tail; + } + list->length++; +} + +static void +remove_from_list(struct rb_mjit_unit_node *node, struct rb_mjit_unit_list *list) +{ + if (node->prev && node->next) { + node->prev->next = node->next; + node->next->prev = node->prev; + } + else if (node->prev == NULL && node->next) { + list->head = node->next; + node->next->prev = NULL; + } + else if (node->prev && node->next == NULL) { + node->prev->next = NULL; + } + else { + list->head = NULL; + } + list->length--; + xfree(node); +} + +/* Return the best unit from list. The best is the first + high priority unit or the unit whose iseq has the biggest number + of calls so far. */ +static struct rb_mjit_unit_node * +get_from_list(struct rb_mjit_unit_list *list) +{ + struct rb_mjit_unit_node *node, *best = NULL; + + if (list->head == NULL) + return NULL; + + /* Find iseq with max total_calls */ + for (node = list->head; node != NULL; node = node ? node->next : NULL) { + if (node->unit->iseq == NULL) { /* ISeq is GCed. */ + free_unit(node->unit); + remove_from_list(node, list); + continue; + } + + if (best == NULL || best->unit->iseq->body->total_calls < node->unit->iseq->body->total_calls) { + best = node; + } + } + + return best; +} + +/* Free unit list. This should be called only when worker is finished + because node of unit_queue and one of active_units may have the same unit + during proceeding unit. */ +static void +free_list(struct rb_mjit_unit_list *list) +{ + struct rb_mjit_unit_node *node, *next; + for (node = list->head; node != NULL; node = next) { + next = node->next; + free_unit(node->unit); + xfree(node); + } +} + +/* XXX_COMMONN_ARGS define the command line arguments of XXX C + compiler used by MJIT. + + XXX_EMIT_PCH_ARGS define additional options to generate the + precomiled header. + + XXX_USE_PCH_ARAGS define additional options to use the precomiled + header. */ +static const char *GCC_COMMON_ARGS_DEBUG[] = {"gcc", "-O0", "-g", "-Wfatal-errors", "-fPIC", "-shared", "-w", "-pipe", "-nostartfiles", "-nodefaultlibs", "-nostdlib", NULL}; +static const char *GCC_COMMON_ARGS[] = {"gcc", "-O2", "-Wfatal-errors", "-fPIC", "-shared", "-w", "-pipe", "-nostartfiles", "-nodefaultlibs", "-nostdlib", NULL}; +static const char *GCC_USE_PCH_ARGS[] = {"-I/tmp", NULL}; +static const char *GCC_EMIT_PCH_ARGS[] = {NULL}; + +#ifdef __MACH__ + +static const char *CLANG_COMMON_ARGS_DEBUG[] = {"clang", "-O0", "-g", "-dynamic", "-I/usr/local/include", "-L/usr/local/lib", "-w", "-bundle", NULL}; +static const char *CLANG_COMMON_ARGS[] = {"clang", "-O2", "-dynamic", "-I/usr/local/include", "-L/usr/local/lib", "-w", "-bundle", NULL}; + +#else + +static const char *CLANG_COMMON_ARGS_DEBUG[] = {"clang", "-O0", "-g", "-fPIC", "-shared", "-I/usr/local/include", "-L/usr/local/lib", "-w", "-bundle", NULL}; +static const char *CLANG_COMMON_ARGS[] = {"clang", "-O2", "-fPIC", "-shared", "-I/usr/local/include", "-L/usr/local/lib", "-w", "-bundle", NULL}; + +#endif /* #if __MACH__ */ + +static const char *CLANG_USE_PCH_ARGS[] = {"-include-pch", NULL, "-Wl,-undefined", "-Wl,dynamic_lookup", NULL}; +static const char *CLANG_EMIT_PCH_ARGS[] = {"-emit-pch", NULL}; + +/* Status of the the precompiled header creation. The status is + shared by the workers and the pch thread. */ +static enum {PCH_NOT_READY, PCH_FAILED, PCH_SUCCESS} pch_status; + +/* The function producing the pre-compiled header. */ +static void +make_pch(void) +{ + int exit_code; + static const char *input[] = {NULL, NULL}; + static const char *output[] = {"-o", NULL, NULL}; + char **args; + + verbose(2, "Creating precompiled header"); + input[0] = header_file; + output[1] = pch_file; + if (mjit_opts.cc == MJIT_CC_CLANG) + args = form_args(4, (mjit_opts.debug ? CLANG_COMMON_ARGS_DEBUG : CLANG_COMMON_ARGS), + CLANG_EMIT_PCH_ARGS, input, output); + else + args = form_args(4, (mjit_opts.debug ? GCC_COMMON_ARGS_DEBUG : GCC_COMMON_ARGS), + GCC_EMIT_PCH_ARGS, input, output); + if (args == NULL) { + if (mjit_opts.warnings || mjit_opts.verbose) + fprintf(stderr, "MJIT warning: making precompiled header failed on forming args\n"); + CRITICAL_SECTION_START(3, "in make_pch"); + pch_status = PCH_FAILED; + CRITICAL_SECTION_FINISH(3, "in make_pch"); + return; + } + + exit_code = exec_process(cc_path, args); + xfree(args); + + CRITICAL_SECTION_START(3, "in make_pch"); + if (exit_code == 0) { + pch_status = PCH_SUCCESS; + } else { + if (mjit_opts.warnings || mjit_opts.verbose) + fprintf(stderr, "MJIT warning: Making precompiled header failed on compilation. Stopping MJIT worker...\n"); + 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. It returns 1 if it succeeds. */ +static int +compile_c_to_so(const char *c_file, const char *so_file) +{ + int exit_code; + static const char *input[] = {NULL, NULL}; + static const char *output[] = {"-o", NULL, NULL}; + static const char *libs[] = { +#ifdef _WIN32 + /* Link to ruby.dll.a, because Windows DLLs don't allow unresolved symbols. */ + "-L" LIBRUBY_LIBDIR, + LIBRUBYARG_SHARED, + "-lmsvcrt", +# ifdef __GNUC__ + "-lgcc", +# endif +#endif + NULL}; + char **args; + + input[0] = c_file; + output[1] = so_file; + if (mjit_opts.cc == MJIT_CC_CLANG) { + CLANG_USE_PCH_ARGS[1] = pch_file; + args = form_args(5, (mjit_opts.debug ? CLANG_COMMON_ARGS_DEBUG : CLANG_COMMON_ARGS), + CLANG_USE_PCH_ARGS, input, output, libs); + } + else { + args = form_args(5, (mjit_opts.debug ? GCC_COMMON_ARGS_DEBUG : GCC_COMMON_ARGS), + GCC_USE_PCH_ARGS, input, output, libs); + } + if (args == NULL) + return FALSE; + + exit_code = exec_process(cc_path, args); + xfree(args); + + if (exit_code != 0) + verbose(2, "compile_c_to_so: compile error: %d", exit_code); + return exit_code == 0; +} + +static void * +load_func_from_so(const char *so_file, const char *funcname, struct rb_mjit_unit *unit) +{ + void *handle, *func; + + handle = dlopen(so_file, RTLD_NOW); + if (handle == NULL) { + if (mjit_opts.warnings || mjit_opts.verbose) + fprintf(stderr, "MJIT warning: failure in loading code from '%s': %s\n", so_file, dlerror()); + return (void *)NOT_ADDED_JIT_ISEQ_FUNC; + } + + func = dlsym(handle, funcname); + unit->handle = handle; + return func; +} + +/* Compile ISeq in UNIT and return function pointer of JIT-ed code. + It may return NOT_COMPILABLE_JIT_ISEQ_FUNC if something went wrong. */ +static void * +convert_unit_to_func(struct rb_mjit_unit *unit) +{ + char c_file[70], so_file[70], funcname[35]; + int success; + FILE *f; + void *func; + double start_time, end_time; + + sprint_uniq_filename(c_file, unit->id, "_ruby_mjit", ".c"); + sprint_uniq_filename(so_file, unit->id, "_ruby_mjit", ".so"); + sprintf(funcname, "_mjit%d", unit->id); + + f = fopen(c_file, "w"); + /* -include-pch is used for Clang */ + if (mjit_opts.cc == MJIT_CC_GCC) { + const char *s = pch_file; + fprintf(f, "#include \""); + /* print pch_file except .gch */ + for (; strcmp(s, ".gch") != 0; s++) { + switch(*s) { + case '\\': + fprintf(f, "\\%c", *s); + break; + default: + fprintf(f, "%c", *s); + } + } + fprintf(f, "\"\n"); + } + +#ifdef _WIN32 + fprintf(f, "void _pei386_runtime_relocator(void){}\n"); + fprintf(f, "int __stdcall DllMainCRTStartup(void* hinstDLL, unsigned int fdwReason, void* lpvReserved) { return 1; }\n"); +#endif + + /* wait until mjit_gc_finish_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); + } + in_jit = TRUE; + CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish"); + + verbose(2, "start compile: %s@%s:%d -> %s", RSTRING_PTR(unit->iseq->body->location.label), + RSTRING_PTR(rb_iseq_path(unit->iseq)), FIX2INT(unit->iseq->body->location.first_lineno), c_file); + fprintf(f, "/* %s@%s:%d */\n\n", RSTRING_PTR(unit->iseq->body->location.label), + RSTRING_PTR(rb_iseq_path(unit->iseq)), FIX2INT(unit->iseq->body->location.first_lineno)); + success = mjit_compile(f, unit->iseq->body, funcname); + + /* 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); + if (!success) { + if (!mjit_opts.save_temps) + remove(c_file); + return (void *)NOT_COMPILABLE_JIT_ISEQ_FUNC; + } + + start_time = real_ms_time(); + success = compile_c_to_so(c_file, so_file); + end_time = real_ms_time(); + + if (!mjit_opts.save_temps) + remove(c_file); + if (!success) { + verbose(2, "Failed to generate so: %s", so_file); + return (void *)NOT_COMPILABLE_JIT_ISEQ_FUNC; + } + + func = load_func_from_so(so_file, funcname, unit); + if (!mjit_opts.save_temps) + remove(so_file); + + if ((ptrdiff_t)func > (ptrdiff_t)LAST_JIT_ISEQ_FUNC) { + struct rb_mjit_unit_node *node = create_list_node(unit); + CRITICAL_SECTION_START(3, "end of jit"); + add_to_list(node, &active_units); + if (unit->iseq) + verbose(1, "JIT success (%.1fms): %s@%s:%d -> %s", end_time - start_time, RSTRING_PTR(unit->iseq->body->location.label), + RSTRING_PTR(rb_iseq_path(unit->iseq)), FIX2INT(unit->iseq->body->location.first_lineno), c_file); + CRITICAL_SECTION_FINISH(3, "end of jit"); + } + return func; +} + +/* Set to TRUE to finish worker. */ +static int finish_worker_p; +/* Set to TRUE if worker is finished. */ +static int worker_finished; + +/* 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. */ +static void +worker(void) +{ + make_pch(); + if (pch_status == PCH_FAILED) { + mjit_init_p = FALSE; + CRITICAL_SECTION_START(3, "in worker to update worker_finished"); + worker_finished = 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_finished"); + return; /* TODO: do the same thing in the latter half of mjit_finish */ + } + + /* main worker loop */ + while (!finish_worker_p) { + struct rb_mjit_unit_node *node; + + /* wait until unit is available */ + CRITICAL_SECTION_START(3, "in worker dequeue"); + while ((unit_queue.head == NULL || active_units.length > mjit_opts.max_cache_size) && !finish_worker_p) { + rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex); + verbose(3, "Getting wakeup from client"); + } + node = get_from_list(&unit_queue); + CRITICAL_SECTION_FINISH(3, "in worker dequeue"); + + if (node) { + void *func = convert_unit_to_func(node->unit); + + CRITICAL_SECTION_START(3, "in jit func replace"); + if (node->unit->iseq) { /* Check whether GCed or not */ + /* Usage of jit_code might be not in a critical section. */ + ATOMIC_SET(node->unit->iseq->body->jit_func, func); + } + remove_from_list(node, &unit_queue); + CRITICAL_SECTION_FINISH(3, "in jit func replace"); + } + } + + CRITICAL_SECTION_START(3, "in the end of worker to update worker_finished"); + worker_finished = TRUE; + CRITICAL_SECTION_FINISH(3, "in the end of worker to update worker_finished"); +} + +/* MJIT info related to an existing continutaion. */ +struct mjit_cont { + rb_execution_context_t *ec; /* continuation ec */ + struct mjit_cont *prev, *next; /* used to form lists */ +}; + +/* Double linked list of registered continuations. This is used to detect + units which are in use in unload_units. */ +static struct mjit_cont *first_cont; + +/* Register a new continuation with thread TH. Return MJIT info about + the continuation. */ +struct mjit_cont * +mjit_cont_new(rb_execution_context_t *ec) +{ + struct mjit_cont *cont; + + cont = ZALLOC(struct mjit_cont); + cont->ec = ec; + + CRITICAL_SECTION_START(3, "in mjit_cont_new"); + if (first_cont == NULL) { + cont->next = cont->prev = NULL; + } + else { + cont->prev = NULL; + cont->next = first_cont; + first_cont->prev = cont; + } + first_cont = cont; + CRITICAL_SECTION_FINISH(3, "in mjit_cont_new"); + + return cont; +} + +/* Unregister continuation CONT. */ +void +mjit_cont_free(struct mjit_cont *cont) +{ + CRITICAL_SECTION_START(3, "in mjit_cont_new"); + if (cont == first_cont) { + first_cont = cont->next; + if (first_cont != NULL) + first_cont->prev = NULL; + } + else { + cont->prev->next = cont->next; + if (cont->next != NULL) + cont->next->prev = cont->prev; + } + CRITICAL_SECTION_FINISH(3, "in mjit_cont_new"); + + xfree(cont); +} + +/* Finish work with continuation info. */ +static void +finish_conts(void) +{ + struct mjit_cont *cont, *next; + + for (cont = first_cont; cont != NULL; cont = next) { + next = cont->next; + xfree(cont); + } +} + +/* Create unit for ISEQ. */ +static void +create_unit(const rb_iseq_t *iseq) +{ + struct rb_mjit_unit *unit; + + unit = ZALLOC(struct rb_mjit_unit); + if (unit == NULL) + return; + + unit->id = current_unit_num++; + unit->iseq = iseq; + iseq->body->jit_unit = unit; +} + +/* Set up field used_code_p for unit iseqs whose iseq on the stack of ec. */ +static void +mark_ec_units(rb_execution_context_t *ec) +{ + const rb_iseq_t *iseq; + const rb_control_frame_t *cfp; + rb_control_frame_t *last_cfp = ec->cfp; + const rb_control_frame_t *end_marker_cfp; + ptrdiff_t i, size; + + if (ec->vm_stack == NULL) + return; + end_marker_cfp = RUBY_VM_END_CONTROL_FRAME(ec); + size = end_marker_cfp - last_cfp; + for (i = 0, cfp = end_marker_cfp - 1; i < size; i++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { + if (cfp->pc && (iseq = cfp->iseq) != NULL + && imemo_type((VALUE) iseq) == imemo_iseq + && (iseq->body->jit_unit) != NULL) { + iseq->body->jit_unit->used_code_p = TRUE; + } + } +} + +/* Unload JIT code of some units to satisfy the maximum permitted + number of units with a loaded code. */ +static void +unload_units(void) +{ + rb_vm_t *vm = GET_THREAD()->vm; + rb_thread_t *th = NULL; + struct rb_mjit_unit_node *node, *next, *worst_node; + struct rb_mjit_unit *unit; + struct mjit_cont *cont; + int delete_num, units_num = active_units.length; + + /* For now, we don't unload units when ISeq is GCed. We should + unload such ISeqs first here. */ + for (node = active_units.head; node != NULL; node = next) { + next = node->next; + if (node->unit->iseq == NULL) { /* ISeq is GCed. */ + free_unit(node->unit); + remove_from_list(node, &active_units); + } + } + + /* Detect units which are in use and can't be unloaded. */ + for (node = active_units.head; node != NULL; node = node->next) { + assert(node->unit != NULL && node->unit->iseq != NULL && node->unit->handle != NULL); + node->unit->used_code_p = FALSE; + } + list_for_each(&vm->living_threads, th, vmlt_node) { + mark_ec_units(th->ec); + } + for (cont = first_cont; cont != NULL; cont = cont->next) { + mark_ec_units(cont->ec); + } + + /* Remove 1/10 units more to decrease unloading calls. */ + delete_num = active_units.length / 10; + for (; active_units.length > mjit_opts.max_cache_size - delete_num;) { + /* Find one unit that has the minimum total_calls. */ + worst_node = NULL; + for (node = active_units.head; node != NULL; node = node->next) { + if (node->unit->used_code_p) /* We can't unload code on stack. */ + continue; + + if (worst_node == NULL || worst_node->unit->iseq->body->total_calls > node->unit->iseq->body->total_calls) { + worst_node = node; + } + } + if (worst_node == NULL) + break; + + /* Unload the worst node. */ + verbose(2, "Unloading unit %d (calls=%lu)", worst_node->unit->id, worst_node->unit->iseq->body->total_calls); + unit = worst_node->unit; + unit->iseq->body->jit_func = (void *)NOT_READY_JIT_ISEQ_FUNC; + remove_from_list(worst_node, &active_units); + + assert(unit->handle != NULL); + dlclose(unit->handle); + unit->handle = NULL; + } + verbose(1, "Too many JIT code -- %d units unloaded", units_num - active_units.length); +} + +/* Add ISEQ to be JITed in parallel with the current thread. + Unload some JIT codes if there are too many of them. */ +void +mjit_add_iseq_to_process(const rb_iseq_t *iseq) +{ + struct rb_mjit_unit_node *node; + + if (!mjit_init_p) + return; + + iseq->body->jit_func = (void *)NOT_READY_JIT_ISEQ_FUNC; + create_unit(iseq); + if (iseq->body->jit_unit == NULL) + /* Failure in creating the unit. */ + return; + + node = create_list_node(iseq->body->jit_unit); + CRITICAL_SECTION_START(3, "in add_iseq_to_process"); + add_to_list(node, &unit_queue); + if (active_units.length >= mjit_opts.max_cache_size) { + unload_units(); + } + 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"); +} + +/* Wait for JIT compilation finish for --jit-wait. This should only return a function pointer + or NOT_COMPILABLE_JIT_ISEQ_FUNC. */ +mjit_func_t +mjit_get_iseq_func(const struct rb_iseq_constant_body *body) +{ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000; + while ((enum rb_mjit_iseq_func)body->jit_func == NOT_READY_JIT_ISEQ_FUNC) { + CRITICAL_SECTION_START(3, "in mjit_get_iseq_func for a client wakeup"); + rb_native_cond_broadcast(&mjit_worker_wakeup); + CRITICAL_SECTION_FINISH(3, "in mjit_get_iseq_func for a client wakeup"); + rb_thread_wait_for(tv); + } + return body->jit_func; +} + +/* A name of the header file included in any C file generated by MJIT for iseqs. */ +#define RUBY_MJIT_HEADER_FILE ("rb_mjit_min_header-" RUBY_VERSION ".h") +/* GCC and CLANG executable paths. TODO: The paths should absolute + ones to prevent changing C compiler for security reasons. */ +#define GCC_PATH "gcc" +#define CLANG_PATH "clang" + +static void +init_header_filename(void) +{ + FILE *f; + + header_file = xmalloc(strlen(MJIT_HEADER_BUILD_DIR) + 2 + strlen(RUBY_MJIT_HEADER_FILE)); + if (header_file == NULL) + return; + strcpy(header_file, MJIT_HEADER_BUILD_DIR); + strcat(header_file, "/"); + strcat(header_file, RUBY_MJIT_HEADER_FILE); + + if ((f = fopen(header_file, "r")) == NULL) { + xfree(header_file); + header_file = xmalloc(strlen(MJIT_HEADER_INSTALL_DIR) + 2 + strlen(RUBY_MJIT_HEADER_FILE)); + if (header_file == NULL) + return; + strcpy(header_file, MJIT_HEADER_INSTALL_DIR); + strcat(header_file, "/"); + strcat(header_file, RUBY_MJIT_HEADER_FILE); + if ((f = fopen(header_file, "r")) == NULL) { + xfree(header_file); + header_file = NULL; + return; + } + } + fclose(f); +} + +/* This is called after each fork in the child in to switch off MJIT + engine in the child as it does not inherit MJIT threads. */ +static void +child_after_fork(void) +{ + verbose(3, "Switching off MJIT in a forked child"); + mjit_init_p = FALSE; + /* TODO: Should we initiate MJIT in the forked Ruby. */ +} + +/* Default permitted number of units with a JIT code kept in + memory. */ +#define DEFAULT_CACHE_SIZE 1000 +/* A default threshold used to add iseq to JIT. */ +#define DEFAULT_MIN_CALLS_TO_ADD 5 +/* Minimum value for JIT cache size. */ +#define MIN_CACHE_SIZE 10 + +/* Initialize MJIT. Start a thread creating the precompiled header and + processing ISeqs. The function should be called first for using MJIT. + If everything is successfull, MJIT_INIT_P will be TRUE. */ +void +mjit_init(struct mjit_options *opts) +{ + mjit_opts = *opts; + mjit_init_p = TRUE; + + /* Normalize options */ + if (mjit_opts.min_calls == 0) + mjit_opts.min_calls = DEFAULT_MIN_CALLS_TO_ADD; + if (mjit_opts.max_cache_size <= 0) + mjit_opts.max_cache_size = DEFAULT_CACHE_SIZE; + if (mjit_opts.max_cache_size < MIN_CACHE_SIZE) + mjit_opts.max_cache_size = MIN_CACHE_SIZE; + + if (mjit_opts.cc == MJIT_CC_DEFAULT) { +#if defined(__MACH__) + mjit_opts.cc = MJIT_CC_CLANG; + verbose(2, "MJIT: CC defaults to clang"); +#else + mjit_opts.cc = MJIT_CC_GCC; + verbose(2, "MJIT: CC defaults to gcc"); +#endif + } + + /* Initialize variables for compilation */ + pch_status = PCH_NOT_READY; + if (mjit_opts.cc == MJIT_CC_CLANG) { + cc_path = CLANG_PATH; + } else { + cc_path = GCC_PATH; + } + + if (getenv("TMP") != NULL) { /* For MinGW */ + tmp_dir = get_string(getenv("TMP")); + } + else { + tmp_dir = get_string("/tmp"); + } + + init_header_filename(); + pch_file = get_uniq_filename(0, "_mjit_h", ".h.gch"); + if (header_file == NULL || pch_file == NULL) { + mjit_init_p = FALSE; + verbose(1, "Failure in MJIT header file name initialization\n"); + return; + } + + init_list(&unit_queue); + init_list(&active_units); + + /* Initialize mutex */ + rb_native_mutex_initialize(&mjit_engine_mutex); + rb_native_cond_initialize(&mjit_pch_wakeup, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&mjit_client_wakeup, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&mjit_worker_wakeup, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&mjit_gc_wakeup, RB_CONDATTR_CLOCK_MONOTONIC); + + /* Initialize worker thread */ + finish_worker_p = FALSE; + worker_finished = FALSE; + if (rb_thread_create_mjit_thread(child_after_fork, worker) == FALSE) { + mjit_init_p = 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"); + } +} + +/* Finish the threads processing units and creating PCH, finalize + and free MJIT data. It should be called last during MJIT + life. */ +void +mjit_finish(void) +{ + if (!mjit_init_p) + return; + + /* Wait for pch finish */ + verbose(2, "Canceling pch and worker threads"); + 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 */ + finish_worker_p = TRUE; + while (!worker_finished) { + verbose(3, "Sending cancel signal to workers"); + CRITICAL_SECTION_START(3, "in mjit_finish"); + rb_native_cond_broadcast(&mjit_worker_wakeup); + CRITICAL_SECTION_FINISH(3, "in mjit_finish"); + } + + 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); + + /* cleanup temps */ + if (!mjit_opts.save_temps) + remove(pch_file); + + xfree(tmp_dir); tmp_dir = NULL; + xfree(pch_file); pch_file = NULL; + xfree(header_file); header_file = NULL; + + free_list(&unit_queue); + free_list(&active_units); + finish_conts(); + + mjit_init_p = FALSE; + verbose(1, "Successful MJIT finish"); +} + +void +mjit_mark(void) +{ + struct rb_mjit_unit_node *node; + if (!mjit_init_p) + return; + RUBY_MARK_ENTER("mjit"); + CRITICAL_SECTION_START(4, "mjit_mark"); + for (node = unit_queue.head; node != NULL; node = node->next) { + if (node->unit->iseq) { /* ISeq is still not GCed */ + rb_gc_mark((VALUE)node->unit->iseq); + } + } + CRITICAL_SECTION_FINISH(4, "mjit_mark"); + RUBY_MARK_LEAVE("mjit"); +} diff --git a/mjit.h b/mjit.h new file mode 100644 index 0000000000..452110e6ec --- /dev/null +++ b/mjit.h @@ -0,0 +1,138 @@ +/********************************************************************** + + mjit.h - Interface to MRI method JIT compiler + + Copyright (C) 2017 Vladimir Makarov <[email protected]>. + +**********************************************************************/ + +#ifndef RUBY_MJIT_H +#define RUBY_MJIT_H 1 + +#include "ruby.h" + +/* Special address values of a function generated from the + corresponding iseq by MJIT: */ +enum rb_mjit_iseq_func { + /* ISEQ was not queued yet for the machine code generation */ + NOT_ADDED_JIT_ISEQ_FUNC = 0, + /* ISEQ is already queued for the machine code generation but the + code is not ready yet for the execution */ + NOT_READY_JIT_ISEQ_FUNC = 1, + /* ISEQ included not compilable insn or some assertion failed */ + NOT_COMPILABLE_JIT_ISEQ_FUNC = 2, + /* End mark */ + LAST_JIT_ISEQ_FUNC = 3, +}; + +/* C compiler used to generate native code. */ +enum rb_mjit_cc { + /* Not selected */ + MJIT_CC_DEFAULT = 0, + /* GNU Compiler Collection */ + MJIT_CC_GCC = 1, + /* LLVM/Clang */ + MJIT_CC_CLANG = 2, +}; + +/* MJIT options which can be defined on the MRI command line. */ +struct mjit_options { + char on; /* flag of MJIT usage */ + /* Default: clang for macOS, cl for Windows, gcc for others. */ + enum rb_mjit_cc cc; + /* Save temporary files after MRI finish. The temporary files + include the pre-compiled header, C code file generated for ISEQ, + and the corresponding object file. */ + char save_temps; + /* Print MJIT warnings to stderr. */ + char warnings; + /* Disable compiler optimization and add debug symbols. It can be + very slow. */ + char debug; + /* If not 0, all ISeqs are synchronously compiled. For testing. */ + unsigned int wait; + /* Number of calls to trigger JIT compilation. For testing. */ + unsigned int min_calls; + /* Force printing info about MJIT work of level VERBOSE or + less. 0=silence, 1=medium, 2=verbose. */ + int verbose; + /* Maximal permitted number of iseq JIT codes in a MJIT memory + cache. */ + int max_cache_size; +}; + +typedef VALUE (*mjit_func_t)(rb_execution_context_t *, rb_control_frame_t *); + +RUBY_SYMBOL_EXPORT_BEGIN +extern struct mjit_options mjit_opts; +extern int mjit_init_p; + +extern void mjit_add_iseq_to_process(const rb_iseq_t *iseq); +extern mjit_func_t mjit_get_iseq_func(const struct rb_iseq_constant_body *body); +RUBY_SYMBOL_EXPORT_END + +extern int mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname); +extern void mjit_init(struct mjit_options *opts); +extern void mjit_finish(void); +extern void mjit_gc_start_hook(void); +extern void mjit_gc_finish_hook(void); +extern void mjit_free_iseq(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); + +/* A threshold used to reject long iseqs from JITting as such iseqs + takes too much time to be compiled. */ +#define JIT_ISEQ_SIZE_THRESHOLD 1000 + +/* Return TRUE if given ISeq body should be compiled by MJIT */ +static inline int +mjit_target_iseq_p(struct rb_iseq_constant_body *body) +{ + return (body->type == ISEQ_TYPE_METHOD || body->type == ISEQ_TYPE_BLOCK) + && body->iseq_size < JIT_ISEQ_SIZE_THRESHOLD; +} + +/* Try to execute the current iseq in ec. Use JIT code if it is ready. + If it is not, add ISEQ to the compilation queue and return Qundef. */ +static inline VALUE +mjit_exec(rb_execution_context_t *ec) +{ + const rb_iseq_t *iseq; + struct rb_iseq_constant_body *body; + long unsigned total_calls; + mjit_func_t func; + + if (!mjit_init_p) + return Qundef; + + iseq = ec->cfp->iseq; + body = iseq->body; + total_calls = ++body->total_calls; + + func = body->jit_func; + if (UNLIKELY(mjit_opts.wait && mjit_opts.min_calls == total_calls && mjit_target_iseq_p(body) + && (enum rb_mjit_iseq_func)func == NOT_ADDED_JIT_ISEQ_FUNC)) { + mjit_add_iseq_to_process(iseq); + func = mjit_get_iseq_func(body); + } + + if (UNLIKELY((ptrdiff_t)func <= (ptrdiff_t)LAST_JIT_ISEQ_FUNC)) { + switch ((enum rb_mjit_iseq_func)func) { + case NOT_ADDED_JIT_ISEQ_FUNC: + if (total_calls == mjit_opts.min_calls && mjit_target_iseq_p(body)) { + mjit_add_iseq_to_process(iseq); + } + return Qundef; + case NOT_READY_JIT_ISEQ_FUNC: + case NOT_COMPILABLE_JIT_ISEQ_FUNC: + return Qundef; + default: /* to avoid warning with LAST_JIT_ISEQ_FUNC */ + break; + } + } + + return func(ec, ec->cfp); +} + +#endif /* RUBY_MJIT_H */ diff --git a/mjit_compile.c b/mjit_compile.c new file mode 100644 index 0000000000..a48ae84e4c --- /dev/null +++ b/mjit_compile.c @@ -0,0 +1,18 @@ +/********************************************************************** + + mjit_compile.c - MRI method JIT compiler + + Copyright (C) 2017 Takashi Kokubun <[email protected]>. + +**********************************************************************/ + +#include "internal.h" +#include "vm_core.h" + +/* Compile ISeq to C code in F. Return TRUE if it succeeds to compile. */ +int +mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname) +{ + /* TODO: Write your own JIT compiler here. */ + return FALSE; +} @@ -51,6 +51,8 @@ #include "ruby/util.h" +#include "mjit.h" + #ifndef HAVE_STDLIB_H char *getenv(); #endif @@ -135,6 +137,7 @@ struct ruby_cmdline_options { VALUE req_list; unsigned int features; unsigned int dump; + struct mjit_options mjit; int safe_level; int sflag, xflag; unsigned int warning: 1; @@ -193,7 +196,7 @@ static void show_usage_line(const char *str, unsigned int namelen, unsigned int secondlen, int help) { const unsigned int w = 16; - const int wrap = help && namelen + secondlen - 2 > w; + const int wrap = help && namelen + secondlen - 1 > w; printf(" %.*s%-*.*s%-*s%s\n", namelen-1, str, (wrap ? 0 : w - namelen + 1), (help ? secondlen-1 : 0), str + namelen, @@ -238,6 +241,8 @@ usage(const char *name, int help) M("-w", "", "turn warnings on for your script"), M("-W[level=2]", "", "set warning level; 0=silence, 1=medium, 2=verbose"), M("-x[directory]", "", "strip off text before #!ruby line and perhaps cd to directory"), + M("--jit", "", "enable MJIT with default options (experimental)"), + M("--jit-[option]","", "enable MJIT with an option (experimental)"), M("-h", "", "show this message, --help for more info"), }; static const struct message help_msg[] = { @@ -263,6 +268,16 @@ usage(const char *name, int help) M("rubyopt", "", "RUBYOPT environment variable (default: enabled)"), M("frozen-string-literal", "", "freeze all string literals (default: disabled)"), }; + static const struct message mjit_options[] = { + M("--jit-cc=cc", "", "C compiler to generate native code (gcc, clang)"), + M("--jit-warnings", "", "Enable printing MJIT warnings"), + M("--jit-debug", "", "Enable MJIT debugging (very slow)"), + M("--jit-wait", "", "Wait until JIT compilation is finished everytime (for testing)"), + M("--jit-save-temps", "", "Save MJIT temporary files in $TMP or /tmp (for testing)"), + M("--jit-verbose=num", "", "Print MJIT logs of level num or less to stderr (default: 0)"), + M("--jit-max-cache=num", "", "Max number of methods to be JIT-ed in a cache (default: 1000)"), + M("--jit-min-calls=num", "", "Number of calls to trigger JIT (for testing, default: 5)"), + }; int i; const int num = numberof(usage_msg) - (help ? 1 : 0); #define SHOW(m) show_usage_line((m).str, (m).namelen, (m).secondlen, help) @@ -281,6 +296,9 @@ usage(const char *name, int help) puts("Features:"); for (i = 0; i < numberof(features); ++i) SHOW(features[i]); + puts("MJIT options (experimental):"); + for (i = 0; i < numberof(mjit_options); ++i) + SHOW(mjit_options[i]); } #define rubylib_path_new rb_str_new @@ -893,6 +911,55 @@ set_option_encoding_once(const char *type, VALUE *name, const char *e, long elen #define set_source_encoding_once(opt, e, elen) \ set_option_encoding_once("source", &(opt)->src.enc.name, (e), (elen)) +static enum rb_mjit_cc +parse_mjit_cc(const char *s) +{ + if (strcmp(s, "gcc") == 0) { + return MJIT_CC_GCC; + } + else if (strcmp(s, "clang") == 0) { + return MJIT_CC_CLANG; + } + else { + rb_raise(rb_eRuntimeError, "invalid C compiler `%s' (available C compilers: gcc, clang)", s); + } +} + +static void +setup_mjit_options(const char *s, struct mjit_options *mjit_opt) +{ + mjit_opt->on = 1; + if (*s == 0) return; + if (strncmp(s, "-cc=", 4) == 0) { + mjit_opt->cc = parse_mjit_cc(s + 4); + } + else if (strcmp(s, "-warnings") == 0) { + mjit_opt->warnings = 1; + } + else if (strcmp(s, "-debug") == 0) { + mjit_opt->debug = 1; + } + else if (strcmp(s, "-wait") == 0) { + mjit_opt->wait = 1; + } + else if (strcmp(s, "-save-temps") == 0) { + mjit_opt->save_temps = 1; + } + else if (strncmp(s, "-verbose=", 9) == 0) { + mjit_opt->verbose = atoi(s + 9); + } + else if (strncmp(s, "-max-cache=", 11) == 0) { + mjit_opt->max_cache_size = atoi(s + 11); + } + else if (strncmp(s, "-min-calls=", 11) == 0) { + mjit_opt->min_calls = atoi(s + 11); + } + else { + rb_raise(rb_eRuntimeError, + "invalid MJIT option `%s' (--help will show valid MJIT options)", s + 1); + } +} + static long proc_options(long argc, char **argv, ruby_cmdline_options_t *opt, int envopt) { @@ -1245,6 +1312,9 @@ proc_options(long argc, char **argv, ruby_cmdline_options_t *opt, int envopt) opt->verbose = 1; ruby_verbose = Qtrue; } + else if (strncmp("jit", s, 3) == 0) { + setup_mjit_options(s + 3, &opt->mjit); + } else if (strcmp("yydebug", s) == 0) { if (envopt) goto noenvopt_long; opt->dump |= DUMP_BIT(yydebug); @@ -1481,6 +1551,9 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) opt->intern.enc.name = int_enc_name; } + if (opt->mjit.on) + mjit_init(&opt->mjit); + if (opt->src.enc.name) rb_warning("-K is specified; it is for 1.8 compatibility and may cause odd behavior"); diff --git a/test/lib/zombie_hunter.rb b/test/lib/zombie_hunter.rb index 2b81e396ac..ea94844b53 100644 --- a/test/lib/zombie_hunter.rb +++ b/test/lib/zombie_hunter.rb @@ -1,4 +1,8 @@ # frozen_string_literal: true + +# There might be compiler processes executed by MJIT +return if RubyVM::MJIT.enabled? + module ZombieHunter def after_teardown super diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 0b26608a0e..2f0a8a7025 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -543,6 +543,9 @@ class TestIO < Test::Unit::TestCase if have_nonblock? def test_copy_stream_no_busy_wait + # JIT has busy wait on GC. It's hard to test this with JIT. + skip "MJIT has busy wait on GC. We can't test this with JIT." if RubyVM::MJIT.enabled? + msg = 'r58534 [ruby-core:80969] [Backport #13533]' IO.pipe do |r,w| r.nonblock = true diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 083dcec027..dac38a1dc9 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -26,7 +26,7 @@ class TestRubyOptions < Test::Unit::TestCase def test_usage assert_in_out_err(%w(-h)) do |r, e| - assert_operator(r.size, :<=, 24) + assert_operator(r.size, :<=, 25) longer = r[1..-1].select {|x| x.size > 80} assert_equal([], longer) assert_equal([], e) @@ -359,7 +359,7 @@ rb_thread_debug( if (debug_mutex_initialized == 1) { debug_mutex_initialized = 0; - native_mutex_initialize(&debug_mutex); + rb_native_mutex_initialize(&debug_mutex); } va_start(args, fmt); @@ -377,31 +377,31 @@ rb_vm_gvl_destroy(rb_vm_t *vm) { gvl_release(vm); gvl_destroy(vm); - native_mutex_destroy(&vm->thread_destruct_lock); + rb_native_mutex_destroy(&vm->thread_destruct_lock); } void rb_nativethread_lock_initialize(rb_nativethread_lock_t *lock) { - native_mutex_initialize(lock); + rb_native_mutex_initialize(lock); } void rb_nativethread_lock_destroy(rb_nativethread_lock_t *lock) { - native_mutex_destroy(lock); + rb_native_mutex_destroy(lock); } void rb_nativethread_lock_lock(rb_nativethread_lock_t *lock) { - native_mutex_lock(lock); + rb_native_mutex_lock(lock); } void rb_nativethread_lock_unlock(rb_nativethread_lock_t *lock) { - native_mutex_unlock(lock); + rb_native_mutex_unlock(lock); } static int @@ -417,15 +417,15 @@ unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, in RUBY_VM_CHECK_INTS(th->ec); } - native_mutex_lock(&th->interrupt_lock); + rb_native_mutex_lock(&th->interrupt_lock); } while (RUBY_VM_INTERRUPTED_ANY(th->ec) && - (native_mutex_unlock(&th->interrupt_lock), TRUE)); + (rb_native_mutex_unlock(&th->interrupt_lock), TRUE)); VM_ASSERT(th->unblock.func == NULL); th->unblock.func = func; th->unblock.arg = arg; - native_mutex_unlock(&th->interrupt_lock); + rb_native_mutex_unlock(&th->interrupt_lock); return TRUE; } @@ -433,15 +433,15 @@ unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, in static void unblock_function_clear(rb_thread_t *th) { - native_mutex_lock(&th->interrupt_lock); + rb_native_mutex_lock(&th->interrupt_lock); th->unblock.func = NULL; - native_mutex_unlock(&th->interrupt_lock); + rb_native_mutex_unlock(&th->interrupt_lock); } static void rb_threadptr_interrupt_common(rb_thread_t *th, int trap) { - native_mutex_lock(&th->interrupt_lock); + rb_native_mutex_lock(&th->interrupt_lock); if (trap) { RUBY_VM_SET_TRAP_INTERRUPT(th->ec); } @@ -454,7 +454,7 @@ rb_threadptr_interrupt_common(rb_thread_t *th, int trap) else { /* none */ } - native_mutex_unlock(&th->interrupt_lock); + rb_native_mutex_unlock(&th->interrupt_lock); } void @@ -585,7 +585,7 @@ thread_cleanup_func(void *th_ptr, int atfork) if (atfork) return; - native_mutex_destroy(&th->interrupt_lock); + rb_native_mutex_destroy(&th->interrupt_lock); native_thread_destroy(th); } @@ -739,10 +739,10 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start, VALUE *register_stack_s rb_fiber_close(th->ec->fiber_ptr); } - native_mutex_lock(&th->vm->thread_destruct_lock); + rb_native_mutex_lock(&th->vm->thread_destruct_lock); /* make sure vm->running_thread never point me after this point.*/ th->vm->running_thread = NULL; - native_mutex_unlock(&th->vm->thread_destruct_lock); + rb_native_mutex_unlock(&th->vm->thread_destruct_lock); thread_cleanup_func(th, FALSE); gvl_release(th->vm); @@ -773,7 +773,7 @@ thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(ANYARGS)) th->pending_interrupt_mask_stack = rb_ary_dup(current_th->pending_interrupt_mask_stack); RBASIC_CLEAR_CLASS(th->pending_interrupt_mask_stack); - native_mutex_initialize(&th->interrupt_lock); + rb_native_mutex_initialize(&th->interrupt_lock); /* kick thread */ err = native_thread_create(th); @@ -4096,12 +4096,12 @@ timer_thread_function(void *arg) * vm->running_thread switch. however it guarantees th->running_thread * point to valid pointer or NULL. */ - native_mutex_lock(&vm->thread_destruct_lock); + rb_native_mutex_lock(&vm->thread_destruct_lock); /* for time slice */ if (vm->running_thread) { RUBY_VM_SET_TIMER_INTERRUPT(vm->running_thread->ec); } - native_mutex_unlock(&vm->thread_destruct_lock); + rb_native_mutex_unlock(&vm->thread_destruct_lock); /* check signal */ rb_threadptr_check_signal(vm->main_thread); @@ -4936,8 +4936,8 @@ Init_Thread(void) /* acquire global vm lock */ gvl_init(th->vm); gvl_acquire(th->vm, th); - native_mutex_initialize(&th->vm->thread_destruct_lock); - native_mutex_initialize(&th->interrupt_lock); + rb_native_mutex_initialize(&th->vm->thread_destruct_lock); + rb_native_mutex_initialize(&th->interrupt_lock); th->pending_interrupt_queue = rb_ary_tmp_new(0); th->pending_interrupt_queue_checked = 0; diff --git a/thread_pthread.c b/thread_pthread.c index f54e5f04d8..9401dcc2a0 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -12,6 +12,7 @@ #ifdef THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION #include "gc.h" +#include "mjit.h" #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> @@ -34,16 +35,16 @@ #include <kernel/OS.h> #endif -static void native_mutex_lock(rb_nativethread_lock_t *lock); -static void native_mutex_unlock(rb_nativethread_lock_t *lock); +void rb_native_mutex_lock(rb_nativethread_lock_t *lock); +void rb_native_mutex_unlock(rb_nativethread_lock_t *lock); static int native_mutex_trylock(rb_nativethread_lock_t *lock); -static void native_mutex_initialize(rb_nativethread_lock_t *lock); -static void native_mutex_destroy(rb_nativethread_lock_t *lock); -static void native_cond_signal(rb_nativethread_cond_t *cond); -static void native_cond_broadcast(rb_nativethread_cond_t *cond); -static void native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex); -static void native_cond_initialize(rb_nativethread_cond_t *cond, int flags); -static void native_cond_destroy(rb_nativethread_cond_t *cond); +void rb_native_mutex_initialize(rb_nativethread_lock_t *lock); +void rb_native_mutex_destroy(rb_nativethread_lock_t *lock); +void rb_native_cond_signal(rb_nativethread_cond_t *cond); +void rb_native_cond_broadcast(rb_nativethread_cond_t *cond); +void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex); +void rb_native_cond_initialize(rb_nativethread_cond_t *cond, int flags); +void rb_native_cond_destroy(rb_nativethread_cond_t *cond); static void rb_thread_wakeup_timer_thread_low(void); static struct { pthread_t id; @@ -84,14 +85,14 @@ gvl_acquire_common(rb_vm_t *vm) } while (vm->gvl.acquired) { - native_cond_wait(&vm->gvl.cond, &vm->gvl.lock); + rb_native_cond_wait(&vm->gvl.cond, &vm->gvl.lock); } vm->gvl.waiting--; if (vm->gvl.need_yield) { vm->gvl.need_yield = 0; - native_cond_signal(&vm->gvl.switch_cond); + rb_native_cond_signal(&vm->gvl.switch_cond); } } @@ -101,9 +102,9 @@ gvl_acquire_common(rb_vm_t *vm) static void gvl_acquire(rb_vm_t *vm, rb_thread_t *th) { - native_mutex_lock(&vm->gvl.lock); + rb_native_mutex_lock(&vm->gvl.lock); gvl_acquire_common(vm); - native_mutex_unlock(&vm->gvl.lock); + rb_native_mutex_unlock(&vm->gvl.lock); } static void @@ -111,28 +112,28 @@ gvl_release_common(rb_vm_t *vm) { vm->gvl.acquired = 0; if (vm->gvl.waiting > 0) - native_cond_signal(&vm->gvl.cond); + rb_native_cond_signal(&vm->gvl.cond); } static void gvl_release(rb_vm_t *vm) { - native_mutex_lock(&vm->gvl.lock); + rb_native_mutex_lock(&vm->gvl.lock); gvl_release_common(vm); - native_mutex_unlock(&vm->gvl.lock); + rb_native_mutex_unlock(&vm->gvl.lock); } static void gvl_yield(rb_vm_t *vm, rb_thread_t *th) { - native_mutex_lock(&vm->gvl.lock); + rb_native_mutex_lock(&vm->gvl.lock); gvl_release_common(vm); /* An another thread is processing GVL yield. */ if (UNLIKELY(vm->gvl.wait_yield)) { while (vm->gvl.wait_yield) - native_cond_wait(&vm->gvl.switch_wait_cond, &vm->gvl.lock); + rb_native_cond_wait(&vm->gvl.switch_wait_cond, &vm->gvl.lock); goto acquire; } @@ -141,28 +142,28 @@ gvl_yield(rb_vm_t *vm, rb_thread_t *th) vm->gvl.need_yield = 1; vm->gvl.wait_yield = 1; while (vm->gvl.need_yield) - native_cond_wait(&vm->gvl.switch_cond, &vm->gvl.lock); + rb_native_cond_wait(&vm->gvl.switch_cond, &vm->gvl.lock); vm->gvl.wait_yield = 0; } else { - native_mutex_unlock(&vm->gvl.lock); + rb_native_mutex_unlock(&vm->gvl.lock); sched_yield(); - native_mutex_lock(&vm->gvl.lock); + rb_native_mutex_lock(&vm->gvl.lock); } - native_cond_broadcast(&vm->gvl.switch_wait_cond); + rb_native_cond_broadcast(&vm->gvl.switch_wait_cond); acquire: gvl_acquire_common(vm); - native_mutex_unlock(&vm->gvl.lock); + rb_native_mutex_unlock(&vm->gvl.lock); } static void gvl_init(rb_vm_t *vm) { - native_mutex_initialize(&vm->gvl.lock); - native_cond_initialize(&vm->gvl.cond, RB_CONDATTR_CLOCK_MONOTONIC); - native_cond_initialize(&vm->gvl.switch_cond, RB_CONDATTR_CLOCK_MONOTONIC); - native_cond_initialize(&vm->gvl.switch_wait_cond, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_mutex_initialize(&vm->gvl.lock); + rb_native_cond_initialize(&vm->gvl.cond, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&vm->gvl.switch_cond, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&vm->gvl.switch_wait_cond, RB_CONDATTR_CLOCK_MONOTONIC); vm->gvl.acquired = 0; vm->gvl.waiting = 0; vm->gvl.need_yield = 0; @@ -172,10 +173,10 @@ gvl_init(rb_vm_t *vm) static void gvl_destroy(rb_vm_t *vm) { - native_cond_destroy(&vm->gvl.switch_wait_cond); - native_cond_destroy(&vm->gvl.switch_cond); - native_cond_destroy(&vm->gvl.cond); - native_mutex_destroy(&vm->gvl.lock); + rb_native_cond_destroy(&vm->gvl.switch_wait_cond); + rb_native_cond_destroy(&vm->gvl.switch_cond); + rb_native_cond_destroy(&vm->gvl.cond); + rb_native_mutex_destroy(&vm->gvl.lock); } #if defined(HAVE_WORKING_FORK) @@ -202,8 +203,8 @@ mutex_debug(const char *msg, void *lock) } } -static void -native_mutex_lock(pthread_mutex_t *lock) +void +rb_native_mutex_lock(pthread_mutex_t *lock) { int r; mutex_debug("lock", lock); @@ -212,8 +213,8 @@ native_mutex_lock(pthread_mutex_t *lock) } } -static void -native_mutex_unlock(pthread_mutex_t *lock) +void +rb_native_mutex_unlock(pthread_mutex_t *lock) { int r; mutex_debug("unlock", lock); @@ -238,8 +239,8 @@ native_mutex_trylock(pthread_mutex_t *lock) return 0; } -static void -native_mutex_initialize(pthread_mutex_t *lock) +void +rb_native_mutex_initialize(pthread_mutex_t *lock) { int r = pthread_mutex_init(lock, 0); mutex_debug("init", lock); @@ -248,8 +249,8 @@ native_mutex_initialize(pthread_mutex_t *lock) } } -static void -native_mutex_destroy(pthread_mutex_t *lock) +void +rb_native_mutex_destroy(pthread_mutex_t *lock) { int r = pthread_mutex_destroy(lock); mutex_debug("destroy", lock); @@ -258,8 +259,8 @@ native_mutex_destroy(pthread_mutex_t *lock) } } -static void -native_cond_initialize(rb_nativethread_cond_t *cond, int flags) +void +rb_native_cond_initialize(rb_nativethread_cond_t *cond, int flags) { int r; # if USE_MONOTONIC_COND @@ -287,8 +288,8 @@ native_cond_initialize(rb_nativethread_cond_t *cond, int flags) return; } -static void -native_cond_destroy(rb_nativethread_cond_t *cond) +void +rb_native_cond_destroy(rb_nativethread_cond_t *cond) { int r = pthread_cond_destroy(&cond->cond); if (r != 0) { @@ -302,12 +303,12 @@ native_cond_destroy(rb_nativethread_cond_t *cond) * * http://www.opensource.apple.com/source/Libc/Libc-763.11/pthreads/pthread_cond.c * - * The following native_cond_signal and native_cond_broadcast functions + * The following rb_native_cond_signal and rb_native_cond_broadcast functions * need to retrying until pthread functions don't return EAGAIN. */ -static void -native_cond_signal(rb_nativethread_cond_t *cond) +void +rb_native_cond_signal(rb_nativethread_cond_t *cond) { int r; do { @@ -318,20 +319,20 @@ native_cond_signal(rb_nativethread_cond_t *cond) } } -static void -native_cond_broadcast(rb_nativethread_cond_t *cond) +void +rb_native_cond_broadcast(rb_nativethread_cond_t *cond) { int r; do { r = pthread_cond_broadcast(&cond->cond); } while (r == EAGAIN); if (r != 0) { - rb_bug_errno("native_cond_broadcast", r); + rb_bug_errno("rb_native_cond_broadcast", r); } } -static void -native_cond_wait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex) +void +rb_native_cond_wait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex) { int r = pthread_cond_wait(&cond->cond, mutex); if (r != 0) { @@ -449,7 +450,7 @@ Init_native_thread(rb_thread_t *th) fill_thread_id_str(th); native_thread_init(th); #ifdef USE_UBF_LIST - native_mutex_initialize(&ubf_list_lock); + rb_native_mutex_initialize(&ubf_list_lock); #endif posix_signal(SIGVTALRM, null_func); } @@ -462,14 +463,14 @@ native_thread_init(rb_thread_t *th) #ifdef USE_UBF_LIST list_node_init(&nd->ubf_list); #endif - native_cond_initialize(&nd->sleep_cond, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_cond_initialize(&nd->sleep_cond, RB_CONDATTR_CLOCK_MONOTONIC); ruby_thread_set_native(th); } static void native_thread_destroy(rb_thread_t *th) { - native_cond_destroy(&th->native_thread_data.sleep_cond); + rb_native_cond_destroy(&th->native_thread_data.sleep_cond); } #ifndef USE_THREAD_CACHE @@ -917,7 +918,7 @@ register_cached_thread_and_wait(void) ts.tv_sec = tv.tv_sec + 60; ts.tv_nsec = tv.tv_usec * 1000; - native_mutex_lock(&thread_cache_lock); + rb_native_mutex_lock(&thread_cache_lock); { entry->th_area = &th_area; entry->cond = &cond; @@ -939,9 +940,9 @@ register_cached_thread_and_wait(void) } free(entry); /* ok */ - native_cond_destroy(&cond); + rb_native_cond_destroy(&cond); } - native_mutex_unlock(&thread_cache_lock); + rb_native_mutex_unlock(&thread_cache_lock); return (rb_thread_t *)th_area; } @@ -955,7 +956,7 @@ use_cached_thread(rb_thread_t *th) struct cached_thread_entry *entry; if (cached_thread_root) { - native_mutex_lock(&thread_cache_lock); + rb_native_mutex_lock(&thread_cache_lock); entry = cached_thread_root; { if (cached_thread_root) { @@ -965,9 +966,9 @@ use_cached_thread(rb_thread_t *th) } } if (result) { - native_cond_signal(entry->cond); + rb_native_cond_signal(entry->cond); } - native_mutex_unlock(&thread_cache_lock); + rb_native_mutex_unlock(&thread_cache_lock); } #endif return result; @@ -1067,7 +1068,7 @@ ubf_pthread_cond_signal(void *ptr) { rb_thread_t *th = (rb_thread_t *)ptr; thread_debug("ubf_pthread_cond_signal (%p)\n", (void *)th); - native_cond_signal(&th->native_thread_data.sleep_cond); + rb_native_cond_signal(&th->native_thread_data.sleep_cond); } static void @@ -1101,7 +1102,7 @@ native_sleep(rb_thread_t *th, struct timeval *timeout_tv) GVL_UNLOCK_BEGIN(); { - native_mutex_lock(lock); + rb_native_mutex_lock(lock); th->unblock.func = ubf_pthread_cond_signal; th->unblock.arg = th; @@ -1111,14 +1112,14 @@ native_sleep(rb_thread_t *th, struct timeval *timeout_tv) } else { if (!timeout_tv) - native_cond_wait(cond, lock); + rb_native_cond_wait(cond, lock); else native_cond_timedwait(cond, lock, &timeout); } th->unblock.func = 0; th->unblock.arg = 0; - native_mutex_unlock(lock); + rb_native_mutex_unlock(lock); } GVL_UNLOCK_END(); @@ -1135,9 +1136,9 @@ register_ubf_list(rb_thread_t *th) struct list_node *node = &th->native_thread_data.ubf_list; if (list_empty((struct list_head*)node)) { - native_mutex_lock(&ubf_list_lock); + rb_native_mutex_lock(&ubf_list_lock); list_add(&ubf_list_head, node); - native_mutex_unlock(&ubf_list_lock); + rb_native_mutex_unlock(&ubf_list_lock); } } @@ -1148,9 +1149,9 @@ unregister_ubf_list(rb_thread_t *th) struct list_node *node = &th->native_thread_data.ubf_list; if (!list_empty((struct list_head*)node)) { - native_mutex_lock(&ubf_list_lock); + rb_native_mutex_lock(&ubf_list_lock); list_del_init(node); - native_mutex_unlock(&ubf_list_lock); + rb_native_mutex_unlock(&ubf_list_lock); } } @@ -1197,12 +1198,12 @@ ubf_wakeup_all_threads(void) native_thread_data_t *dat; if (!ubf_threads_empty()) { - native_mutex_lock(&ubf_list_lock); + rb_native_mutex_lock(&ubf_list_lock); list_for_each(&ubf_list_head, dat, ubf_list) { th = container_of(dat, rb_thread_t, native_thread_data); ubf_wakeup_thread(th); } - native_mutex_unlock(&ubf_list_lock); + rb_native_mutex_unlock(&ubf_list_lock); } } @@ -1535,9 +1536,9 @@ thread_timer(void *p) #endif #if !USE_SLEEPY_TIMER_THREAD - native_mutex_initialize(&timer_thread_lock); - native_cond_initialize(&timer_thread_cond, RB_CONDATTR_CLOCK_MONOTONIC); - native_mutex_lock(&timer_thread_lock); + rb_native_mutex_initialize(&timer_thread_lock); + rb_native_cond_initialize(&timer_thread_cond, RB_CONDATTR_CLOCK_MONOTONIC); + rb_native_mutex_lock(&timer_thread_lock); #endif while (system_working > 0) { @@ -1554,9 +1555,9 @@ thread_timer(void *p) CLOSE_INVALIDATE(normal[0]); CLOSE_INVALIDATE(low[0]); #else - native_mutex_unlock(&timer_thread_lock); - native_cond_destroy(&timer_thread_cond); - native_mutex_destroy(&timer_thread_lock); + rb_native_mutex_unlock(&timer_thread_lock); + rb_native_cond_destroy(&timer_thread_cond); + rb_native_mutex_destroy(&timer_thread_lock); #endif if (TT_DEBUG) WRITE_CONST(2, "finish timer thread\n"); @@ -1772,4 +1773,40 @@ rb_nativethread_self(void) return pthread_self(); } +/* A function that wraps actual worker function, for pthread abstraction. */ +static void * +mjit_worker(void *arg) +{ + void (*worker_func)(void) = arg; + + if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) { + fprintf(stderr, "Cannot enable cancelation in MJIT worker\n"); + } +#ifdef SET_CURRENT_THREAD_NAME + SET_CURRENT_THREAD_NAME("ruby-mjitworker"); /* 16 byte including NUL */ +#endif + worker_func(); + return NULL; +} + +/* Launch MJIT thread. Returns FALSE if it fails to create thread. */ +int +rb_thread_create_mjit_thread(void (*child_hook)(void), void (*worker_func)(void)) +{ + pthread_attr_t attr; + pthread_t worker_pid; + + pthread_atfork(NULL, NULL, child_hook); + if (pthread_attr_init(&attr) == 0 + && pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) == 0 + && pthread_create(&worker_pid, &attr, mjit_worker, worker_func) == 0) { + /* jit_worker thread is not to be joined */ + pthread_detach(worker_pid); + return TRUE; + } + else { + return FALSE; + } +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ diff --git a/thread_win32.c b/thread_win32.c index 5619fb462c..bb330e9bf2 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -24,8 +24,8 @@ static volatile DWORD ruby_native_thread_key = TLS_OUT_OF_INDEXES; static int w32_wait_events(HANDLE *events, int count, DWORD timeout, rb_thread_t *th); -static void native_mutex_lock(rb_nativethread_lock_t *lock); -static void native_mutex_unlock(rb_nativethread_lock_t *lock); +void rb_native_mutex_lock(rb_nativethread_lock_t *lock); +void rb_native_mutex_unlock(rb_nativethread_lock_t *lock); static void w32_error(const char *func) @@ -54,7 +54,7 @@ w32_mutex_lock(HANDLE lock) { DWORD result; while (1) { - thread_debug("native_mutex_lock: %p\n", lock); + thread_debug("rb_native_mutex_lock: %p\n", lock); result = w32_wait_events(&lock, 1, INFINITE, 0); switch (result) { case WAIT_OBJECT_0: @@ -85,7 +85,7 @@ w32_mutex_create(void) { HANDLE lock = CreateMutex(NULL, FALSE, NULL); if (lock == NULL) { - w32_error("native_mutex_initialize"); + w32_error("rb_native_mutex_initialize"); } return lock; } @@ -280,10 +280,10 @@ native_sleep(rb_thread_t *th, struct timeval *tv) { DWORD ret; - native_mutex_lock(&th->interrupt_lock); + rb_native_mutex_lock(&th->interrupt_lock); th->unblock.func = ubf_handle; th->unblock.arg = th; - native_mutex_unlock(&th->interrupt_lock); + rb_native_mutex_unlock(&th->interrupt_lock); if (RUBY_VM_INTERRUPTED(th->ec)) { /* interrupted. return immediate */ @@ -294,16 +294,16 @@ native_sleep(rb_thread_t *th, struct timeval *tv) thread_debug("native_sleep done (%lu)\n", ret); } - native_mutex_lock(&th->interrupt_lock); + rb_native_mutex_lock(&th->interrupt_lock); th->unblock.func = 0; th->unblock.arg = 0; - native_mutex_unlock(&th->interrupt_lock); + rb_native_mutex_unlock(&th->interrupt_lock); } GVL_UNLOCK_END(); } -static void -native_mutex_lock(rb_nativethread_lock_t *lock) +void +rb_native_mutex_lock(rb_nativethread_lock_t *lock) { #if USE_WIN32_MUTEX w32_mutex_lock(lock->mutex); @@ -312,8 +312,8 @@ native_mutex_lock(rb_nativethread_lock_t *lock) #endif } -static void -native_mutex_unlock(rb_nativethread_lock_t *lock) +void +rb_native_mutex_unlock(rb_nativethread_lock_t *lock) { #if USE_WIN32_MUTEX thread_debug("release mutex: %p\n", lock->mutex); @@ -343,8 +343,8 @@ native_mutex_trylock(rb_nativethread_lock_t *lock) #endif } -static void -native_mutex_initialize(rb_nativethread_lock_t *lock) +void +rb_native_mutex_initialize(rb_nativethread_lock_t *lock) { #if USE_WIN32_MUTEX lock->mutex = w32_mutex_create(); @@ -354,8 +354,8 @@ native_mutex_initialize(rb_nativethread_lock_t *lock) #endif } -static void -native_mutex_destroy(rb_nativethread_lock_t *lock) +void +rb_native_mutex_destroy(rb_nativethread_lock_t *lock) { #if USE_WIN32_MUTEX w32_close_handle(lock->mutex); @@ -370,9 +370,8 @@ struct cond_event_entry { HANDLE event; }; -#if 0 -static void -native_cond_signal(rb_nativethread_cond_t *cond) +void +rb_native_cond_signal(rb_nativethread_cond_t *cond) { /* cond is guarded by mutex */ struct cond_event_entry *e = cond->next; @@ -390,8 +389,8 @@ native_cond_signal(rb_nativethread_cond_t *cond) } } -static void -native_cond_broadcast(rb_nativethread_cond_t *cond) +void +rb_native_cond_broadcast(rb_nativethread_cond_t *cond) { /* cond is guarded by mutex */ struct cond_event_entry *e = cond->next; @@ -426,14 +425,14 @@ native_cond_timedwait_ms(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *m head->prev->next = &entry; head->prev = &entry; - native_mutex_unlock(mutex); + rb_native_mutex_unlock(mutex); { r = WaitForSingleObject(entry.event, msec); if ((r != WAIT_OBJECT_0) && (r != WAIT_TIMEOUT)) { - rb_bug("native_cond_wait: WaitForSingleObject returns %lu", r); + rb_bug("rb_native_cond_wait: WaitForSingleObject returns %lu", r); } } - native_mutex_lock(mutex); + rb_native_mutex_lock(mutex); entry.prev->next = entry.next; entry.next->prev = entry.prev; @@ -442,12 +441,13 @@ native_cond_timedwait_ms(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *m return (r == WAIT_OBJECT_0) ? 0 : ETIMEDOUT; } -static void -native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex) +void +rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex) { native_cond_timedwait_ms(cond, mutex, INFINITE); } +#if 0 static unsigned long abs_timespec_to_timeout_ms(const struct timespec *ts) { @@ -505,20 +505,20 @@ native_cond_timeout(rb_nativethread_cond_t *cond, struct timespec timeout_rel) return timeout; } +#endif -static void -native_cond_initialize(rb_nativethread_cond_t *cond, int flags) +void +rb_native_cond_initialize(rb_nativethread_cond_t *cond, int flags) { cond->next = (struct cond_event_entry *)cond; cond->prev = (struct cond_event_entry *)cond; } -static void -native_cond_destroy(rb_nativethread_cond_t *cond) +void +rb_native_cond_destroy(rb_nativethread_cond_t *cond) { /* */ } -#endif void ruby_init_stack(volatile VALUE *addr) @@ -777,4 +777,27 @@ native_set_thread_name(rb_thread_t *th) { } +static unsigned long __stdcall +mjit_worker(void *arg) +{ + void (*worker_func)(void) = arg; + rb_w32_set_thread_description(GetCurrentThread(), L"ruby-mjitworker"); + worker_func(); + return 0; +} + +/* Launch MJIT thread. Returns FALSE if it fails to create thread. */ +int +rb_thread_create_mjit_thread(void (*child_hook)(void), void (*worker_func)(void)) +{ + size_t stack_size = 4 * 1024; /* 4KB is the minimum commit size */ + HANDLE thread_id = w32_create_thread(stack_size, mjit_worker, worker_func); + if (thread_id == 0) { + return FALSE; + } + + w32_resume_thread(thread_id); + return TRUE; +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ @@ -298,6 +298,7 @@ static VALUE vm_invoke_proc(rb_execution_context_t *ec, rb_proc_t *proc, VALUE s static VALUE rb_block_param_proxy; +#include "mjit.h" #include "vm_insnhelper.h" #include "vm_exec.h" #include "vm_insnhelper.c" @@ -1786,8 +1787,10 @@ vm_exec(rb_execution_context_t *ec) _tag.retval = Qnil; if ((state = EC_EXEC_TAG()) == TAG_NONE) { + result = mjit_exec(ec); vm_loop_start: - result = vm_exec_core(ec, initial); + if (result == Qundef) + result = vm_exec_core(ec, initial); VM_ASSERT(ec->tag == &_tag); if ((state = _tag.state) != TAG_NONE) { err = (struct vm_throw_data *)result; @@ -1870,6 +1873,7 @@ vm_exec(rb_execution_context_t *ec) *ec->cfp->sp++ = THROW_DATA_VAL(err); #endif ec->errinfo = Qnil; + result = Qundef; goto vm_loop_start; } } @@ -1909,6 +1913,7 @@ vm_exec(rb_execution_context_t *ec) if (cfp == escape_cfp) { cfp->pc = cfp->iseq->body->iseq_encoded + entry->cont; ec->errinfo = Qnil; + result = Qundef; goto vm_loop_start; } } @@ -1943,6 +1948,7 @@ vm_exec(rb_execution_context_t *ec) } ec->errinfo = Qnil; VM_ASSERT(ec->tag->state == TAG_NONE); + result = Qundef; goto vm_loop_start; } } @@ -1994,6 +2000,7 @@ vm_exec(rb_execution_context_t *ec) state = 0; ec->tag->state = TAG_NONE; ec->errinfo = Qnil; + result = Qundef; goto vm_loop_start; } else { @@ -2122,6 +2129,8 @@ rb_vm_mark(void *ptr) rb_vm_trace_mark_event_hooks(&vm->event_hooks); rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); + + mjit_mark(); } RUBY_MARK_LEAVE("vm"); @@ -2742,6 +2751,12 @@ core_hash_merge_kwd(int argc, VALUE *argv) return hash; } +static VALUE +mjit_enabled_p(void) +{ + return mjit_init_p ? Qtrue : Qfalse; +} + extern VALUE *rb_gc_stack_start; extern size_t rb_gc_stack_maxsize; #ifdef __ia64 @@ -2795,6 +2810,7 @@ Init_VM(void) VALUE opts; VALUE klass; VALUE fcore; + VALUE mjit; /* ::RubyVM */ rb_cRubyVM = rb_define_class("RubyVM", rb_cObject); @@ -2826,6 +2842,10 @@ Init_VM(void) rb_gc_register_mark_object(fcore); rb_mRubyVMFrozenCore = fcore; + /* RubyVM::MJIT */ + mjit = rb_define_module_under(rb_cRubyVM, "MJIT"); + rb_define_singleton_method(mjit, "enabled?", mjit_enabled_p, 0); + /* * Document-class: Thread * @@ -292,6 +292,9 @@ pathobj_realpath(VALUE pathobj) } } +/* A forward declaration */ +struct rb_mjit_unit; + struct rb_iseq_constant_body { enum iseq_type { ISEQ_TYPE_TOP, @@ -414,6 +417,11 @@ struct rb_iseq_constant_body { unsigned int ci_size; unsigned int ci_kw_size; unsigned int stack_max; /* for stack overflow check */ + + /* The following fields are MJIT related info. */ + void *jit_func; /* function pointer for loaded native code */ + long unsigned total_calls; /* number of total calls with `mjit_exec()` */ + struct rb_mjit_unit *jit_unit; }; /* T_IMEMO/iseq */ diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 1d54c0b9a1..31dbdac3fa 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -129,7 +129,7 @@ enum vm_regan_acttype { #define CALL_METHOD(calling, ci, cc) do { \ VALUE v = (*(cc)->call)(ec, GET_CFP(), (calling), (ci), (cc)); \ - if (v == Qundef) { \ + if (v == Qundef && (v = mjit_exec(ec)) == Qundef) { \ RESTORE_REGS(); \ NEXT_INSN(); \ } \ diff --git a/win32/Makefile.sub b/win32/Makefile.sub index cbd36e0405..15f13a90ca 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -276,7 +276,7 @@ LDSHARED_0 = @if exist $(@).manifest $(MINIRUBY) -run -e wait_writable -- -n 10 LDSHARED_1 = @if exist $(@).manifest $(MANIFESTTOOL) -manifest $(@).manifest -outputresource:$(@);2 LDSHARED_2 = @if exist $(@).manifest @$(RM) $(@:/=\).manifest !endif -CPPFLAGS = $(DEFS) $(ARCHDEFS) $(CPPFLAGS) +CPPFLAGS = $(DEFS) $(ARCHDEFS) $(CPPFLAGS) -DMJIT_HEADER_BUILD_DIR=\""$(EXTOUT)/include/$(arch)"\" -DLIBRUBYARG_SHARED=\""$(LIBRUBYARG_SHARED)"\" -DLIBRUBY_LIBDIR=\""$(prefix)/lib"\" -DMJIT_HEADER_INSTALL_DIR=\""$(prefix)/include/$(RUBY_BASE_NAME)-$(ruby_version)/$(arch)"\" DLDFLAGS = $(LDFLAGS) -dll SOLIBS = |