Skip to content

Commit

Permalink
mjit_compile.c: merge initial JIT compiler
Browse files Browse the repository at this point in the history
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 ed935aa
Show file tree
Hide file tree
Showing 50 changed files with 680 additions and 101 deletions.
2 changes: 1 addition & 1 deletion Makefile.in
Expand Up @@ -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 \
Expand Down
6 changes: 3 additions & 3 deletions array.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion bignum.c
Expand Up @@ -4490,7 +4490,7 @@ rb_uint128t2big(uint128_t n)
return big;
}

VALUE
MJIT_FUNC_EXPORTED VALUE
rb_int128t2big(int128_t n)
{
int neg = 0;
Expand Down
4 changes: 2 additions & 2 deletions class.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions common.mk
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compile.c
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion error.c
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions gc.c
Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions hash.c
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2155,7 +2155,7 @@ keys_i(VALUE key, VALUE value, VALUE ary)
*
*/

VALUE
MJIT_FUNC_EXPORTED VALUE
rb_hash_keys(VALUE hash)
{
VALUE keys;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions include/ruby/defines.h
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions insns.def
Expand Up @@ -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();
}

/**********************************************************/
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -1339,8 +1343,7 @@ opt_call_c_function
THROW_EXCEPTION(err);
}

RESTORE_REGS();
NEXT_INSN();
EXEC_EC_CFP();
}

/* BLT */
Expand Down
1 change: 1 addition & 0 deletions internal.h
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion iseq.c
Expand Up @@ -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);
Expand Down
62 changes: 62 additions & 0 deletions mjit.c
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions mjit.h
Expand Up @@ -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. */
Expand Down

0 comments on commit ed935aa

Please sign in to comment.