Permalink
Browse files

mjit_compile.c: merge initial JIT compiler

which has been developed by Takashi Kokubun <takashikkbn@gmail> as
YARV-MJIT. Many of its bugs are fixed by wanabe <s.wanabe@gmail.com>.

This JIT compiler is designed to be a safe migration path to introduce
JIT compiler to MRI. So this commit does not include any bytecode
changes or dynamic instruction modifications, which are done in original
MJIT.

This commit even strips off some aggressive optimizations from
YARV-MJIT, and thus it's slower than YARV-MJIT too. But it's still
fairly faster than Ruby 2.5 in some benchmarks (attached below).

Note that this JIT compiler passes `make test`, `make test-all`, `make
test-spec` without JIT, and even with JIT. Not only it's perfectly safe
with JIT disabled because it does not replace VM instructions unlike
MJIT, but also with JIT enabled it stably runs Ruby applications
including Rails applications.

I'm expecting this version as just "initial" JIT compiler. I have many
optimization ideas which are skipped for initial merging, and you may
easily replace this JIT compiler with a faster one by just replacing
mjit_compile.c. `mjit_compile` interface is designed for the purpose.

common.mk: update dependencies for mjit_compile.c.

internal.h: declare `rb_vm_insn_addr2insn` for MJIT.

vm.c: exclude some definitions if `-DMJIT_HEADER` is provided to
compiler. This avoids to include some functions which take a long time
to compile, e.g. vm_exec_core. Some of the purpose is achieved in
transform_mjit_header.rb (see `IGNORED_FUNCTIONS`) but others are
manually resolved for now. Load mjit_helper.h for MJIT header.
mjit_helper.h: New. This is a file used only by JIT-ed code. I'll
refactor `mjit_call_cfunc` later.
vm_eval.c: add some #ifdef switches to skip compiling some functions
like Init_vm_eval.

win32/mkexports.rb: export thread/ec functions, which are used by MJIT.

include/ruby/defines.h: add MJIT_FUNC_EXPORTED macro alis to clarify
that a function is exported only for MJIT.

array.c: export a function used by MJIT.
bignum.c: ditto.
class.c: ditto.
compile.c: ditto.
error.c: ditto.
gc.c: ditto.
hash.c: ditto.
iseq.c: ditto.
numeric.c: ditto.
object.c: ditto.
proc.c: ditto.
re.c: ditto.
st.c: ditto.
string.c: ditto.
thread.c: ditto.
variable.c: ditto.
vm_backtrace.c: ditto.
vm_insnhelper.c: ditto.
vm_method.c: ditto.

I would like to improve maintainability of function exports, but I
believe this way is acceptable as initial merging if we clarify the
new exports are for MJIT (so that we can use them as TODO list to fix)
and add unit tests to detect unresolved symbols.
I'll add unit tests of JIT compilations in succeeding commits.

Author: Takashi Kokubun <takashikkbn@gmail.com>
Contributor: wanabe <s.wanabe@gmail.com>

