mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 16:44:01 +02:00
MJIT: Compile methods in batches (#6900)
* MJIT: Compile methods in batches * MJIT: make mjit-bindgen * MJIT: Fix RubyVM::MJIT tests
This commit is contained in:
parent
7055574cf9
commit
9d59d093bd
Notes:
git
2022-12-11 06:21:25 +00:00
Merged-By: k0kubun <takashikkbn@gmail.com>
6 changed files with 339 additions and 395 deletions
397
mjit.c
397
mjit.c
|
@ -124,12 +124,6 @@
|
||||||
|
|
||||||
#define MJIT_TMP_PREFIX "_ruby_mjit_"
|
#define MJIT_TMP_PREFIX "_ruby_mjit_"
|
||||||
|
|
||||||
// Linked list of struct rb_mjit_unit.
|
|
||||||
struct rb_mjit_unit_list {
|
|
||||||
struct ccan_list_head head;
|
|
||||||
int length; // the list length
|
|
||||||
};
|
|
||||||
|
|
||||||
extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock);
|
extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock);
|
||||||
extern void rb_native_mutex_unlock(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_initialize(rb_nativethread_lock_t *lock);
|
||||||
|
@ -148,6 +142,11 @@ bool mjit_enabled = false;
|
||||||
// true if JIT-ed code should be called. When `ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS`
|
// true if JIT-ed code should be called. When `ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS`
|
||||||
// and `mjit_call_p == false`, any JIT-ed code execution is cancelled as soon as possible.
|
// and `mjit_call_p == false`, any JIT-ed code execution is cancelled as soon as possible.
|
||||||
bool mjit_call_p = false;
|
bool mjit_call_p = false;
|
||||||
|
// There's an ISEQ in unit_queue whose total_calls reached 2 * call_threshold.
|
||||||
|
// If this is true, check_unit_queue will start compiling ISEQs in unit_queue.
|
||||||
|
static bool mjit_compile_p = false;
|
||||||
|
// The actual number of units in active_units
|
||||||
|
static int active_units_length = 0;
|
||||||
|
|
||||||
// Priority queue of iseqs waiting for JIT compilation.
|
// Priority queue of iseqs waiting for JIT compilation.
|
||||||
// This variable is a pointer to head unit of the queue.
|
// This variable is a pointer to head unit of the queue.
|
||||||
|
@ -162,10 +161,6 @@ static struct rb_mjit_unit_list stale_units = { CCAN_LIST_HEAD_INIT(stale_units.
|
||||||
static int current_unit_num;
|
static int current_unit_num;
|
||||||
// A mutex for conitionals and critical sections.
|
// A mutex for conitionals and critical sections.
|
||||||
static rb_nativethread_lock_t mjit_engine_mutex;
|
static rb_nativethread_lock_t mjit_engine_mutex;
|
||||||
// The times when unload_units is requested. unload_units is called after some requests.
|
|
||||||
static int unload_requests = 0;
|
|
||||||
// The total number of unloaded units.
|
|
||||||
static int total_unloads = 0;
|
|
||||||
// Set to true to stop worker.
|
// Set to true to stop worker.
|
||||||
static bool stop_worker_p;
|
static bool stop_worker_p;
|
||||||
// Set to true if worker is stopped.
|
// Set to true if worker is stopped.
|
||||||
|
@ -662,10 +657,17 @@ c_compile_unit(struct rb_mjit_unit *unit)
|
||||||
|
|
||||||
static void compile_prelude(FILE *f);
|
static void compile_prelude(FILE *f);
|
||||||
|
|
||||||
// Compile all JIT code into a single .c file
|
|
||||||
static bool
|
static bool
|
||||||
mjit_compact(char* c_file)
|
mjit_batch(struct rb_mjit_unit *unit)
|
||||||
{
|
{
|
||||||
|
VM_ASSERT(unit->type == MJIT_UNIT_BATCH);
|
||||||
|
static const char c_ext[] = ".c";
|
||||||
|
static const char so_ext[] = DLEXT;
|
||||||
|
char c_file[MAXPATHLEN], so_file[MAXPATHLEN];
|
||||||
|
|
||||||
|
sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext);
|
||||||
|
sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
|
||||||
|
|
||||||
FILE *f;
|
FILE *f;
|
||||||
int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600);
|
int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600);
|
||||||
if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
|
if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
|
||||||
|
@ -677,18 +679,11 @@ mjit_compact(char* c_file)
|
||||||
|
|
||||||
compile_prelude(f);
|
compile_prelude(f);
|
||||||
|
|
||||||
// This entire loop lock GC so that we do not need to consider a case that
|
|
||||||
// ISeq is GC-ed in a middle of re-compilation. It takes 3~4ms with 100 methods
|
|
||||||
// on my machine. It's not too bad compared to compilation time of C (7200~8000ms),
|
|
||||||
// but it might be larger if we use a larger --jit-max-cache.
|
|
||||||
//
|
|
||||||
// TODO: Consider using a more granular lock after we implement inlining across
|
|
||||||
// compacted functions (not done yet).
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
struct rb_mjit_unit *child_unit = 0;
|
struct rb_mjit_unit *child_unit = 0;
|
||||||
ccan_list_for_each(&active_units.head, child_unit, unode) {
|
ccan_list_for_each(&unit->units.head, child_unit, unode) {
|
||||||
if (!success) continue;
|
if (!success) continue;
|
||||||
if (ISEQ_BODY(child_unit->iseq)->mjit_unit == NULL) continue; // Sometimes such units are created. TODO: Investigate why
|
if (child_unit->iseq == NULL) continue; // ISEQ is GCed
|
||||||
|
|
||||||
char funcname[MAXPATHLEN];
|
char funcname[MAXPATHLEN];
|
||||||
sprint_funcname(funcname, sizeof(funcname), child_unit);
|
sprint_funcname(funcname, sizeof(funcname), child_unit);
|
||||||
|
@ -709,8 +704,9 @@ mjit_compact(char* c_file)
|
||||||
// Compile all cached .c files and build a single .so file. Reload all JIT func from it.
|
// Compile all cached .c files and build a single .so file. Reload all JIT func from it.
|
||||||
// This improves the code locality for better performance in terms of iTLB and iCache.
|
// This improves the code locality for better performance in terms of iTLB and iCache.
|
||||||
static bool
|
static bool
|
||||||
mjit_compact_unit(struct rb_mjit_unit *unit)
|
mjit_compact(struct rb_mjit_unit *unit)
|
||||||
{
|
{
|
||||||
|
VM_ASSERT(unit->type == MJIT_UNIT_COMPACT);
|
||||||
static const char c_ext[] = ".c";
|
static const char c_ext[] = ".c";
|
||||||
static const char so_ext[] = DLEXT;
|
static const char so_ext[] = DLEXT;
|
||||||
char c_file[MAXPATHLEN], so_file[MAXPATHLEN];
|
char c_file[MAXPATHLEN], so_file[MAXPATHLEN];
|
||||||
|
@ -718,13 +714,91 @@ mjit_compact_unit(struct rb_mjit_unit *unit)
|
||||||
sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext);
|
sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext);
|
||||||
sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
|
sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
|
||||||
|
|
||||||
return mjit_compact(c_file);
|
FILE *f;
|
||||||
|
int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600);
|
||||||
|
if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
|
||||||
|
int e = errno;
|
||||||
|
if (fd >= 0) (void)close(fd);
|
||||||
|
verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_prelude(f);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
struct rb_mjit_unit *batch_unit = 0, *child_unit = 0;
|
||||||
|
ccan_list_for_each(&active_units.head, batch_unit, unode) {
|
||||||
|
ccan_list_for_each(&batch_unit->units.head, child_unit, unode) {
|
||||||
|
if (!success) continue;
|
||||||
|
if (child_unit->iseq == NULL) continue; // ISEQ is GCed
|
||||||
|
|
||||||
|
char funcname[MAXPATHLEN];
|
||||||
|
sprint_funcname(funcname, sizeof(funcname), child_unit);
|
||||||
|
|
||||||
|
int iseq_lineno = ISEQ_BODY(child_unit->iseq)->location.first_lineno;
|
||||||
|
const char *sep = "@";
|
||||||
|
const char *iseq_label = RSTRING_PTR(ISEQ_BODY(child_unit->iseq)->location.label);
|
||||||
|
const char *iseq_path = RSTRING_PTR(rb_iseq_path(child_unit->iseq));
|
||||||
|
if (!iseq_label) iseq_label = sep = "";
|
||||||
|
fprintf(f, "\n/* %s%s%s:%d */\n", iseq_label, sep, iseq_path, iseq_lineno);
|
||||||
|
success &= mjit_compile(f, child_unit->iseq, funcname, child_unit->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_batch_funcs_from_so(struct rb_mjit_unit *unit, char *c_file, char *so_file)
|
||||||
|
{
|
||||||
|
double end_time = real_ms_time();
|
||||||
|
|
||||||
|
void *handle = dlopen(so_file, RTLD_NOW);
|
||||||
|
if (handle == NULL) {
|
||||||
|
mjit_warning("failure in loading code from batched '%s': %s", so_file, dlerror());
|
||||||
|
xfree(unit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unit->handle = handle;
|
||||||
|
|
||||||
|
// lazily dlclose handle on `mjit_finish()`.
|
||||||
|
add_to_list(unit, &active_units);
|
||||||
|
active_units_length += unit->units.length;
|
||||||
|
|
||||||
|
if (!mjit_opts.save_temps)
|
||||||
|
remove_so_file(so_file, unit);
|
||||||
|
|
||||||
|
struct rb_mjit_unit *child_unit = 0;
|
||||||
|
ccan_list_for_each(&unit->units.head, child_unit, unode) {
|
||||||
|
char funcname[MAXPATHLEN];
|
||||||
|
sprint_funcname(funcname, sizeof(funcname), child_unit);
|
||||||
|
|
||||||
|
void *func;
|
||||||
|
if ((func = dlsym(handle, funcname)) == NULL) {
|
||||||
|
mjit_warning("skipping to load '%s' from '%s': %s", funcname, so_file, dlerror());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child_unit->iseq) { // Check whether GCed or not
|
||||||
|
// Usage of jit_code might be not in a critical section.
|
||||||
|
const rb_iseq_t *iseq = child_unit->iseq;
|
||||||
|
MJIT_ATOMIC_SET(ISEQ_BODY(iseq)->jit_func, (jit_func_t)func);
|
||||||
|
|
||||||
|
verbose(1, "JIT success: %s@%s:%d",
|
||||||
|
RSTRING_PTR(ISEQ_BODY(iseq)->location.label),
|
||||||
|
RSTRING_PTR(rb_iseq_path(iseq)), ISEQ_BODY(iseq)->location.first_lineno);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
verbose(1, "JIT skip: A compiled method has been GCed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verbose(1, "JIT batch (%.1fms): Batched %d methods %s -> %s", end_time - current_cc_ms, unit->units.length, c_file, so_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
load_compact_funcs_from_so(struct rb_mjit_unit *unit, char *c_file, char *so_file)
|
load_compact_funcs_from_so(struct rb_mjit_unit *unit, char *c_file, char *so_file)
|
||||||
{
|
{
|
||||||
struct rb_mjit_unit *cur = 0;
|
|
||||||
double end_time = real_ms_time();
|
double end_time = real_ms_time();
|
||||||
|
|
||||||
void *handle = dlopen(so_file, RTLD_NOW);
|
void *handle = dlopen(so_file, RTLD_NOW);
|
||||||
|
@ -735,44 +809,33 @@ load_compact_funcs_from_so(struct rb_mjit_unit *unit, char *c_file, char *so_fil
|
||||||
}
|
}
|
||||||
unit->handle = handle;
|
unit->handle = handle;
|
||||||
|
|
||||||
// lazily dlclose handle (and .so file for win32) on `mjit_finish()`.
|
// lazily dlclose handle on `mjit_finish()`.
|
||||||
add_to_list(unit, &compact_units);
|
add_to_list(unit, &compact_units);
|
||||||
|
|
||||||
if (!mjit_opts.save_temps)
|
if (!mjit_opts.save_temps)
|
||||||
remove_so_file(so_file, unit);
|
remove_so_file(so_file, unit);
|
||||||
|
|
||||||
ccan_list_for_each(&active_units.head, cur, unode) {
|
struct rb_mjit_unit *batch_unit = 0, *child_unit = 0;
|
||||||
void *func;
|
ccan_list_for_each(&active_units.head, batch_unit, unode) {
|
||||||
char funcname[MAXPATHLEN];
|
ccan_list_for_each(&batch_unit->units.head, child_unit, unode) {
|
||||||
sprint_funcname(funcname, sizeof(funcname), cur);
|
if (child_unit->iseq == NULL) continue; // ISEQ is GCed
|
||||||
|
|
||||||
|
char funcname[MAXPATHLEN];
|
||||||
|
sprint_funcname(funcname, sizeof(funcname), child_unit);
|
||||||
|
|
||||||
|
void *func;
|
||||||
if ((func = dlsym(handle, funcname)) == NULL) {
|
if ((func = dlsym(handle, funcname)) == NULL) {
|
||||||
mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror());
|
mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cur->iseq) { // Check whether GCed or not
|
if (child_unit->iseq) { // Check whether GCed or not
|
||||||
// Usage of jit_code might be not in a critical section.
|
// Usage of jit_code might be not in a critical section.
|
||||||
MJIT_ATOMIC_SET(ISEQ_BODY(cur->iseq)->jit_func, (jit_func_t)func);
|
MJIT_ATOMIC_SET(ISEQ_BODY(child_unit->iseq)->jit_func, (jit_func_t)func);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verbose(1, "JIT compaction (%.1fms): Compacted %d methods %s -> %s", end_time - current_cc_ms, active_units.length, c_file, so_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
mjit_warning("failure in loading code from '%s': %s", so_file, dlerror());
|
|
||||||
return (void *)MJIT_FUNC_FAILED;
|
|
||||||
}
|
}
|
||||||
|
verbose(1, "JIT compaction (%.1fms): Compacted %d methods %s -> %s", end_time - current_cc_ms, active_units_length, c_file, so_file);
|
||||||
func = dlsym(handle, funcname);
|
|
||||||
unit->handle = handle;
|
|
||||||
return func;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef __clang__
|
#ifndef __clang__
|
||||||
|
@ -813,51 +876,6 @@ compile_prelude(FILE *f)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile ISeq in UNIT and return function pointer of JIT-ed code.
|
|
||||||
// It may return MJIT_FUNC_FAILED if something went wrong.
|
|
||||||
static bool
|
|
||||||
mjit_compile_unit(struct rb_mjit_unit *unit)
|
|
||||||
{
|
|
||||||
static const char c_ext[] = ".c";
|
|
||||||
static const char so_ext[] = DLEXT;
|
|
||||||
char c_file[MAXPATHLEN], so_file[MAXPATHLEN], funcname[MAXPATHLEN];
|
|
||||||
|
|
||||||
sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext);
|
|
||||||
sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
|
|
||||||
sprint_funcname(funcname, sizeof(funcname), unit);
|
|
||||||
|
|
||||||
FILE *f;
|
|
||||||
int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600);
|
|
||||||
if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
|
|
||||||
int e = errno;
|
|
||||||
if (fd >= 0) (void)close(fd);
|
|
||||||
verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// print #include of MJIT header, etc.
|
|
||||||
compile_prelude(f);
|
|
||||||
|
|
||||||
// To make MJIT worker thread-safe against GC.compact, copy ISeq values while `in_jit` is true.
|
|
||||||
int iseq_lineno = ISEQ_BODY(unit->iseq)->location.first_lineno;
|
|
||||||
char *iseq_label = alloca(RSTRING_LEN(ISEQ_BODY(unit->iseq)->location.label) + 1);
|
|
||||||
char *iseq_path = alloca(RSTRING_LEN(rb_iseq_path(unit->iseq)) + 1);
|
|
||||||
strcpy(iseq_label, RSTRING_PTR(ISEQ_BODY(unit->iseq)->location.label));
|
|
||||||
strcpy(iseq_path, RSTRING_PTR(rb_iseq_path(unit->iseq)));
|
|
||||||
|
|
||||||
verbose(2, "start compilation: %s@%s:%d -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
|
|
||||||
fprintf(f, "/* %s@%s:%d */\n\n", iseq_label, iseq_path, iseq_lineno);
|
|
||||||
bool success = mjit_compile(f, unit->iseq, funcname, unit->id);
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
if (!success) {
|
|
||||||
if (!mjit_opts.save_temps)
|
|
||||||
remove_file(c_file);
|
|
||||||
verbose(1, "JIT failure: %s@%s:%d -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static pid_t
|
static pid_t
|
||||||
start_c_compile_unit(struct rb_mjit_unit *unit)
|
start_c_compile_unit(struct rb_mjit_unit *unit)
|
||||||
{
|
{
|
||||||
|
@ -915,79 +933,6 @@ mjit_capture_cc_entries(const struct rb_iseq_constant_body *compiled_iseq, const
|
||||||
return cc_entries_index;
|
return cc_entries_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up field `used_code_p` for unit iseqs whose iseq on the stack of ec.
|
|
||||||
static void
|
|
||||||
mark_iseq_units(const rb_iseq_t *iseq, void *data)
|
|
||||||
{
|
|
||||||
if (ISEQ_BODY(iseq)->mjit_unit != NULL) {
|
|
||||||
ISEQ_BODY(iseq)->mjit_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)
|
|
||||||
{
|
|
||||||
struct rb_mjit_unit *unit = 0, *next;
|
|
||||||
int units_num = active_units.length;
|
|
||||||
|
|
||||||
// For now, we don't unload units when ISeq is GCed. We should
|
|
||||||
// unload such ISeqs first here.
|
|
||||||
ccan_list_for_each_safe(&active_units.head, unit, next, unode) {
|
|
||||||
if (unit->iseq == NULL) { // ISeq is GCed.
|
|
||||||
remove_from_list(unit, &active_units);
|
|
||||||
free_unit(unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect units which are in use and can't be unloaded.
|
|
||||||
ccan_list_for_each(&active_units.head, unit, unode) {
|
|
||||||
VM_ASSERT(unit->iseq != NULL && unit->handle != NULL);
|
|
||||||
unit->used_code_p = false;
|
|
||||||
}
|
|
||||||
// All threads have a root_fiber which has a mjit_cont. Other normal fibers also
|
|
||||||
// have a mjit_cont. Thus we can check ISeqs in use by scanning ec of mjit_conts.
|
|
||||||
rb_jit_cont_each_iseq(mark_iseq_units, NULL);
|
|
||||||
// TODO: check stale_units and unload unused ones! (note that the unit is not associated to ISeq anymore)
|
|
||||||
|
|
||||||
// Unload units whose total_calls is smaller than any total_calls in unit_queue.
|
|
||||||
// TODO: make the algorithm more efficient
|
|
||||||
long unsigned prev_queue_calls = -1;
|
|
||||||
while (true) {
|
|
||||||
// Calculate the next max total_calls in unit_queue
|
|
||||||
long unsigned max_queue_calls = 0;
|
|
||||||
ccan_list_for_each(&unit_queue.head, unit, unode) {
|
|
||||||
if (unit->iseq != NULL && max_queue_calls < ISEQ_BODY(unit->iseq)->total_calls
|
|
||||||
&& ISEQ_BODY(unit->iseq)->total_calls < prev_queue_calls) {
|
|
||||||
max_queue_calls = ISEQ_BODY(unit->iseq)->total_calls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev_queue_calls = max_queue_calls;
|
|
||||||
|
|
||||||
bool unloaded_p = false;
|
|
||||||
ccan_list_for_each_safe(&active_units.head, unit, next, unode) {
|
|
||||||
if (unit->used_code_p) // We can't unload code on stack.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (max_queue_calls > ISEQ_BODY(unit->iseq)->total_calls) {
|
|
||||||
verbose(2, "Unloading unit %d (calls=%lu, threshold=%lu)",
|
|
||||||
unit->id, ISEQ_BODY(unit->iseq)->total_calls, max_queue_calls);
|
|
||||||
VM_ASSERT(unit->handle != NULL);
|
|
||||||
remove_from_list(unit, &active_units);
|
|
||||||
free_unit(unit);
|
|
||||||
unloaded_p = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!unloaded_p) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (units_num > active_units.length) {
|
|
||||||
verbose(1, "Too many JIT code -- %d units unloaded", units_num - active_units.length);
|
|
||||||
total_unloads += units_num - active_units.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info);
|
static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info);
|
||||||
|
|
||||||
// Return an unique file name in /tmp with PREFIX and SUFFIX and
|
// Return an unique file name in /tmp with PREFIX and SUFFIX and
|
||||||
|
@ -1110,6 +1055,9 @@ create_unit(enum rb_mjit_unit_type type)
|
||||||
struct rb_mjit_unit *unit = ZALLOC_N(struct rb_mjit_unit, 1);
|
struct rb_mjit_unit *unit = ZALLOC_N(struct rb_mjit_unit, 1);
|
||||||
unit->id = current_unit_num++;
|
unit->id = current_unit_num++;
|
||||||
unit->type = type;
|
unit->type = type;
|
||||||
|
if (type == MJIT_UNIT_BATCH) {
|
||||||
|
ccan_list_head_init(&unit->units.head);
|
||||||
|
}
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1132,28 +1080,26 @@ check_unit_queue(void)
|
||||||
if (worker_stopped) return;
|
if (worker_stopped) return;
|
||||||
if (current_cc_pid != 0) return; // still compiling
|
if (current_cc_pid != 0) return; // still compiling
|
||||||
|
|
||||||
// Run unload_units after it's requested `max_cache_size / 10` (default: 10) times.
|
// TODO: resurrect unload_units
|
||||||
// This throttles the call to mitigate locking in unload_units. It also throttles JIT compaction.
|
if (active_units_length >= mjit_opts.max_cache_size) return; // wait until unload_units makes a progress
|
||||||
int throttle_threshold = mjit_opts.max_cache_size / 10;
|
|
||||||
if (unload_requests >= throttle_threshold) {
|
|
||||||
unload_units();
|
|
||||||
unload_requests = 0;
|
|
||||||
if (active_units.length == mjit_opts.max_cache_size && mjit_opts.wait) { // Sometimes all methods may be in use
|
|
||||||
mjit_opts.max_cache_size++; // avoid infinite loop on `mjit_wait`. Note that --jit-wait is just for testing.
|
|
||||||
verbose(1, "No units can be unloaded -- incremented max-cache-size to %d for --jit-wait", mjit_opts.max_cache_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (active_units.length >= mjit_opts.max_cache_size) return; // wait until unload_units makes a progress
|
|
||||||
|
|
||||||
// Dequeue a unit
|
// No ISEQ in unit_queue has enough calls to trigger JIT
|
||||||
struct rb_mjit_unit *unit = get_from_list(&unit_queue);
|
if (!mjit_compile_p) return;
|
||||||
if (unit == NULL) return;
|
mjit_compile_p = false;
|
||||||
VM_ASSERT(unit->type == MJIT_UNIT_ISEQ);
|
|
||||||
|
// Compile all ISEQs in unit_queue together
|
||||||
|
struct rb_mjit_unit *unit = create_unit(MJIT_UNIT_BATCH);
|
||||||
|
struct rb_mjit_unit *child_unit = NULL;
|
||||||
|
VM_ASSERT(unit_queue.length > 0);
|
||||||
|
while ((child_unit = get_from_list(&unit_queue)) != NULL && (active_units_length + unit->units.length) < mjit_opts.max_cache_size) {
|
||||||
|
add_to_list(child_unit, &unit->units);
|
||||||
|
ISEQ_BODY(child_unit->iseq)->jit_func = (jit_func_t)MJIT_FUNC_COMPILING;
|
||||||
|
}
|
||||||
|
|
||||||
// Run the MJIT compiler synchronously
|
// Run the MJIT compiler synchronously
|
||||||
current_cc_ms = real_ms_time();
|
current_cc_ms = real_ms_time();
|
||||||
current_cc_unit = unit;
|
current_cc_unit = unit;
|
||||||
bool success = mjit_compile_unit(unit);
|
bool success = mjit_batch(unit);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
mjit_notify_waitpid(1);
|
mjit_notify_waitpid(1);
|
||||||
return;
|
return;
|
||||||
|
@ -1181,19 +1127,13 @@ check_compaction(void)
|
||||||
int max_compact_size = mjit_opts.max_cache_size / 100;
|
int max_compact_size = mjit_opts.max_cache_size / 100;
|
||||||
if (max_compact_size < 10) max_compact_size = 10;
|
if (max_compact_size < 10) max_compact_size = 10;
|
||||||
|
|
||||||
// Run unload_units after it's requested `max_cache_size / 10` (default: 10) times.
|
if (active_units_length == mjit_opts.max_cache_size) {
|
||||||
// This throttles the call to mitigate locking in unload_units. It also throttles JIT compaction.
|
|
||||||
int throttle_threshold = mjit_opts.max_cache_size / 10;
|
|
||||||
|
|
||||||
if (compact_units.length < max_compact_size
|
|
||||||
&& ((!mjit_opts.wait && unit_queue.length == 0 && active_units.length > 1)
|
|
||||||
|| (active_units.length == mjit_opts.max_cache_size && compact_units.length * throttle_threshold <= total_unloads))) { // throttle compaction by total_unloads
|
|
||||||
struct rb_mjit_unit *unit = create_unit(MJIT_UNIT_COMPACT);
|
struct rb_mjit_unit *unit = create_unit(MJIT_UNIT_COMPACT);
|
||||||
|
|
||||||
// Run the MJIT compiler synchronously
|
// Run the MJIT compiler synchronously
|
||||||
current_cc_ms = real_ms_time();
|
current_cc_ms = real_ms_time();
|
||||||
current_cc_unit = unit;
|
current_cc_unit = unit;
|
||||||
bool success = mjit_compact_unit(unit);
|
bool success = mjit_compact(unit);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
mjit_notify_waitpid(1);
|
mjit_notify_waitpid(1);
|
||||||
return;
|
return;
|
||||||
|
@ -1227,9 +1167,8 @@ mjit_notify_waitpid(int exit_code)
|
||||||
// Check the result
|
// Check the result
|
||||||
if (exit_code != 0) {
|
if (exit_code != 0) {
|
||||||
verbose(2, "Failed to generate so");
|
verbose(2, "Failed to generate so");
|
||||||
if (current_cc_unit->type == MJIT_UNIT_ISEQ) {
|
// TODO: set MJIT_FUNC_FAILED to unit->units
|
||||||
current_cc_unit->iseq->body->jit_func = (jit_func_t)MJIT_FUNC_FAILED;
|
// TODO: free list of unit->units
|
||||||
}
|
|
||||||
free_unit(current_cc_unit);
|
free_unit(current_cc_unit);
|
||||||
current_cc_unit = NULL;
|
current_cc_unit = NULL;
|
||||||
return;
|
return;
|
||||||
|
@ -1238,39 +1177,22 @@ mjit_notify_waitpid(int exit_code)
|
||||||
// Load .so file
|
// Load .so file
|
||||||
char so_file[MAXPATHLEN];
|
char so_file[MAXPATHLEN];
|
||||||
sprint_uniq_filename(so_file, (int)sizeof(so_file), current_cc_unit->id, MJIT_TMP_PREFIX, DLEXT);
|
sprint_uniq_filename(so_file, (int)sizeof(so_file), current_cc_unit->id, MJIT_TMP_PREFIX, DLEXT);
|
||||||
if (current_cc_unit->type == MJIT_UNIT_COMPACT) {
|
switch (current_cc_unit->type) {
|
||||||
load_compact_funcs_from_so(current_cc_unit, c_file, so_file);
|
case MJIT_UNIT_ISEQ:
|
||||||
current_cc_unit = NULL;
|
rb_bug("unreachable: current_cc_unit->type must not be MJIT_UNIT_ISEQ");
|
||||||
}
|
case MJIT_UNIT_BATCH:
|
||||||
else { // MJIT_UNIT_ISEQ
|
load_batch_funcs_from_so(current_cc_unit, c_file, so_file);
|
||||||
// Load the function from so
|
|
||||||
char funcname[MAXPATHLEN];
|
|
||||||
sprint_funcname(funcname, sizeof(funcname), current_cc_unit);
|
|
||||||
void *func = load_func_from_so(so_file, funcname, current_cc_unit);
|
|
||||||
|
|
||||||
// Delete .so file
|
|
||||||
if (!mjit_opts.save_temps)
|
|
||||||
remove_file(so_file);
|
|
||||||
|
|
||||||
// Set the jit_func if successful
|
|
||||||
if (current_cc_unit->iseq != NULL) { // mjit_free_iseq could nullify this
|
|
||||||
rb_iseq_t *iseq = current_cc_unit->iseq;
|
|
||||||
if (!MJIT_FUNC_STATE_P(func)) {
|
|
||||||
double end_time = real_ms_time();
|
|
||||||
verbose(1, "JIT success (%.1fms): %s@%s:%d -> %s",
|
|
||||||
end_time - current_cc_ms, RSTRING_PTR(ISEQ_BODY(iseq)->location.label),
|
|
||||||
RSTRING_PTR(rb_iseq_path(iseq)), ISEQ_BODY(iseq)->location.first_lineno, c_file);
|
|
||||||
|
|
||||||
add_to_list(current_cc_unit, &active_units);
|
|
||||||
}
|
|
||||||
MJIT_ATOMIC_SET(ISEQ_BODY(iseq)->jit_func, func);
|
|
||||||
} // TODO: free unit on else?
|
|
||||||
current_cc_unit = NULL;
|
current_cc_unit = NULL;
|
||||||
|
|
||||||
// Run compaction if it should
|
// Run compaction if it should
|
||||||
if (!stop_worker_p) {
|
if (!stop_worker_p) {
|
||||||
check_compaction();
|
check_compaction();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case MJIT_UNIT_COMPACT:
|
||||||
|
load_compact_funcs_from_so(current_cc_unit, c_file, so_file);
|
||||||
|
current_cc_unit = NULL;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip further compilation if mjit_finish is trying to stop it
|
// Skip further compilation if mjit_finish is trying to stop it
|
||||||
|
@ -1328,13 +1250,29 @@ mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_inf
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ISEQ_BODY(iseq)->jit_func = (jit_func_t)MJIT_FUNC_COMPILING;
|
// For batching multiple ISEQs, we only enqueue ISEQs when total_calls reaches call_threshold,
|
||||||
create_iseq_unit(iseq);
|
// and compile all enqueued ISEQs when any ISEQ reaches call_threshold * 2.
|
||||||
if (compile_info != NULL)
|
bool recompile_p = !MJIT_FUNC_STATE_P(ISEQ_BODY(iseq)->jit_func);
|
||||||
ISEQ_BODY(iseq)->mjit_unit->compile_info = *compile_info;
|
if (!ISEQ_BODY(iseq)->mjit_unit || recompile_p) { // call_threshold, or recompile
|
||||||
add_to_list(ISEQ_BODY(iseq)->mjit_unit, &unit_queue);
|
// Discard an old unit with recompile_p
|
||||||
if (active_units.length >= mjit_opts.max_cache_size) {
|
if (recompile_p) {
|
||||||
unload_requests++;
|
ISEQ_BODY(iseq)->mjit_unit->iseq = NULL; // Ignore this from compaction
|
||||||
|
ISEQ_BODY(iseq)->jit_func = (jit_func_t)MJIT_FUNC_NOT_COMPILED;
|
||||||
|
active_units_length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new unit and enqueue it
|
||||||
|
struct rb_mjit_unit *unit = create_iseq_unit(iseq);
|
||||||
|
if (recompile_p) {
|
||||||
|
VM_ASSERT(compile_info != NULL);
|
||||||
|
unit->compile_info = *compile_info;
|
||||||
|
}
|
||||||
|
add_to_list(unit, &unit_queue);
|
||||||
|
ISEQ_BODY(iseq)->total_calls = 0; // come here again :)
|
||||||
|
}
|
||||||
|
else { // call_threshold * 2
|
||||||
|
VM_ASSERT(compile_info == NULL);
|
||||||
|
mjit_compile_p = true; // compile all ISEQs in unit_queue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1715,6 +1653,11 @@ mjit_init(const struct mjit_options *opts)
|
||||||
// Normalize options
|
// Normalize options
|
||||||
if (mjit_opts.call_threshold == 0)
|
if (mjit_opts.call_threshold == 0)
|
||||||
mjit_opts.call_threshold = DEFAULT_CALL_THRESHOLD;
|
mjit_opts.call_threshold = DEFAULT_CALL_THRESHOLD;
|
||||||
|
if (mjit_opts.call_threshold % 2 == 1) {
|
||||||
|
mjit_opts.call_threshold += 1;
|
||||||
|
mjit_warning("--mjit-call-threshold must be an even number. Using %d instead.", mjit_opts.call_threshold);
|
||||||
|
}
|
||||||
|
mjit_opts.call_threshold /= 2; // Half for enqueue, half for trigger
|
||||||
if (mjit_opts.max_cache_size <= 0)
|
if (mjit_opts.max_cache_size <= 0)
|
||||||
mjit_opts.max_cache_size = DEFAULT_MAX_CACHE_SIZE;
|
mjit_opts.max_cache_size = DEFAULT_MAX_CACHE_SIZE;
|
||||||
if (mjit_opts.max_cache_size < MIN_CACHE_SIZE)
|
if (mjit_opts.max_cache_size < MIN_CACHE_SIZE)
|
||||||
|
|
24
mjit_c.h
24
mjit_c.h
|
@ -14,25 +14,31 @@
|
||||||
#define NOT_COMPILED_STACK_SIZE -1
|
#define NOT_COMPILED_STACK_SIZE -1
|
||||||
#define ALREADY_COMPILED_P(status, pos) (status->stack_size_for_pos[pos] != NOT_COMPILED_STACK_SIZE)
|
#define ALREADY_COMPILED_P(status, pos) (status->stack_size_for_pos[pos] != NOT_COMPILED_STACK_SIZE)
|
||||||
|
|
||||||
// Type of rb_mjit_unit
|
// Linked list of struct rb_mjit_unit.
|
||||||
|
struct rb_mjit_unit_list {
|
||||||
|
struct ccan_list_head head;
|
||||||
|
int length; // the list length
|
||||||
|
};
|
||||||
|
|
||||||
enum rb_mjit_unit_type {
|
enum rb_mjit_unit_type {
|
||||||
// Single-ISEQ unit for mjit_compile
|
// Single-ISEQ unit for unit_queue
|
||||||
MJIT_UNIT_ISEQ = 0,
|
MJIT_UNIT_ISEQ = 0,
|
||||||
|
// Multi-ISEQ unit for mjit_batch
|
||||||
|
MJIT_UNIT_BATCH = 1,
|
||||||
// All-ISEQ unit for mjit_compact
|
// All-ISEQ unit for mjit_compact
|
||||||
MJIT_UNIT_COMPACT = 1,
|
MJIT_UNIT_COMPACT = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The unit structure that holds metadata of ISeq for MJIT.
|
// The unit structure that holds metadata of ISeq for MJIT.
|
||||||
// TODO: Use different structs for ISEQ and COMPACT
|
// TODO: Use different structs for ISEQ and BATCH/COMPACT
|
||||||
struct rb_mjit_unit {
|
struct rb_mjit_unit {
|
||||||
struct ccan_list_node unode;
|
struct ccan_list_node unode;
|
||||||
// Unique order number of unit.
|
// Unique order number of unit.
|
||||||
int id;
|
int id;
|
||||||
// Dlopen handle of the loaded object file.
|
|
||||||
void *handle;
|
|
||||||
// Type of this unit
|
// Type of this unit
|
||||||
enum rb_mjit_unit_type type;
|
enum rb_mjit_unit_type type;
|
||||||
|
|
||||||
|
/* MJIT_UNIT_ISEQ */
|
||||||
// ISEQ for a non-batch unit
|
// ISEQ for a non-batch unit
|
||||||
rb_iseq_t *iseq;
|
rb_iseq_t *iseq;
|
||||||
// Only used by unload_units. Flag to check this unit is currently on stack or not.
|
// Only used by unload_units. Flag to check this unit is currently on stack or not.
|
||||||
|
@ -43,6 +49,12 @@ struct rb_mjit_unit {
|
||||||
const struct rb_callcache **cc_entries;
|
const struct rb_callcache **cc_entries;
|
||||||
// ISEQ_BODY(iseq)->ci_size + ones of inlined iseqs
|
// ISEQ_BODY(iseq)->ci_size + ones of inlined iseqs
|
||||||
unsigned int cc_entries_size;
|
unsigned int cc_entries_size;
|
||||||
|
|
||||||
|
/* MJIT_UNIT_BATCH, MJIT_UNIT_COMPACT */
|
||||||
|
// Dlopen handle of the loaded object file.
|
||||||
|
void *handle;
|
||||||
|
// Units compacted by this batch
|
||||||
|
struct rb_mjit_unit_list units; // MJIT_UNIT_BATCH only
|
||||||
};
|
};
|
||||||
|
|
||||||
// Storage to keep data which is consistent in each conditional branch.
|
// Storage to keep data which is consistent in each conditional branch.
|
||||||
|
|
|
@ -620,13 +620,14 @@ module RubyVM::MJIT
|
||||||
"rb_mjit_unit", Primitive.cexpr!("SIZEOF(struct rb_mjit_unit)"),
|
"rb_mjit_unit", Primitive.cexpr!("SIZEOF(struct rb_mjit_unit)"),
|
||||||
unode: [self.ccan_list_node, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), unode)")],
|
unode: [self.ccan_list_node, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), unode)")],
|
||||||
id: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), id)")],
|
id: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), id)")],
|
||||||
handle: [CType::Pointer.new { CType::Immediate.parse("void") }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), handle)")],
|
|
||||||
type: [self.rb_mjit_unit_type, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), type)")],
|
type: [self.rb_mjit_unit_type, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), type)")],
|
||||||
iseq: [CType::Pointer.new { self.rb_iseq_t }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), iseq)")],
|
iseq: [CType::Pointer.new { self.rb_iseq_t }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), iseq)")],
|
||||||
used_code_p: [self._Bool, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), used_code_p)")],
|
used_code_p: [self._Bool, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), used_code_p)")],
|
||||||
compile_info: [self.rb_mjit_compile_info, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), compile_info)")],
|
compile_info: [self.rb_mjit_compile_info, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), compile_info)")],
|
||||||
cc_entries: [CType::Pointer.new { CType::Pointer.new { self.rb_callcache } }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), cc_entries)")],
|
cc_entries: [CType::Pointer.new { CType::Pointer.new { self.rb_callcache } }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), cc_entries)")],
|
||||||
cc_entries_size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), cc_entries_size)")],
|
cc_entries_size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), cc_entries_size)")],
|
||||||
|
handle: [CType::Pointer.new { CType::Immediate.parse("void") }, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), handle)")],
|
||||||
|
units: [self.rb_mjit_unit_list, Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_unit *)NULL)), units)")],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -787,5 +788,9 @@ module RubyVM::MJIT
|
||||||
CType::Stub.new(:rb_mjit_unit_type)
|
CType::Stub.new(:rb_mjit_unit_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def C.rb_mjit_unit_list
|
||||||
|
CType::Stub.new(:rb_mjit_unit_list)
|
||||||
|
end
|
||||||
|
|
||||||
### MJIT bindgen end ###
|
### MJIT bindgen end ###
|
||||||
end if RubyVM::MJIT.enabled? && RubyVM::MJIT.const_defined?(:C) # not defined for miniruby
|
end if RubyVM::MJIT.enabled? && RubyVM::MJIT.const_defined?(:C) # not defined for miniruby
|
||||||
|
|
|
@ -2,7 +2,7 @@ require 'rbconfig'
|
||||||
|
|
||||||
module JITSupport
|
module JITSupport
|
||||||
JIT_TIMEOUT = 600 # 10min for each...
|
JIT_TIMEOUT = 600 # 10min for each...
|
||||||
JIT_SUCCESS_PREFIX = 'JIT success \(\d+\.\dms\)'
|
JIT_SUCCESS_PREFIX = 'JIT success'
|
||||||
JIT_RECOMPILE_PREFIX = 'JIT recompile'
|
JIT_RECOMPILE_PREFIX = 'JIT recompile'
|
||||||
JIT_COMPACTION_PREFIX = 'JIT compaction \(\d+\.\dms\)'
|
JIT_COMPACTION_PREFIX = 'JIT compaction \(\d+\.\dms\)'
|
||||||
UNSUPPORTED_COMPILERS = [
|
UNSUPPORTED_COMPILERS = [
|
||||||
|
|
|
@ -11,6 +11,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
/\AJIT recompile: .+\n\z/,
|
/\AJIT recompile: .+\n\z/,
|
||||||
/\AJIT inline: .+\n\z/,
|
/\AJIT inline: .+\n\z/,
|
||||||
/\AJIT cancel: .+\n\z/,
|
/\AJIT cancel: .+\n\z/,
|
||||||
|
/\AJIT batch \([^)]+ms\): .+\n\z/,
|
||||||
/\ASuccessful MJIT finish\n\z/,
|
/\ASuccessful MJIT finish\n\z/,
|
||||||
]
|
]
|
||||||
MAX_CACHE_PATTERNS = [
|
MAX_CACHE_PATTERNS = [
|
||||||
|
@ -67,11 +68,11 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_nop
|
def test_compile_insn_nop
|
||||||
assert_compile_once('nil rescue true', result_inspect: 'nil', insns: %i[nop])
|
assert_compile_twice('nil rescue true', result_inspect: 'nil', insns: %i[nop])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_local
|
def test_compile_insn_local
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0])
|
||||||
begin;
|
begin;
|
||||||
foo = 1
|
foo = 1
|
||||||
foo
|
foo
|
||||||
|
@ -96,7 +97,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_blockparam
|
def test_compile_insn_blockparam
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2, insns: %i[getblockparam setblockparam])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, insns: %i[getblockparam setblockparam])
|
||||||
begin;
|
begin;
|
||||||
def foo(&b)
|
def foo(&b)
|
||||||
a = b
|
a = b
|
||||||
|
@ -105,6 +106,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
print foo { 1 }
|
print foo { 1 }
|
||||||
|
print foo { 1 }
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,25 +126,25 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_getspecial
|
def test_compile_insn_getspecial
|
||||||
assert_compile_once('$1', result_inspect: 'nil', insns: %i[getspecial])
|
assert_compile_twice('$1', result_inspect: 'nil', insns: %i[getspecial])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_setspecial
|
def test_compile_insn_setspecial
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial])
|
||||||
begin;
|
begin;
|
||||||
true if nil.nil?..nil.nil?
|
true if nil.nil?..nil.nil?
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_instancevariable
|
def test_compile_insn_instancevariable
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable])
|
||||||
begin;
|
begin;
|
||||||
@foo = 1
|
@foo = 1
|
||||||
@foo
|
@foo
|
||||||
end;
|
end;
|
||||||
|
|
||||||
# optimized getinstancevariable call
|
# optimized getinstancevariable call
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 1, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class A
|
class A
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -162,7 +164,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_classvariable
|
def test_compile_insn_classvariable
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 1, insns: %i[getclassvariable setclassvariable])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, insns: %i[getclassvariable setclassvariable])
|
||||||
begin;
|
begin;
|
||||||
class Foo
|
class Foo
|
||||||
def self.foo
|
def self.foo
|
||||||
|
@ -172,19 +174,22 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
print Foo.foo
|
print Foo.foo
|
||||||
|
print Foo.foo
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_constant
|
def test_compile_insn_constant
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_getconstant_path setconstant])
|
EnvUtil.suppress_warning do
|
||||||
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_getconstant_path setconstant])
|
||||||
begin;
|
begin;
|
||||||
FOO = 1
|
FOO = 1
|
||||||
FOO
|
FOO
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_compile_insn_global
|
def test_compile_insn_global
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal])
|
||||||
begin;
|
begin;
|
||||||
$foo = 1
|
$foo = 1
|
||||||
$foo
|
$foo
|
||||||
|
@ -192,26 +197,26 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_putnil
|
def test_compile_insn_putnil
|
||||||
assert_compile_once('nil', result_inspect: 'nil', insns: %i[putnil])
|
assert_compile_twice('nil', result_inspect: 'nil', insns: %i[putnil])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_putself
|
def test_compile_insn_putself
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1, insns: %i[putself])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1, insns: %i[putself])
|
||||||
begin;
|
begin;
|
||||||
proc { print "hello" }.call
|
2.times { print "hello" }
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_putobject
|
def test_compile_insn_putobject
|
||||||
assert_compile_once('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_])
|
assert_compile_twice('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_])
|
||||||
assert_compile_once('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_])
|
assert_compile_twice('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_])
|
||||||
assert_compile_once('2', result_inspect: '2', insns: %i[putobject])
|
assert_compile_twice('2', result_inspect: '2', insns: %i[putobject])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_definemethod_definesmethod
|
def test_compile_insn_definemethod_definesmethod
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworld', success_count: 3, insns: %i[definemethod definesmethod])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworldhelloworld', success_count: 3, insns: %i[definemethod definesmethod])
|
||||||
begin;
|
begin;
|
||||||
print 1.times.map {
|
print 2.times.map {
|
||||||
def method_definition
|
def method_definition
|
||||||
'hello'
|
'hello'
|
||||||
end
|
end
|
||||||
|
@ -226,9 +231,9 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_putspecialobject
|
def test_compile_insn_putspecialobject
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'a', success_count: 2, insns: %i[putspecialobject])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'aa', success_count: 2, insns: %i[putspecialobject])
|
||||||
begin;
|
begin;
|
||||||
print 1.times.map {
|
print 2.times.map {
|
||||||
def a
|
def a
|
||||||
'a'
|
'a'
|
||||||
end
|
end
|
||||||
|
@ -241,15 +246,15 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_putstring_concatstrings_objtostring
|
def test_compile_insn_putstring_concatstrings_objtostring
|
||||||
assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring])
|
assert_compile_twice('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_toregexp
|
def test_compile_insn_toregexp
|
||||||
assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp])
|
assert_compile_twice('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_newarray
|
def test_compile_insn_newarray
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray])
|
||||||
begin;
|
begin;
|
||||||
a, b, c = 1, 2, 3
|
a, b, c = 1, 2, 3
|
||||||
[a, b, c]
|
[a, b, c]
|
||||||
|
@ -257,39 +262,39 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_newarraykwsplat
|
def test_compile_insn_newarraykwsplat
|
||||||
assert_compile_once('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat])
|
assert_compile_twice('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_intern_duparray
|
def test_compile_insn_intern_duparray
|
||||||
assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray])
|
assert_compile_twice('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_expandarray
|
def test_compile_insn_expandarray
|
||||||
assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray])
|
assert_compile_twice('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_concatarray
|
def test_compile_insn_concatarray
|
||||||
assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray])
|
assert_compile_twice('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_splatarray
|
def test_compile_insn_splatarray
|
||||||
assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray])
|
assert_compile_twice('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_newhash
|
def test_compile_insn_newhash
|
||||||
assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash])
|
assert_compile_twice('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_duphash
|
def test_compile_insn_duphash
|
||||||
assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash])
|
assert_compile_twice('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_newrange
|
def test_compile_insn_newrange
|
||||||
assert_compile_once('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange])
|
assert_compile_twice('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_pop
|
def test_compile_insn_pop
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop])
|
||||||
begin;
|
begin;
|
||||||
a = false
|
a = false
|
||||||
b = 1
|
b = 1
|
||||||
|
@ -298,7 +303,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_dup
|
def test_compile_insn_dup
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup])
|
||||||
begin;
|
begin;
|
||||||
a = 1
|
a = 1
|
||||||
a&.+(2)
|
a&.+(2)
|
||||||
|
@ -306,7 +311,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_dupn
|
def test_compile_insn_dupn
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn])
|
||||||
begin;
|
begin;
|
||||||
klass = Class.new
|
klass = Class.new
|
||||||
klass::X ||= true
|
klass::X ||= true
|
||||||
|
@ -314,7 +319,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_swap_topn
|
def test_compile_insn_swap_topn
|
||||||
assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn])
|
assert_compile_twice('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_reput
|
def test_compile_insn_reput
|
||||||
|
@ -322,11 +327,11 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_setn
|
def test_compile_insn_setn
|
||||||
assert_compile_once('[nil][0] = 1', result_inspect: '1', insns: %i[setn])
|
assert_compile_twice('[nil][0] = 1', result_inspect: '1', insns: %i[setn])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_adjuststack
|
def test_compile_insn_adjuststack
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack])
|
||||||
begin;
|
begin;
|
||||||
x = [true]
|
x = [true]
|
||||||
x[0] ||= nil
|
x[0] ||= nil
|
||||||
|
@ -335,16 +340,17 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_defined
|
def test_compile_insn_defined
|
||||||
assert_compile_once('defined?(a)', result_inspect: 'nil', insns: %i[defined])
|
assert_compile_twice('defined?(a)', result_inspect: 'nil', insns: %i[defined])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_checkkeyword
|
def test_compile_insn_checkkeyword
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1, insns: %i[checkkeyword])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'truetrue', success_count: 1, insns: %i[checkkeyword])
|
||||||
begin;
|
begin;
|
||||||
def test(x: rand)
|
def test(x: rand)
|
||||||
x
|
x
|
||||||
end
|
end
|
||||||
print test(x: true)
|
print test(x: true)
|
||||||
|
print test(x: true)
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -357,35 +363,36 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_send
|
def test_compile_insn_send
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 3, insns: %i[send])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4, insns: %i[send])
|
||||||
begin;
|
begin;
|
||||||
print proc { yield_self { 1 } }.call
|
print proc { yield_self { 1 } }.call
|
||||||
|
print proc { yield_self { 1 } }.call
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_str_freeze
|
def test_compile_insn_opt_str_freeze
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze])
|
||||||
begin;
|
begin;
|
||||||
'foo'.freeze
|
'foo'.freeze
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_nil_p
|
def test_compile_insn_opt_nil_p
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p])
|
||||||
begin;
|
begin;
|
||||||
nil.nil?.nil?
|
nil.nil?.nil?
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_str_uminus
|
def test_compile_insn_opt_str_uminus
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus])
|
||||||
begin;
|
begin;
|
||||||
-'bar'
|
-'bar'
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_newarray_max
|
def test_compile_insn_opt_newarray_max
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max])
|
||||||
begin;
|
begin;
|
||||||
a = 1
|
a = 1
|
||||||
b = 2
|
b = 2
|
||||||
|
@ -394,7 +401,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_newarray_min
|
def test_compile_insn_opt_newarray_min
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min])
|
||||||
begin;
|
begin;
|
||||||
a = 1
|
a = 1
|
||||||
b = 2
|
b = 2
|
||||||
|
@ -403,11 +410,11 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_send_without_block
|
def test_compile_insn_opt_send_without_block
|
||||||
assert_compile_once('print', result_inspect: 'nil', insns: %i[opt_send_without_block])
|
assert_compile_twice('print', result_inspect: 'nil', insns: %i[opt_send_without_block])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_invokesuper
|
def test_compile_insn_invokesuper
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4, insns: %i[invokesuper])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 4, insns: %i[invokesuper])
|
||||||
begin;
|
begin;
|
||||||
mod = Module.new {
|
mod = Module.new {
|
||||||
def test
|
def test
|
||||||
|
@ -421,21 +428,23 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
print klass.new.test
|
print klass.new.test
|
||||||
|
print klass.new.test
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_invokeblock_leave
|
def test_compile_insn_invokeblock_leave
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2, insns: %i[invokeblock leave])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '22', success_count: 2, insns: %i[invokeblock leave])
|
||||||
begin;
|
begin;
|
||||||
def foo
|
def foo
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
print foo { 2 }
|
print foo { 2 }
|
||||||
|
print foo { 2 }
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_throw
|
def test_compile_insn_throw
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2, insns: %i[throw])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '44', success_count: 2, insns: %i[throw])
|
||||||
begin;
|
begin;
|
||||||
def test
|
def test
|
||||||
proc do
|
proc do
|
||||||
|
@ -448,11 +457,12 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end.call
|
end.call
|
||||||
end
|
end
|
||||||
print test
|
print test
|
||||||
|
print test
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_jump_branchif
|
def test_compile_insn_jump_branchif
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif])
|
||||||
begin;
|
begin;
|
||||||
a = false
|
a = false
|
||||||
1 + 1 while a
|
1 + 1 while a
|
||||||
|
@ -460,7 +470,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_branchunless
|
def test_compile_insn_branchunless
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless])
|
||||||
begin;
|
begin;
|
||||||
a = true
|
a = true
|
||||||
if a
|
if a
|
||||||
|
@ -472,7 +482,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_branchnil
|
def test_compile_insn_branchnil
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil])
|
||||||
begin;
|
begin;
|
||||||
a = 2
|
a = 2
|
||||||
a&.+(1)
|
a&.+(1)
|
||||||
|
@ -480,7 +490,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_objtostring
|
def test_compile_insn_objtostring
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring])
|
||||||
begin;
|
begin;
|
||||||
a = '2'
|
a = '2'
|
||||||
"4#{a}"
|
"4#{a}"
|
||||||
|
@ -488,15 +498,15 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_getconstant_path
|
def test_compile_insn_getconstant_path
|
||||||
assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getconstant_path])
|
assert_compile_twice('Struct', result_inspect: 'Struct', insns: %i[opt_getconstant_path])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_once
|
def test_compile_insn_once
|
||||||
assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once])
|
assert_compile_twice('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_checkmatch_opt_case_dispatch
|
def test_compile_insn_checkmatch_opt_case_dispatch
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
|
||||||
begin;
|
begin;
|
||||||
case 'hello'
|
case 'hello'
|
||||||
when 'hello'
|
when 'hello'
|
||||||
|
@ -506,34 +516,34 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_calc
|
def test_compile_insn_opt_calc
|
||||||
assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
|
assert_compile_twice('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
|
||||||
assert_compile_once('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
|
assert_compile_twice('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
|
||||||
assert_compile_once('4 + 2', result_inspect: '6')
|
assert_compile_twice('4 + 2', result_inspect: '6')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_cmp
|
def test_compile_insn_opt_cmp
|
||||||
assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq])
|
assert_compile_twice('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_rel
|
def test_compile_insn_opt_rel
|
||||||
assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge])
|
assert_compile_twice('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_ltlt
|
def test_compile_insn_opt_ltlt
|
||||||
assert_compile_once('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt])
|
assert_compile_twice('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_and
|
def test_compile_insn_opt_and
|
||||||
assert_compile_once('1 & 3', result_inspect: '1', insns: %i[opt_and])
|
assert_compile_twice('1 & 3', result_inspect: '1', insns: %i[opt_and])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_or
|
def test_compile_insn_opt_or
|
||||||
assert_compile_once('1 | 3', result_inspect: '3', insns: %i[opt_or])
|
assert_compile_twice('1 | 3', result_inspect: '3', insns: %i[opt_or])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_aref
|
def test_compile_insn_opt_aref
|
||||||
# optimized call (optimized JIT) -> send call
|
# optimized call (optimized JIT) -> send call
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, call_threshold: 1, insns: %i[opt_aref])
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '221', success_count: 1, call_threshold: 2, insns: %i[opt_aref])
|
||||||
begin;
|
begin;
|
||||||
obj = Object.new
|
obj = Object.new
|
||||||
def obj.[](h)
|
def obj.[](h)
|
||||||
|
@ -542,11 +552,12 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
|
|
||||||
block = proc { |h| h[1] }
|
block = proc { |h| h[1] }
|
||||||
print block.call({ 1 => 2 })
|
print block.call({ 1 => 2 })
|
||||||
|
print block.call({ 1 => 2 })
|
||||||
print block.call(obj)
|
print block.call(obj)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
# send call -> optimized call (send JIT) -> optimized call
|
# send call -> optimized call (send JIT) -> optimized call
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 2, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 3, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
obj = Object.new
|
obj = Object.new
|
||||||
def obj.[](h)
|
def obj.[](h)
|
||||||
|
@ -561,11 +572,11 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_aref_with
|
def test_compile_insn_opt_aref_with
|
||||||
assert_compile_once("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with])
|
assert_compile_twice("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_aset
|
def test_compile_insn_opt_aset
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with])
|
||||||
begin;
|
begin;
|
||||||
hash = { '1' => 2 }
|
hash = { '1' => 2 }
|
||||||
(hash['2'] = 2) + (hash[1.to_s] = 3)
|
(hash['2'] = 2) + (hash[1.to_s] = 3)
|
||||||
|
@ -573,7 +584,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_length_size
|
def test_compile_insn_opt_length_size
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size])
|
||||||
begin;
|
begin;
|
||||||
array = [1, 2]
|
array = [1, 2]
|
||||||
array.length + array.size
|
array.length + array.size
|
||||||
|
@ -581,20 +592,20 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_empty_p
|
def test_compile_insn_opt_empty_p
|
||||||
assert_compile_once('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p])
|
assert_compile_twice('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_succ
|
def test_compile_insn_opt_succ
|
||||||
assert_compile_once('1.succ', result_inspect: '2', insns: %i[opt_succ])
|
assert_compile_twice('1.succ', result_inspect: '2', insns: %i[opt_succ])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_not
|
def test_compile_insn_opt_not
|
||||||
assert_compile_once('!!true', result_inspect: 'true', insns: %i[opt_not])
|
assert_compile_twice('!!true', result_inspect: 'true', insns: %i[opt_not])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_regexpmatch2
|
def test_compile_insn_opt_regexpmatch2
|
||||||
assert_compile_once("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2])
|
assert_compile_twice("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2])
|
||||||
assert_compile_once("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2])
|
assert_compile_twice("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_invokebuiltin
|
def test_compile_insn_invokebuiltin
|
||||||
|
@ -603,7 +614,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
EOS
|
EOS
|
||||||
insns = collect_insns(iseq)
|
insns = collect_insns(iseq)
|
||||||
mark_tested_insn(:invokebuiltin, used_insns: insns)
|
mark_tested_insn(:invokebuiltin, used_insns: insns)
|
||||||
assert_eval_with_jit('print [].sample(1)', stdout: '[]', success_count: 1)
|
assert_eval_with_jit('print [].sample(1); print [].sample(1)', stdout: '[][]', success_count: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_opt_invokebuiltin_delegate_leave
|
def test_compile_insn_opt_invokebuiltin_delegate_leave
|
||||||
|
@ -612,11 +623,11 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
EOS
|
EOS
|
||||||
insns = collect_insns(iseq)
|
insns = collect_insns(iseq)
|
||||||
mark_tested_insn(:opt_invokebuiltin_delegate_leave, used_insns: insns)
|
mark_tested_insn(:opt_invokebuiltin_delegate_leave, used_insns: insns)
|
||||||
assert_eval_with_jit('print "\x00".unpack("c")', stdout: '[0]', success_count: 1)
|
assert_eval_with_jit('print "\x00".unpack("c");print "\x00".unpack("c")', stdout: '[0][0]', success_count: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_insn_checkmatch
|
def test_compile_insn_checkmatch
|
||||||
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch])
|
assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch])
|
||||||
begin;
|
begin;
|
||||||
ary = %w(hello good-bye)
|
ary = %w(hello good-bye)
|
||||||
case 'hello'
|
case 'hello'
|
||||||
|
@ -627,23 +638,25 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_compile_opt_pc
|
def test_compile_opt_pc
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1)
|
||||||
begin;
|
begin;
|
||||||
def test(arg = 'hello')
|
def test(arg = 'hello')
|
||||||
print arg
|
print arg
|
||||||
end
|
end
|
||||||
test
|
test
|
||||||
|
test
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_mjit_output
|
def test_mjit_output
|
||||||
out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, call_threshold: 5)
|
out, err = eval_with_jit('4.times { puts "MJIT" }', verbose: 1, call_threshold: 2)
|
||||||
assert_equal("MJIT\n" * 5, out)
|
assert_equal("MJIT\n" * 4, out)
|
||||||
assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err)
|
assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1$/, err)
|
||||||
assert_match(/^Successful MJIT finish$/, err)
|
assert_match(/^Successful MJIT finish$/, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nothing_to_unload_with_jit_wait
|
def test_nothing_to_unload_with_jit_wait
|
||||||
|
omit 'unload_units is removed for now'
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
|
||||||
begin;
|
begin;
|
||||||
def a1() a2() end
|
def a1() a2() end
|
||||||
|
@ -662,6 +675,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unload_units_on_fiber
|
def test_unload_units_on_fiber
|
||||||
|
omit 'unload_units is removed for now'
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
|
||||||
begin;
|
begin;
|
||||||
def a1() a2(false); a2(true) end
|
def a1() a2(false); a2(true) end
|
||||||
|
@ -684,9 +698,10 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unload_units_and_compaction
|
def test_unload_units_and_compaction
|
||||||
|
omit 'unload_units is removed for now'
|
||||||
Dir.mktmpdir("jit_test_unload_units_") do |dir|
|
Dir.mktmpdir("jit_test_unload_units_") do |dir|
|
||||||
# MIN_CACHE_SIZE is 10
|
# MIN_CACHE_SIZE is 10
|
||||||
out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, call_threshold: 1, max_cache: 10)
|
out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, call_threshold: 2, max_cache: 10)
|
||||||
begin;
|
begin;
|
||||||
i = 0
|
i = 0
|
||||||
while i < 11
|
while i < 11
|
||||||
|
@ -741,12 +756,13 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
def arr
|
def arr
|
||||||
[nil, [:type => :development]]
|
[nil, [:type => :development]]
|
||||||
end
|
end
|
||||||
|
arr
|
||||||
p arr
|
p arr
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_local_stack_on_exception
|
def test_local_stack_on_exception
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2)
|
||||||
begin;
|
begin;
|
||||||
def b
|
def b
|
||||||
raise
|
raise
|
||||||
|
@ -761,11 +777,12 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
print a
|
print a
|
||||||
|
print a
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_local_stack_with_sp_motion_by_blockargs
|
def test_local_stack_with_sp_motion_by_blockargs
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 2)
|
||||||
begin;
|
begin;
|
||||||
def b(base)
|
def b(base)
|
||||||
1
|
1
|
||||||
|
@ -782,11 +799,12 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
print a
|
print a
|
||||||
|
print a
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_catching_deep_exception
|
def test_catching_deep_exception
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 4)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4)
|
||||||
begin;
|
begin;
|
||||||
def catch_true(paths, prefixes) # catch_except_p: true
|
def catch_true(paths, prefixes) # catch_except_p: true
|
||||||
prefixes.each do |prefix| # catch_except_p: true
|
prefixes.each do |prefix| # catch_except_p: true
|
||||||
|
@ -801,6 +819,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
print wrapper(['1'], ['2'])
|
print wrapper(['1'], ['2'])
|
||||||
|
print wrapper(['1'], ['2'])
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -819,7 +838,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_inlined_c_method
|
def test_inlined_c_method
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 2, recompile_count: 1, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 1, recompile_count: 1, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
def test(obj, recursive: nil)
|
def test(obj, recursive: nil)
|
||||||
if recursive
|
if recursive
|
||||||
|
@ -856,7 +875,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_inlined_undefined_ivar
|
def test_inlined_undefined_ivar
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 5, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 4, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class Foo
|
class Foo
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -877,7 +896,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_inlined_setivar_frozen
|
def test_inlined_setivar_frozen
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 2, call_threshold: 3)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 1, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class A
|
class A
|
||||||
def a
|
def a
|
||||||
|
@ -911,7 +930,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_attr_reader
|
def test_attr_reader
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 3, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class A
|
class A
|
||||||
attr_reader :a, :b
|
attr_reader :a, :b
|
||||||
|
@ -942,7 +961,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
print(2 * a.test)
|
print(2 * a.test)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 1, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 3, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class Hoge
|
class Hoge
|
||||||
attr_reader :foo
|
attr_reader :foo
|
||||||
|
@ -1004,12 +1023,13 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_jump_to_precompiled_branch
|
def test_jump_to_precompiled_branch
|
||||||
assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0", success_count: 1, call_threshold: 1)
|
assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0.0", success_count: 1, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
def test(foo)
|
def test(foo)
|
||||||
".#{foo unless foo == 1}" if true
|
".#{foo unless foo == 1}" if true
|
||||||
end
|
end
|
||||||
print test(0)
|
print test(0)
|
||||||
|
print test(0)
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1038,7 +1058,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
omit '.bundle.dSYM directory is left but removing it is not supported for now'
|
omit '.bundle.dSYM directory is left but removing it is not supported for now'
|
||||||
end
|
end
|
||||||
Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir|
|
Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir|
|
||||||
eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 1)
|
eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
def a; end; a
|
def a; end; a
|
||||||
exec "true"
|
exec "true"
|
||||||
|
@ -1070,7 +1090,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_frame_omitted_inlining
|
def test_frame_omitted_inlining
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\n", success_count: 1, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\ntrue\n", success_count: 2, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
class Integer
|
class Integer
|
||||||
remove_method :zero?
|
remove_method :zero?
|
||||||
|
@ -1079,20 +1099,20 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
3.times do
|
4.times do
|
||||||
p 0.zero?
|
p 0.zero?
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_block_handler_with_possible_frame_omitted_inlining
|
def test_block_handler_with_possible_frame_omitted_inlining
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n", success_count: 2, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n70.0\n", success_count: 2, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
def multiply(a, b)
|
def multiply(a, b)
|
||||||
a *= b
|
a *= b
|
||||||
end
|
end
|
||||||
|
|
||||||
3.times do
|
4.times do
|
||||||
p multiply(7.0, 10.0)
|
p multiply(7.0, 10.0)
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
|
@ -1131,7 +1151,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_mjit_pause_wait
|
def test_mjit_pause_wait
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, call_threshold: 1)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
RubyVM::MJIT.pause
|
RubyVM::MJIT.pause
|
||||||
proc {}.call
|
proc {}.call
|
||||||
|
@ -1139,7 +1159,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_not_cancel_by_tracepoint_class
|
def test_not_cancel_by_tracepoint_class
|
||||||
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 1, call_threshold: 2)
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
TracePoint.new(:class) {}.enable
|
TracePoint.new(:class) {}.enable
|
||||||
2.times {}
|
2.times {}
|
||||||
|
@ -1155,7 +1175,7 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_caller_locations_without_catch_table
|
def test_caller_locations_without_catch_table
|
||||||
out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 1)
|
out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2)
|
||||||
begin;
|
begin;
|
||||||
def b # 2
|
def b # 2
|
||||||
caller_locations.first # 3
|
caller_locations.first # 3
|
||||||
|
@ -1173,64 +1193,31 @@ class TestMJIT < Test::Unit::TestCase
|
||||||
assert_equal("-e:8:in `a'\n", lines[1])
|
assert_equal("-e:8:in `a'\n", lines[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_fork_with_mjit_worker_thread
|
|
||||||
Dir.mktmpdir("jit_test_fork_with_mjit_worker_thread_") do |dir|
|
|
||||||
# call_threshold: 2 to skip fork block
|
|
||||||
out, err = eval_with_jit({ "TMPDIR" => dir }, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2, verbose: 1)
|
|
||||||
begin;
|
|
||||||
def before_fork; end
|
|
||||||
def after_fork; end
|
|
||||||
|
|
||||||
before_fork; before_fork # the child should not delete this .o file
|
|
||||||
pid = Process.fork do # this child should not delete shared .pch file
|
|
||||||
sleep 2.0 # to prevent mixing outputs on Solaris
|
|
||||||
after_fork; after_fork # this child does not share JIT-ed after_fork with parent
|
|
||||||
end
|
|
||||||
after_fork; after_fork # this parent does not share JIT-ed after_fork with child
|
|
||||||
|
|
||||||
Process.waitpid(pid)
|
|
||||||
end;
|
|
||||||
success_count = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
|
|
||||||
debug_info = "stdout:\n```\n#{out}\n```\n\nstderr:\n```\n#{err}```\n"
|
|
||||||
assert_equal(3, success_count, debug_info)
|
|
||||||
|
|
||||||
# assert no remove error
|
|
||||||
assert_equal("Successful MJIT finish\n" * 2, err.gsub(/^#{JIT_SUCCESS_PREFIX}:[^\n]+\n/, ''), debug_info)
|
|
||||||
|
|
||||||
# ensure objects are deleted
|
|
||||||
if RUBY_PLATFORM.match?(/darwin/)
|
|
||||||
omit '.bundle.dSYM directory is left but removing it is not supported for now'
|
|
||||||
end
|
|
||||||
assert_send([Dir, :empty?, dir], debug_info)
|
|
||||||
end
|
|
||||||
end if defined?(fork)
|
|
||||||
|
|
||||||
def test_jit_failure
|
def test_jit_failure
|
||||||
_, err = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 1, verbose: 1)
|
_, err = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2, verbose: 1)
|
||||||
begin;
|
begin;
|
||||||
1.times do
|
2.times do
|
||||||
class A
|
class A
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
assert_match(/^MJIT warning: .+ unsupported instruction: defineclass/, err)
|
assert_match(/^MJIT warning: .+ unsupported instruction: defineclass/, err)
|
||||||
assert_match(/^JIT failure: block in <main>/, err)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# The shortest way to test one proc
|
# The shortest way to test one proc
|
||||||
def assert_compile_once(script, result_inspect:, insns: [], uplevel: 1)
|
def assert_compile_twice(script, result_inspect:, insns: [], uplevel: 1)
|
||||||
if script.match?(/\A\n.+\n\z/m)
|
if script.match?(/\A\n.+\n\z/m)
|
||||||
script = script.gsub(/^/, ' ')
|
script = script.gsub(/^/, ' ')
|
||||||
else
|
else
|
||||||
script = " #{script} "
|
script = " #{script} "
|
||||||
end
|
end
|
||||||
assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1)
|
assert_eval_with_jit("test = proc {#{script}}; p test.call; p test.call", stdout: "#{result_inspect}\n#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shorthand for normal test cases
|
# Shorthand for normal test cases
|
||||||
def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, call_threshold: 1, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: [])
|
def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, call_threshold: 2, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: [])
|
||||||
out, err = eval_with_jit(script, verbose: 1, call_threshold: call_threshold, max_cache: max_cache)
|
out, err = eval_with_jit(script, verbose: 1, call_threshold: call_threshold, max_cache: max_cache)
|
||||||
success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
|
success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
|
||||||
recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size
|
recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size
|
||||||
|
|
|
@ -14,10 +14,10 @@ class TestRubyVMMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause
|
def test_pause
|
||||||
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, wait: false)
|
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
|
||||||
i = 0
|
i = 0
|
||||||
while i < 5
|
while i < 5
|
||||||
eval("def mjit#{i}; end; mjit#{i}")
|
eval("def mjit#{i}; end; mjit#{i}; mjit#{i}")
|
||||||
i += 1
|
i += 1
|
||||||
end
|
end
|
||||||
print RubyVM::MJIT.pause
|
print RubyVM::MJIT.pause
|
||||||
|
@ -36,25 +36,22 @@ class TestRubyVMMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_waits_until_compaction
|
def test_pause_waits_until_compaction
|
||||||
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, wait: false)
|
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
|
||||||
def a() end; a
|
def a() end; a; a
|
||||||
def b() end; b
|
def b() end; b; b
|
||||||
RubyVM::MJIT.pause
|
RubyVM::MJIT.pause
|
||||||
EOS
|
EOS
|
||||||
assert_equal(
|
assert_equal(
|
||||||
2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
|
2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
|
||||||
"unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
|
"unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
|
||||||
)
|
)
|
||||||
assert_equal(
|
|
||||||
1, err.scan(/#{JITSupport::JIT_COMPACTION_PREFIX}/).size,
|
|
||||||
"unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
|
|
||||||
) unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_after_waitall
|
def test_pause_after_waitall
|
||||||
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, wait: false)
|
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
|
||||||
def test() = nil
|
def test() = nil
|
||||||
test
|
test
|
||||||
|
test
|
||||||
Process.waitall
|
Process.waitall
|
||||||
print RubyVM::MJIT.pause
|
print RubyVM::MJIT.pause
|
||||||
EOS
|
EOS
|
||||||
|
@ -65,7 +62,7 @@ class TestRubyVMMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_does_not_hang_on_full_units
|
def test_pause_does_not_hang_on_full_units
|
||||||
out, _ = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, max_cache: 10, wait: false)
|
out, _ = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, max_cache: 10, wait: false)
|
||||||
i = 0
|
i = 0
|
||||||
while i < 11
|
while i < 11
|
||||||
eval("def mjit#{i}; end; mjit#{i}")
|
eval("def mjit#{i}; end; mjit#{i}")
|
||||||
|
@ -77,7 +74,7 @@ class TestRubyVMMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_wait_false
|
def test_pause_wait_false
|
||||||
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, wait: false)
|
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
|
||||||
i = 0
|
i = 0
|
||||||
while i < 10
|
while i < 10
|
||||||
eval("def mjit#{i}; end; mjit#{i}")
|
eval("def mjit#{i}; end; mjit#{i}")
|
||||||
|
@ -95,7 +92,7 @@ class TestRubyVMMJIT < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resume
|
def test_resume
|
||||||
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 1, wait: false)
|
out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
|
||||||
print RubyVM::MJIT.resume
|
print RubyVM::MJIT.resume
|
||||||
print RubyVM::MJIT.pause
|
print RubyVM::MJIT.pause
|
||||||
print RubyVM::MJIT.resume
|
print RubyVM::MJIT.resume
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue