summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in2
-rw-r--r--common.mk17
-rw-r--r--cont.c9
-rw-r--r--eval.c3
-rw-r--r--gc.c5
-rw-r--r--iseq.c2
-rw-r--r--mjit.c1219
-rw-r--r--mjit.h138
-rw-r--r--mjit_compile.c18
-rw-r--r--ruby.c75
-rw-r--r--test/lib/zombie_hunter.rb4
-rw-r--r--test/ruby/test_io.rb3
-rw-r--r--test/ruby/test_rubyoptions.rb2
-rw-r--r--thread.c42
-rw-r--r--thread_pthread.c189
-rw-r--r--thread_win32.c85
-rw-r--r--vm.c22
-rw-r--r--vm_core.h8
-rw-r--r--vm_insnhelper.h2
-rw-r--r--win32/Makefile.sub2
20 files changed, 1713 insertions, 134 deletions
diff --git a/Makefile.in b/Makefile.in
index bca6696..b91f4d2 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)
diff --git a/common.mk b/common.mk
index 28486ba..5f657fc 100644
--- a/common.mk
+++ b/common.mk
@@ -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
diff --git a/cont.c b/cont.c
index 93a64f8..9cb0d96 100644
--- a/cont.c
+++ b/cont.c
@@ -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 *
diff --git a/eval.c b/eval.c
index 40601cc..7c27cd9 100644
--- a/eval.c
+++ b/eval.c
@@ -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. */
diff --git a/gc.c b/gc.c
index fd69acf..cfa345e 100644
--- a/gc.c
+++ b/gc.c
@@ -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 *
diff --git a/iseq.c b/iseq.c
index 58e8eb8..724f81b 100644
--- a/iseq.c
+++ b/iseq.c
@@ -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 0000000..1a2ad32
--- /dev/null
+++ b/mjit.c
@@ -0,0 +1,1219 @@
+/**********************************************************************
+
+ mjit.c - Infrastructure for MRI method JIT compiler
+
+ Copyright (C) 2017 Vladimir Makarov <vmakarov@redhat.com>.
+
+**********************************************************************/
+
+/* 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 0000000..452110e
--- /dev/null
+++ b/mjit.h
@@ -0,0 +1,138 @@
+/**********************************************************************
+
+ mjit.h - Interface to MRI method JIT compiler
+
+ Copyright (C) 2017 Vladimir Makarov <vmakarov@redhat.com>.
+
+**********************************************************************/
+
+#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 0000000..a48ae84
--- /dev/null
+++ b/mjit_compile.c
@@ -0,0 +1,18 @@
+/**********************************************************************
+
+ mjit_compile.c - MRI method JIT compiler
+
+ Copyright (C) 2017 Takashi Kokubun <takashikkbn@gmail.com>.
+
+**********************************************************************/
+
+#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;
+}
diff --git a/ruby.c b/ruby.c
index 358af62..925e4cd 100644
--- a/ruby.c
+++ b/ruby.c
@@ -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 2b81e39..ea94844 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 0b26608..2f0a8a7 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 083dcec..dac38a1 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)
diff --git a/thread.c b/thread.c
index cc8f5a8..f58dccb 100644
--- a/thread.c
+++ b/thread.c
@@ -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 f54e5f0..9401dcc 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 5619fb4..bb330e9 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 */
diff --git a/vm.c b/vm.c
index 6c1c63e..0ff180a 100644
--- a/vm.c
+++ b/vm.c
@@ -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
*
diff --git a/vm_core.h b/vm_core.h
index 8bce789..def9051 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -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 1d54c0b..31dbdac 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 cbd36e0..15f13a9 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 =