Part of [Feature #14235]

---

* Known issues
  * Code generated by gcc is faster than clang. The benchmark may be worse
    in macOS. Following benchmark result is provided by gcc w/ Linux.
  * Performance is decreased when Google Chrome is running
  * JIT can work on MinGW, but it doesn't improve performance at least
    in short running benchmark.
  * Currently it doesn't perform well with Rails. We'll try to fix this
    before release.

---

* Benchmark reslts

Benchmarked with:
Intel 4.0GHz i7-4790K with 16GB memory under x86-64 Ubuntu 8 Cores

- 2.0.0-p0: Ruby 2.0.0-p0
- r62186: Ruby trunk (early 2.6.0), before MJIT changes
- JIT off: On this commit, but without `--jit` option
- JIT on: On this commit, and with `--jit` option

** Optcarrot fps

Benchmark: https://github.com/mame/optcarrot

|         |2.0.0-p0 |r62186   |JIT off  |JIT on   |
|:--------|:--------|:--------|:--------|:--------|
|fps      |37.32    |51.46    |51.31    |58.88    |
|vs 2.0.0 |1.00x    |1.38x    |1.37x    |1.58x    |

** MJIT benchmarks

Benchmark: https://github.com/benchmark-driver/mjit-benchmarks
(Original: https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch/MJIT-benchmarks)

|           |2.0.0-p0 |r62186   |JIT off  |JIT on   |
|:----------|:--------|:--------|:--------|:--------|
|aread      |1.00     |1.09     |1.07     |2.19     |
|aref       |1.00     |1.13     |1.11     |2.22     |
|aset       |1.00     |1.50     |1.45     |2.64     |
|awrite     |1.00     |1.17     |1.13     |2.20     |
|call       |1.00     |1.29     |1.26     |2.02     |
|const2     |1.00     |1.10     |1.10     |2.19     |
|const      |1.00     |1.11     |1.10     |2.19     |
|fannk      |1.00     |1.04     |1.02     |1.00     |
|fib        |1.00     |1.32     |1.31     |1.84     |
|ivread     |1.00     |1.13     |1.12     |2.43     |
|ivwrite    |1.00     |1.23     |1.21     |2.40     |
|mandelbrot |1.00     |1.13     |1.16     |1.28     |
|meteor     |1.00     |2.97     |2.92     |3.17     |
|nbody      |1.00     |1.17     |1.15     |1.49     |
|nest-ntimes|1.00     |1.22     |1.20     |1.39     |
|nest-while |1.00     |1.10     |1.10     |1.37     |
|norm       |1.00     |1.18     |1.16     |1.24     |
|nsvb       |1.00     |1.16     |1.16     |1.17     |
|red-black  |1.00     |1.02     |0.99     |1.12     |
|sieve      |1.00     |1.30     |1.28     |1.62     |
|trees      |1.00     |1.14     |1.13     |1.19     |
|while      |1.00     |1.12     |1.11     |2.41     |

** Discourse's script/bench.rb

Benchmark: https://github.com/discourse/discourse/blob/v1.8.7/script/bench.rb

NOTE: Rails performance was somehow a little degraded with JIT for now.
We should fix this.
(At least I know opt_aref is performing badly in JIT and I have an idea
 to fix it. Please wait for the fix.)

*** JIT off
Your Results: (note for timings- percentile is first, duration is second in millisecs)

categories_admin:
  50: 17
  75: 18
  90: 22
  99: 29
home_admin:
  50: 21
  75: 21
  90: 27
  99: 40
topic_admin:
  50: 17
  75: 18
  90: 22
  99: 32
categories:
  50: 35
  75: 41
  90: 43
  99: 77
home:
  50: 39
  75: 46
  90: 49
  99: 95
topic:
  50: 46
  75: 52
  90: 56
  99: 101

*** JIT on
Your Results: (note for timings- percentile is first, duration is second in millisecs)

categories_admin:
  50: 19
  75: 21
  90: 25
  99: 33
home_admin:
  50: 24
  75: 26
  90: 30
  99: 35
topic_admin:
  50: 19
  75: 20
  90: 25
  99: 30
categories:
  50: 40
  75: 44
  90: 48
  99: 76
home:
  50: 42
  75: 48
  90: 51
  99: 89
topic:
  50: 49
  75: 55
  90: 58
  99: 99

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62197 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
  • Loading branch information...
k0kubun committed Feb 4, 2018
1 parent 56530b5 commit ed935aa5be0e5e6b8d53c3e7d76a9ce395dfa18b
View
@@ -516,7 +516,7 @@ update-simplecov:
update-coverage: update-simplecov update-simplecov-html update-doclie
INSNS = opt_sc.inc optinsn.inc optunifs.inc insns.inc insns_info.inc \
- vmtc.inc vm.inc
+ vmtc.inc vm.inc mjit_compile.inc
$(INSNS): $(srcdir)/insns.def vm_opts.h \
$(srcdir)/defs/opt_operand.def $(srcdir)/defs/opt_insn_unif.def \
View
@@ -506,7 +506,7 @@ VALUE
return ary;
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_ary_tmp_new_from_values(VALUE klass, long n, const VALUE *elts)
{
VALUE ary;
@@ -640,7 +640,7 @@ rb_check_array_type(VALUE ary)
return rb_check_convert_type_with_id(ary, T_ARRAY, "Array", idTo_ary);
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_check_to_array(VALUE ary)
{
return rb_check_convert_type_with_id(ary, T_ARRAY, "Array", idTo_a);
@@ -1297,7 +1297,7 @@ rb_ary_aref2(VALUE ary, VALUE b, VALUE e)
return rb_ary_subseq(ary, beg, len);
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_ary_aref1(VALUE ary, VALUE arg)
{
long beg, len;
View
@@ -4490,7 +4490,7 @@ rb_uint128t2big(uint128_t n)
return big;
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_int128t2big(int128_t n)
{
int neg = 0;
View
@@ -616,7 +616,7 @@ rb_define_class_id(ID id, VALUE super)
* \return the value \c Class#inherited's returns
* \pre Each of \a super and \a klass must be a \c Class object.
*/
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_class_inherited(VALUE super, VALUE klass)
{
ID inherited;
@@ -1773,7 +1773,7 @@ rb_define_attr(VALUE klass, const char *name, int read, int write)
rb_attr(klass, rb_intern(name), read, write, FALSE);
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_keyword_error_new(const char *error, VALUE keys)
{
const VALUE *ptr = RARRAY_CONST_PTR(keys);
View
@@ -890,6 +890,7 @@ $(srcs_vpath)insns.inc: $(srcdir)/tool/ruby_vm/views/insns.inc.erb
$(srcs_vpath)insns_info.inc: $(srcdir)/tool/ruby_vm/views/insns_info.inc.erb
$(srcs_vpath)vmtc.inc: $(srcdir)/tool/ruby_vm/views/vmtc.inc.erb
$(srcs_vpath)vm.inc: $(srcdir)/tool/ruby_vm/views/vm.inc.erb
+$(srcs_vpath)mjit_compile.inc: $(srcdir)/tool/ruby_vm/views/mjit_compile.inc.erb $(srcdir)/tool/ruby_vm/views/_mjit_compile_insn.erb $(srcdir)/tool/ruby_vm/views/_mjit_compile_send.erb
common-srcs: $(srcs_vpath)parse.c $(srcs_vpath)lex.c $(srcs_vpath)enc/trans/newline.c $(srcs_vpath)id.c \
srcs-lib srcs-ext incs
@@ -2003,8 +2004,12 @@ 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)}insns.inc
+mjit_compile.$(OBJEXT): {$(VPATH)}insns_info.inc
mjit_compile.$(OBJEXT): {$(VPATH)}internal.h
+mjit_compile.$(OBJEXT): {$(VPATH)}mjit.h
mjit_compile.$(OBJEXT): {$(VPATH)}mjit_compile.c
+mjit_compile.$(OBJEXT): {$(VPATH)}mjit_compile.inc
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
View
@@ -754,7 +754,7 @@ rb_iseq_translate_threaded_code(rb_iseq_t *iseq)
}
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
-static int
+int
rb_vm_insn_addr2insn(const void *addr) /* cold path */
{
int insn;
View
@@ -1161,7 +1161,7 @@ exc_set_backtrace(VALUE exc, VALUE bt)
return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt));
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_exc_set_backtrace(VALUE exc, VALUE bt)
{
return exc_set_backtrace(exc, bt);
View
5 gc.c
@@ -2216,6 +2216,7 @@ obj_free(rb_objspace_t *objspace, VALUE obj)
break;
case T_MODULE:
case T_CLASS:
+ mjit_remove_class_serial(RCLASS_SERIAL(obj));
rb_id_table_free(RCLASS_M_TBL(obj));
if (RCLASS_IV_TBL(obj)) {
st_free_table(RCLASS_IV_TBL(obj));
@@ -4054,7 +4055,7 @@ stack_check(rb_execution_context_t *ec, int water_mark)
#define STACKFRAME_FOR_CALL_CFUNC 838
-int
+MJIT_FUNC_EXPORTED int
rb_ec_stack_check(rb_execution_context_t *ec)
{
return stack_check(ec, STACKFRAME_FOR_CALL_CFUNC);
@@ -6053,7 +6054,7 @@ rb_gc_writebarrier_unprotect(VALUE obj)
/*
* remember `obj' if needed.
*/
-void
+MJIT_FUNC_EXPORTED void
rb_gc_writebarrier_remember(VALUE obj)
{
rb_objspace_t *objspace = &rb_objspace;
View
10 hash.c
@@ -443,7 +443,7 @@ rb_hash_new_compare_by_id(void)
return hash;
}
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_hash_new_with_size(st_index_t size)
{
VALUE ret = rb_hash_new();
@@ -495,7 +495,7 @@ rb_hash_tbl(VALUE hash)
return hash_tbl(hash);
}
-struct st_table *
+MJIT_FUNC_EXPORTED struct st_table *
rb_hash_tbl_raw(VALUE hash)
{
return hash_tbl(hash);
@@ -2155,7 +2155,7 @@ keys_i(VALUE key, VALUE value, VALUE ary)
*
*/
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_hash_keys(VALUE hash)
{
VALUE keys;
@@ -2243,7 +2243,7 @@ rb_hash_values(VALUE hash)
* See also Enumerable#include?
*/
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_hash_has_key(VALUE hash, VALUE key)
{
if (!RHASH(hash)->ntbl)
@@ -2949,7 +2949,7 @@ rb_hash_compare_by_id(VALUE hash)
*
*/
-VALUE
+MJIT_FUNC_EXPORTED VALUE
rb_hash_compare_by_id_p(VALUE hash)
{
if (!RHASH(hash)->ntbl)
View
@@ -270,6 +270,10 @@ void xfree(void*);
#define RUBY_FUNC_EXPORTED
#endif
+/* MJIT_FUNC_EXPORTED is used for functions which are exported only for MJIT
+ and NOT ensured to be exported in future versions. */
+#define MJIT_FUNC_EXPORTED RUBY_FUNC_EXPORTED
+
#ifndef RUBY_EXTERN
#define RUBY_EXTERN extern
#endif
View
@@ -695,8 +695,7 @@ defineclass
class_iseq->body->iseq_encoded, GET_SP(),
class_iseq->body->local_table_size,
class_iseq->body->stack_max);
- RESTORE_REGS();
- NEXT_INSN();
+ EXEC_EC_CFP();
}
/**********************************************************/
@@ -823,8 +822,7 @@ invokeblock
val = vm_invoke_block(ec, GET_CFP(), &calling, ci, block_handler);
if (val == Qundef) {
- RESTORE_REGS();
- NEXT_INSN();
+ EXEC_EC_CFP();
}
}
@@ -1090,7 +1088,9 @@ opt_neq
val = vm_opt_neq(ci, cc, ci_eq, cc_eq, recv, obj);
if (val == Qundef) {
+#ifndef MJIT_HEADER
ADD_PC(2); /* !!! */
+#endif
DISPATCH_ORIGINAL_INSN(opt_send_without_block);
}
}
@@ -1206,9 +1206,11 @@ opt_aset_with
val = tmp;
}
else {
+#ifndef MJIT_HEADER
TOPN(0) = rb_str_resurrect(key);
PUSH(val);
ADD_PC(1); /* !!! */
+#endif
DISPATCH_ORIGINAL_INSN(opt_send_without_block);
}
}
@@ -1223,8 +1225,10 @@ opt_aref_with
val = vm_opt_aref_with(recv, key);
if (val == Qundef) {
+#ifndef MJIT_HEADER
PUSH(rb_str_resurrect(key));
ADD_PC(1); /* !!! */
+#endif
DISPATCH_ORIGINAL_INSN(opt_send_without_block);
}
}
@@ -1339,8 +1343,7 @@ opt_call_c_function
THROW_EXCEPTION(err);
}
- RESTORE_REGS();
- NEXT_INSN();
+ EXEC_EC_CFP();
}
/* BLT */
View
@@ -1124,6 +1124,7 @@ int rb_dvar_defined(ID, const struct rb_block *);
int rb_local_defined(ID, const struct rb_block *);
const char * rb_insns_name(int i);
VALUE rb_insns_name_array(void);
+int rb_vm_insn_addr2insn(const void *);
/* complex.c */
VALUE rb_complex_plus(VALUE, VALUE);
View
2 iseq.c
@@ -1480,7 +1480,7 @@ rb_iseq_line_no(const rb_iseq_t *iseq, size_t pos)
}
}
-rb_event_flag_t
+MJIT_FUNC_EXPORTED rb_event_flag_t
rb_iseq_event_flags(const rb_iseq_t *iseq, size_t pos)
{
const struct iseq_insn_info_entry *entry = get_insn_info(iseq, pos);
View
62 mjit.c
@@ -83,6 +83,8 @@
#include "mjit.h"
#include "version.h"
#include "gc.h"
+#include "constant.h"
+#include "id_table.h"
#include "ruby_assert.h"
extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock);
@@ -194,6 +196,9 @@ static char *header_file;
static char *pch_file;
/* Path of "/tmp", which can be changed to $TMP in MinGW. */
static char *tmp_dir;
+/* Hash like { 1 => true, 2 => true, ... } whose keys are valid `class_serial`s.
+ This is used to invalidate obsoleted CALL_CACHE. */
+static VALUE valid_class_serials;
/* Ruby level interface module. */
VALUE rb_mMJIT;
@@ -1081,6 +1086,19 @@ child_after_fork(void)
/* TODO: Should we initiate MJIT in the forked Ruby. */
}
+static enum rb_id_table_iterator_result
+valid_class_serials_add_i(ID key, VALUE v, void *unused)
+{
+ rb_const_entry_t *ce = (rb_const_entry_t *)v;
+ VALUE value = ce->value;
+
+ if (!rb_is_const_id(key)) return ID_TABLE_CONTINUE;
+ if (RB_TYPE_P(value, T_MODULE) || RB_TYPE_P(value, T_CLASS)) {
+ mjit_add_class_serial(RCLASS_SERIAL(value));
+ }
+ return ID_TABLE_CONTINUE;
+}
+
/* Default permitted number of units with a JIT code kept in
memory. */
#define DEFAULT_CACHE_SIZE 1000
@@ -1149,6 +1167,14 @@ mjit_init(struct mjit_options *opts)
rb_native_cond_initialize(&mjit_worker_wakeup, RB_CONDATTR_CLOCK_MONOTONIC);
rb_native_cond_initialize(&mjit_gc_wakeup, RB_CONDATTR_CLOCK_MONOTONIC);
+ /* Initialize class_serials cache for compilation */
+ valid_class_serials = rb_hash_new();
+ rb_obj_hide(valid_class_serials);
+ rb_gc_register_mark_object(valid_class_serials);
+ if (RCLASS_CONST_TBL(rb_cObject)) {
+ rb_id_table_foreach(RCLASS_CONST_TBL(rb_cObject), valid_class_serials_add_i, NULL);
+ }
+
/* Initialize worker thread */
finish_worker_p = FALSE;
worker_finished = FALSE;
@@ -1233,3 +1259,39 @@ mjit_mark(void)
CRITICAL_SECTION_FINISH(4, "mjit_mark");
RUBY_MARK_LEAVE("mjit");
}
+
+/* A hook to update valid_class_serials. This should NOT be used in MJIT worker. */
+void
+mjit_add_class_serial(rb_serial_t class_serial)
+{
+ if (!mjit_init_p)
+ return;
+
+ CRITICAL_SECTION_START(3, "in mjit_add_class_serial");
+ rb_hash_aset(valid_class_serials, LONG2FIX(class_serial), Qtrue);
+ CRITICAL_SECTION_FINISH(3, "in mjit_add_class_serial");
+}
+
+/* A hook to update valid_class_serials. This should NOT be used in MJIT worker. */
+void
+mjit_remove_class_serial(rb_serial_t class_serial)
+{
+ if (!mjit_init_p)
+ return;
+
+ CRITICAL_SECTION_START(3, "in mjit_remove_class_serial");
+ rb_hash_delete_entry(valid_class_serials, LONG2FIX(class_serial));
+ CRITICAL_SECTION_FINISH(3, "in mjit_remove_class_serial");
+}
+
+/* Return TRUE if class_serial is not obsoleted. This can be used in MJIT worker. */
+int
+mjit_valid_class_serial_p(rb_serial_t class_serial)
+{
+ int found_p;
+
+ CRITICAL_SECTION_START(3, "in valid_class_serial_p");
+ found_p = st_lookup(RHASH_TBL_RAW(valid_class_serials), LONG2FIX(class_serial), NULL);
+ CRITICAL_SECTION_FINISH(3, "in valid_class_serial_p");
+ return found_p;
+}
View
3 mjit.h
@@ -80,6 +80,9 @@ 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);
+extern void mjit_add_class_serial(rb_serial_t class_serial);
+extern void mjit_remove_class_serial(rb_serial_t class_serial);
+extern int mjit_valid_class_serial_p(rb_serial_t class_serial);
/* A threshold used to reject long iseqs from JITting as such iseqs
takes too much time to be compiled. */
Oops, something went wrong.

0 comments on commit ed935aa

Please sign in to comment.