mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64931 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			776 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			776 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/**********************************************************************
 | 
						|
 | 
						|
  mjit.c - MRI method JIT compiler functions for Ruby's main thread
 | 
						|
 | 
						|
  Copyright (C) 2017 Vladimir Makarov <vmakarov@redhat.com>.
 | 
						|
 | 
						|
**********************************************************************/
 | 
						|
 | 
						|
/* Functions in this file are never executed on MJIT worker thread.
 | 
						|
   So you can safely use Ruby methods and GC in this file. */
 | 
						|
 | 
						|
/* To share variables privately, include mjit_worker.c instead of linking. */
 | 
						|
#include "mjit_worker.c"
 | 
						|
 | 
						|
#include "constant.h"
 | 
						|
#include "id_table.h"
 | 
						|
 | 
						|
extern int rb_thread_create_mjit_thread(void (*worker_func)(void));
 | 
						|
 | 
						|
/* Return an unique file name in /tmp with PREFIX and SUFFIX and
 | 
						|
   number ID.  Use getpid if ID == 0.  The return file name exists
 | 
						|
   until the next function call.  */
 | 
						|
static char *
 | 
						|
get_uniq_filename(unsigned long id, const char *prefix, const char *suffix)
 | 
						|
{
 | 
						|
    char buff[70], *str = buff;
 | 
						|
    int size = sprint_uniq_filename(buff, sizeof(buff), id, prefix, suffix);
 | 
						|
    str = 0;
 | 
						|
    ++size;
 | 
						|
    str = xmalloc(size);
 | 
						|
    if (size <= (int)sizeof(buff)) {
 | 
						|
        memcpy(str, buff, size);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        sprint_uniq_filename(str, size, id, prefix, suffix);
 | 
						|
    }
 | 
						|
    return str;
 | 
						|
}
 | 
						|
 | 
						|
/* Wait until workers don't compile any iseq.  It is called at the
 | 
						|
   start of GC.  */
 | 
						|
void
 | 
						|
mjit_gc_start_hook(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_gc_start_hook");
 | 
						|
    while (in_jit) {
 | 
						|
        verbose(4, "Waiting wakeup from a worker for GC");
 | 
						|
        rb_native_cond_wait(&mjit_client_wakeup, &mjit_engine_mutex);
 | 
						|
        verbose(4, "Getting wakeup from a worker for GC");
 | 
						|
    }
 | 
						|
    in_gc = TRUE;
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_gc_start_hook");
 | 
						|
}
 | 
						|
 | 
						|
/* Send a signal to workers to continue iseq compilations.  It is
 | 
						|
   called at the end of GC.  */
 | 
						|
void
 | 
						|
mjit_gc_finish_hook(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_gc_finish_hook");
 | 
						|
    in_gc = FALSE;
 | 
						|
    verbose(4, "Sending wakeup signal to workers after GC");
 | 
						|
    rb_native_cond_broadcast(&mjit_gc_wakeup);
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_gc_finish_hook");
 | 
						|
}
 | 
						|
 | 
						|
/* Iseqs can be garbage collected.  This function should call when it
 | 
						|
   happens.  It removes iseq from the unit.  */
 | 
						|
void
 | 
						|
mjit_free_iseq(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_free_iseq");
 | 
						|
    if (iseq->body->jit_unit) {
 | 
						|
        /* jit_unit is not freed here because it may be referred by multiple
 | 
						|
           lists of units. `get_from_list` and `mjit_finish` do the job. */
 | 
						|
        iseq->body->jit_unit->iseq = NULL;
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_free_iseq");
 | 
						|
}
 | 
						|
 | 
						|
/* Do we need this...? */
 | 
						|
static void
 | 
						|
init_list(struct rb_mjit_unit_list *list)
 | 
						|
{
 | 
						|
    list->head = NULL;
 | 
						|
    list->length = 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Free unit list. This should be called only when worker is finished
 | 
						|
   because node of unit_queue and one of active_units may have the same unit
 | 
						|
   during proceeding unit. */
 | 
						|
static void
 | 
						|
free_list(struct rb_mjit_unit_list *list)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit_node *node, *next;
 | 
						|
    for (node = list->head; node != NULL; node = next) {
 | 
						|
        next = node->next;
 | 
						|
        free_unit(node->unit);
 | 
						|
        xfree(node);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* MJIT info related to an existing continutaion.  */
 | 
						|
struct mjit_cont {
 | 
						|
    rb_execution_context_t *ec; /* continuation ec */
 | 
						|
    struct mjit_cont *prev, *next; /* used to form lists */
 | 
						|
};
 | 
						|
 | 
						|
/* Double linked list of registered continuations. This is used to detect
 | 
						|
   units which are in use in unload_units. */
 | 
						|
static struct mjit_cont *first_cont;
 | 
						|
 | 
						|
/* Register a new continuation with thread TH.  Return MJIT info about
 | 
						|
   the continuation.  */
 | 
						|
struct mjit_cont *
 | 
						|
mjit_cont_new(rb_execution_context_t *ec)
 | 
						|
{
 | 
						|
    struct mjit_cont *cont;
 | 
						|
 | 
						|
    cont = ZALLOC(struct mjit_cont);
 | 
						|
    cont->ec = ec;
 | 
						|
 | 
						|
    CRITICAL_SECTION_START(3, "in mjit_cont_new");
 | 
						|
    if (first_cont == NULL) {
 | 
						|
        cont->next = cont->prev = NULL;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        cont->prev = NULL;
 | 
						|
        cont->next = first_cont;
 | 
						|
        first_cont->prev = cont;
 | 
						|
    }
 | 
						|
    first_cont = cont;
 | 
						|
    CRITICAL_SECTION_FINISH(3, "in mjit_cont_new");
 | 
						|
 | 
						|
    return cont;
 | 
						|
}
 | 
						|
 | 
						|
/* Unregister continuation CONT.  */
 | 
						|
void
 | 
						|
mjit_cont_free(struct mjit_cont *cont)
 | 
						|
{
 | 
						|
    CRITICAL_SECTION_START(3, "in mjit_cont_new");
 | 
						|
    if (cont == first_cont) {
 | 
						|
        first_cont = cont->next;
 | 
						|
        if (first_cont != NULL)
 | 
						|
            first_cont->prev = NULL;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        cont->prev->next = cont->next;
 | 
						|
        if (cont->next != NULL)
 | 
						|
            cont->next->prev = cont->prev;
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(3, "in mjit_cont_new");
 | 
						|
 | 
						|
    xfree(cont);
 | 
						|
}
 | 
						|
 | 
						|
/* Finish work with continuation info. */
 | 
						|
static void
 | 
						|
finish_conts(void)
 | 
						|
{
 | 
						|
    struct mjit_cont *cont, *next;
 | 
						|
 | 
						|
    for (cont = first_cont; cont != NULL; cont = next) {
 | 
						|
        next = cont->next;
 | 
						|
        xfree(cont);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* Create unit for ISEQ. */
 | 
						|
static void
 | 
						|
create_unit(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit *unit;
 | 
						|
 | 
						|
    unit = ZALLOC(struct rb_mjit_unit);
 | 
						|
    if (unit == NULL)
 | 
						|
        return;
 | 
						|
 | 
						|
    unit->id = current_unit_num++;
 | 
						|
    unit->iseq = iseq;
 | 
						|
    iseq->body->jit_unit = unit;
 | 
						|
}
 | 
						|
 | 
						|
/* Set up field used_code_p for unit iseqs whose iseq on the stack of ec. */
 | 
						|
static void
 | 
						|
mark_ec_units(rb_execution_context_t *ec)
 | 
						|
{
 | 
						|
    const rb_control_frame_t *cfp;
 | 
						|
 | 
						|
    if (ec->vm_stack == NULL)
 | 
						|
        return;
 | 
						|
    for (cfp = RUBY_VM_END_CONTROL_FRAME(ec) - 1; ; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
 | 
						|
        const rb_iseq_t *iseq;
 | 
						|
        if (cfp->pc && (iseq = cfp->iseq) != NULL
 | 
						|
            && imemo_type((VALUE) iseq) == imemo_iseq
 | 
						|
            && (iseq->body->jit_unit) != NULL) {
 | 
						|
            iseq->body->jit_unit->used_code_p = TRUE;
 | 
						|
        }
 | 
						|
 | 
						|
        if (cfp == ec->cfp)
 | 
						|
            break; /* reached the most recent cfp */
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* Unload JIT code of some units to satisfy the maximum permitted
 | 
						|
   number of units with a loaded code.  */
 | 
						|
static void
 | 
						|
unload_units(void)
 | 
						|
{
 | 
						|
    rb_vm_t *vm = GET_THREAD()->vm;
 | 
						|
    rb_thread_t *th = NULL;
 | 
						|
    struct rb_mjit_unit_node *node, *next, *worst_node;
 | 
						|
    struct mjit_cont *cont;
 | 
						|
    int delete_num, units_num = active_units.length;
 | 
						|
 | 
						|
    /* For now, we don't unload units when ISeq is GCed. We should
 | 
						|
       unload such ISeqs first here. */
 | 
						|
    for (node = active_units.head; node != NULL; node = next) {
 | 
						|
        next = node->next;
 | 
						|
        if (node->unit->iseq == NULL) { /* ISeq is GCed. */
 | 
						|
            free_unit(node->unit);
 | 
						|
            remove_from_list(node, &active_units);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* Detect units which are in use and can't be unloaded. */
 | 
						|
    for (node = active_units.head; node != NULL; node = node->next) {
 | 
						|
        assert(node->unit != NULL && node->unit->iseq != NULL && node->unit->handle != NULL);
 | 
						|
        node->unit->used_code_p = FALSE;
 | 
						|
    }
 | 
						|
    list_for_each(&vm->living_threads, th, vmlt_node) {
 | 
						|
        mark_ec_units(th->ec);
 | 
						|
    }
 | 
						|
    for (cont = first_cont; cont != NULL; cont = cont->next) {
 | 
						|
        mark_ec_units(cont->ec);
 | 
						|
    }
 | 
						|
 | 
						|
    /* Remove 1/10 units more to decrease unloading calls.  */
 | 
						|
    /* TODO: Calculate max total_calls in unit_queue and don't unload units
 | 
						|
       whose total_calls are larger than the max. */
 | 
						|
    delete_num = active_units.length / 10;
 | 
						|
    for (; active_units.length > mjit_opts.max_cache_size - delete_num;) {
 | 
						|
        /* Find one unit that has the minimum total_calls. */
 | 
						|
        worst_node = NULL;
 | 
						|
        for (node = active_units.head; node != NULL; node = node->next) {
 | 
						|
            if (node->unit->used_code_p) /* We can't unload code on stack. */
 | 
						|
                continue;
 | 
						|
 | 
						|
            if (worst_node == NULL || worst_node->unit->iseq->body->total_calls > node->unit->iseq->body->total_calls) {
 | 
						|
                worst_node = node;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (worst_node == NULL)
 | 
						|
            break;
 | 
						|
 | 
						|
        /* Unload the worst node. */
 | 
						|
        verbose(2, "Unloading unit %d (calls=%lu)", worst_node->unit->id, worst_node->unit->iseq->body->total_calls);
 | 
						|
        assert(worst_node->unit->handle != NULL);
 | 
						|
        free_unit(worst_node->unit);
 | 
						|
        remove_from_list(worst_node, &active_units);
 | 
						|
    }
 | 
						|
    verbose(1, "Too many JIT code -- %d units unloaded", units_num - active_units.length);
 | 
						|
}
 | 
						|
 | 
						|
/* Add ISEQ to be JITed in parallel with the current thread.
 | 
						|
   Unload some JIT codes if there are too many of them.  */
 | 
						|
void
 | 
						|
mjit_add_iseq_to_process(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit_node *node;
 | 
						|
 | 
						|
    if (!mjit_enabled || pch_status == PCH_FAILED)
 | 
						|
        return;
 | 
						|
 | 
						|
    iseq->body->jit_func = (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC;
 | 
						|
    create_unit(iseq);
 | 
						|
    if (iseq->body->jit_unit == NULL)
 | 
						|
        /* Failure in creating the unit.  */
 | 
						|
        return;
 | 
						|
 | 
						|
    node = create_list_node(iseq->body->jit_unit);
 | 
						|
    if (node == NULL) {
 | 
						|
        mjit_warning("failed to allocate a node to be added to unit_queue");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    CRITICAL_SECTION_START(3, "in add_iseq_to_process");
 | 
						|
    add_to_list(node, &unit_queue);
 | 
						|
    if (active_units.length >= mjit_opts.max_cache_size) {
 | 
						|
        unload_units();
 | 
						|
    }
 | 
						|
    verbose(3, "Sending wakeup signal to workers in mjit_add_iseq_to_process");
 | 
						|
    rb_native_cond_broadcast(&mjit_worker_wakeup);
 | 
						|
    CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process");
 | 
						|
}
 | 
						|
 | 
						|
/* For this timeout seconds, --jit-wait will wait for JIT compilation finish. */
 | 
						|
#define MJIT_WAIT_TIMEOUT_SECONDS 60
 | 
						|
 | 
						|
/* Wait for JIT compilation finish for --jit-wait, and call the function pointer
 | 
						|
   if the compiled result is not NOT_COMPILED_JIT_ISEQ_FUNC. */
 | 
						|
VALUE
 | 
						|
mjit_wait_call(rb_execution_context_t *ec, struct rb_iseq_constant_body *body)
 | 
						|
{
 | 
						|
    struct timeval tv;
 | 
						|
    int tries = 0;
 | 
						|
    tv.tv_sec = 0;
 | 
						|
    tv.tv_usec = 1000;
 | 
						|
    while (body->jit_func == (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC) {
 | 
						|
        tries++;
 | 
						|
        if (tries / 1000 > MJIT_WAIT_TIMEOUT_SECONDS || pch_status == PCH_FAILED) {
 | 
						|
            CRITICAL_SECTION_START(3, "in mjit_wait_call to set jit_func");
 | 
						|
            body->jit_func = (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC; /* JIT worker seems dead. Give up. */
 | 
						|
            CRITICAL_SECTION_FINISH(3, "in mjit_wait_call to set jit_func");
 | 
						|
            mjit_warning("timed out to wait for JIT finish");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        CRITICAL_SECTION_START(3, "in mjit_wait_call for a client wakeup");
 | 
						|
        rb_native_cond_broadcast(&mjit_worker_wakeup);
 | 
						|
        CRITICAL_SECTION_FINISH(3, "in mjit_wait_call for a client wakeup");
 | 
						|
        rb_thread_wait_for(tv);
 | 
						|
    }
 | 
						|
 | 
						|
    if ((uintptr_t)body->jit_func <= (uintptr_t)LAST_JIT_ISEQ_FUNC) {
 | 
						|
        return Qundef;
 | 
						|
    }
 | 
						|
    return body->jit_func(ec, ec->cfp);
 | 
						|
}
 | 
						|
 | 
						|
extern VALUE ruby_archlibdir_path, ruby_prefix_path;
 | 
						|
 | 
						|
/* Initialize header_file, pch_file, libruby_pathflag. Return TRUE on success. */
 | 
						|
static int
 | 
						|
init_header_filename(void)
 | 
						|
{
 | 
						|
    int fd;
 | 
						|
    /* Root path of the running ruby process. Equal to RbConfig::TOPDIR.  */
 | 
						|
    VALUE basedir_val;
 | 
						|
    const char *basedir;
 | 
						|
    size_t baselen;
 | 
						|
    char *p;
 | 
						|
#ifdef _WIN32
 | 
						|
    static const char libpathflag[] =
 | 
						|
# ifdef _MSC_VER
 | 
						|
        "-LIBPATH:"
 | 
						|
# else
 | 
						|
        "-L"
 | 
						|
# endif
 | 
						|
        ;
 | 
						|
    const size_t libpathflag_len = sizeof(libpathflag) - 1;
 | 
						|
#endif
 | 
						|
 | 
						|
    basedir_val = ruby_prefix_path;
 | 
						|
    basedir = StringValuePtr(basedir_val);
 | 
						|
    baselen = RSTRING_LEN(basedir_val);
 | 
						|
 | 
						|
#ifndef LOAD_RELATIVE
 | 
						|
    if (getenv("MJIT_SEARCH_BUILD_DIR")) {
 | 
						|
        /* This path is not intended to be used on production, but using build directory's
 | 
						|
           header file here because people want to run `make test-all` without running
 | 
						|
           `make install`. Don't use $MJIT_SEARCH_BUILD_DIR except for test-all. */
 | 
						|
        basedir = MJIT_BUILD_DIR;
 | 
						|
        baselen = strlen(basedir);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef _MSC_VER
 | 
						|
    {
 | 
						|
        /* A name of the header file included in any C file generated by MJIT for iseqs. */
 | 
						|
        static const char header_name[] = MJIT_MIN_HEADER_NAME;
 | 
						|
        const size_t header_name_len = sizeof(header_name) - 1;
 | 
						|
 | 
						|
        header_file = xmalloc(baselen + header_name_len + 1);
 | 
						|
        p = append_str2(header_file, basedir, baselen);
 | 
						|
        p = append_str2(p, header_name, header_name_len + 1);
 | 
						|
        if ((fd = rb_cloexec_open(header_file, O_RDONLY, 0)) < 0) {
 | 
						|
            verbose(1, "Cannot access header file: %s", header_file);
 | 
						|
            xfree(header_file);
 | 
						|
            header_file = NULL;
 | 
						|
            return FALSE;
 | 
						|
        }
 | 
						|
        (void)close(fd);
 | 
						|
    }
 | 
						|
 | 
						|
    pch_file = get_uniq_filename(0, MJIT_TMP_PREFIX "h", ".h.gch");
 | 
						|
    if (pch_file == NULL)
 | 
						|
        return FALSE;
 | 
						|
#else
 | 
						|
    {
 | 
						|
        static const char pch_name[] = MJIT_PRECOMPILED_HEADER_NAME;
 | 
						|
        const size_t pch_name_len = sizeof(pch_name) - 1;
 | 
						|
 | 
						|
        pch_file = xmalloc(baselen + pch_name_len + 1);
 | 
						|
        p = append_str2(pch_file, basedir, baselen);
 | 
						|
        p = append_str2(p, pch_name, pch_name_len + 1);
 | 
						|
        if ((fd = rb_cloexec_open(pch_file, O_RDONLY, 0)) < 0) {
 | 
						|
            verbose(1, "Cannot access precompiled header file: %s", pch_file);
 | 
						|
            xfree(pch_file);
 | 
						|
            pch_file = NULL;
 | 
						|
            return FALSE;
 | 
						|
        }
 | 
						|
        (void)close(fd);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
    basedir_val = ruby_archlibdir_path;
 | 
						|
    basedir = StringValuePtr(basedir_val);
 | 
						|
    baselen = RSTRING_LEN(basedir_val);
 | 
						|
    libruby_pathflag = p = xmalloc(libpathflag_len + baselen + 1);
 | 
						|
    p = append_str(p, libpathflag);
 | 
						|
    p = append_str2(p, basedir, baselen);
 | 
						|
    *p = '\0';
 | 
						|
#endif
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/* This is called after each fork in the child in to switch off MJIT
 | 
						|
   engine in the child as it does not inherit MJIT threads.  */
 | 
						|
void
 | 
						|
mjit_child_after_fork(void)
 | 
						|
{
 | 
						|
    if (mjit_enabled) {
 | 
						|
        verbose(3, "Switching off MJIT in a forked child");
 | 
						|
        mjit_enabled = FALSE;
 | 
						|
    }
 | 
						|
    /* 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;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
UINT rb_w32_system_tmpdir(WCHAR *path, UINT len);
 | 
						|
#endif
 | 
						|
 | 
						|
static char *
 | 
						|
system_default_tmpdir(void)
 | 
						|
{
 | 
						|
    /* c.f. ext/etc/etc.c:etc_systmpdir() */
 | 
						|
#ifdef _WIN32
 | 
						|
    WCHAR tmppath[_MAX_PATH];
 | 
						|
    UINT len = rb_w32_system_tmpdir(tmppath, numberof(tmppath));
 | 
						|
    if (len) {
 | 
						|
        int blen = WideCharToMultiByte(CP_UTF8, 0, tmppath, len, NULL, 0, NULL, NULL);
 | 
						|
        char *tmpdir = xmalloc(blen + 1);
 | 
						|
        WideCharToMultiByte(CP_UTF8, 0, tmppath, len, tmpdir, blen, NULL, NULL);
 | 
						|
        tmpdir[blen] = '\0';
 | 
						|
        return tmpdir;
 | 
						|
    }
 | 
						|
#elif defined _CS_DARWIN_USER_TEMP_DIR
 | 
						|
    char path[MAXPATHLEN];
 | 
						|
    size_t len = confstr(_CS_DARWIN_USER_TEMP_DIR, path, sizeof(path));
 | 
						|
    if (len > 0) {
 | 
						|
        char *tmpdir = xmalloc(len);
 | 
						|
        if (len > sizeof(path)) {
 | 
						|
            confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdir, len);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            memcpy(tmpdir, path, len);
 | 
						|
        }
 | 
						|
        return tmpdir;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
check_tmpdir(const char *dir)
 | 
						|
{
 | 
						|
    struct stat st;
 | 
						|
 | 
						|
    if (!dir) return FALSE;
 | 
						|
    if (stat(dir, &st)) return FALSE;
 | 
						|
#ifndef S_ISDIR
 | 
						|
#   define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
 | 
						|
#endif
 | 
						|
    if (!S_ISDIR(st.st_mode)) return FALSE;
 | 
						|
#ifndef _WIN32
 | 
						|
# ifndef S_IWOTH
 | 
						|
#   define S_IWOTH 002
 | 
						|
# endif
 | 
						|
    if (st.st_mode & S_IWOTH) {
 | 
						|
# ifdef S_ISVTX
 | 
						|
        if (!(st.st_mode & S_ISVTX)) return FALSE;
 | 
						|
# else
 | 
						|
        return FALSE;
 | 
						|
# endif
 | 
						|
    }
 | 
						|
    if (access(dir, W_OK)) return FALSE;
 | 
						|
#endif
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
system_tmpdir(void)
 | 
						|
{
 | 
						|
    char *tmpdir;
 | 
						|
# define RETURN_ENV(name) \
 | 
						|
    if (check_tmpdir(tmpdir = getenv(name))) return ruby_strdup(tmpdir)
 | 
						|
    RETURN_ENV("TMPDIR");
 | 
						|
    RETURN_ENV("TMP");
 | 
						|
    tmpdir = system_default_tmpdir();
 | 
						|
    if (check_tmpdir(tmpdir)) return tmpdir;
 | 
						|
    return ruby_strdup("/tmp");
 | 
						|
# undef RETURN_ENV
 | 
						|
}
 | 
						|
 | 
						|
/* Default permitted number of units with a JIT code kept in
 | 
						|
   memory.  */
 | 
						|
#define DEFAULT_CACHE_SIZE 1000
 | 
						|
/* A default threshold used to add iseq to JIT. */
 | 
						|
#define DEFAULT_MIN_CALLS_TO_ADD 5
 | 
						|
/* Minimum value for JIT cache size.  */
 | 
						|
#define MIN_CACHE_SIZE 10
 | 
						|
 | 
						|
/* Start MJIT worker. Return TRUE if worker is sucessfully started. */
 | 
						|
static int
 | 
						|
start_worker(void)
 | 
						|
{
 | 
						|
    stop_worker_p = FALSE;
 | 
						|
    worker_stopped = FALSE;
 | 
						|
 | 
						|
    if (!rb_thread_create_mjit_thread(mjit_worker)) {
 | 
						|
        mjit_enabled = FALSE;
 | 
						|
        rb_native_mutex_destroy(&mjit_engine_mutex);
 | 
						|
        rb_native_cond_destroy(&mjit_pch_wakeup);
 | 
						|
        rb_native_cond_destroy(&mjit_client_wakeup);
 | 
						|
        rb_native_cond_destroy(&mjit_worker_wakeup);
 | 
						|
        rb_native_cond_destroy(&mjit_gc_wakeup);
 | 
						|
        verbose(1, "Failure in MJIT thread initialization\n");
 | 
						|
        return FALSE;
 | 
						|
    }
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/* Initialize MJIT.  Start a thread creating the precompiled header and
 | 
						|
   processing ISeqs.  The function should be called first for using MJIT.
 | 
						|
   If everything is successfull, MJIT_INIT_P will be TRUE.  */
 | 
						|
void
 | 
						|
mjit_init(struct mjit_options *opts)
 | 
						|
{
 | 
						|
    mjit_opts = *opts;
 | 
						|
    mjit_enabled = TRUE;
 | 
						|
    mjit_call_p = TRUE;
 | 
						|
 | 
						|
    /* Normalize options */
 | 
						|
    if (mjit_opts.min_calls == 0)
 | 
						|
        mjit_opts.min_calls = DEFAULT_MIN_CALLS_TO_ADD;
 | 
						|
    if (mjit_opts.max_cache_size <= 0)
 | 
						|
        mjit_opts.max_cache_size = DEFAULT_CACHE_SIZE;
 | 
						|
    if (mjit_opts.max_cache_size < MIN_CACHE_SIZE)
 | 
						|
        mjit_opts.max_cache_size = MIN_CACHE_SIZE;
 | 
						|
 | 
						|
    verbose(2, "MJIT: CC defaults to %s", CC_PATH);
 | 
						|
 | 
						|
    /* Initialize variables for compilation */
 | 
						|
#ifdef _MSC_VER
 | 
						|
    pch_status = PCH_SUCCESS; /* has prebuilt precompiled header */
 | 
						|
#else
 | 
						|
    pch_status = PCH_NOT_READY;
 | 
						|
#endif
 | 
						|
    cc_path = CC_PATH;
 | 
						|
 | 
						|
    tmp_dir = system_tmpdir();
 | 
						|
    verbose(2, "MJIT: tmp_dir is %s", tmp_dir);
 | 
						|
 | 
						|
    if (!init_header_filename()) {
 | 
						|
        mjit_enabled = FALSE;
 | 
						|
        verbose(1, "Failure in MJIT header file name initialization\n");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    init_list(&unit_queue);
 | 
						|
    init_list(&active_units);
 | 
						|
    init_list(&compact_units);
 | 
						|
 | 
						|
    /* Initialize mutex */
 | 
						|
    rb_native_mutex_initialize(&mjit_engine_mutex);
 | 
						|
    rb_native_cond_initialize(&mjit_pch_wakeup);
 | 
						|
    rb_native_cond_initialize(&mjit_client_wakeup);
 | 
						|
    rb_native_cond_initialize(&mjit_worker_wakeup);
 | 
						|
    rb_native_cond_initialize(&mjit_gc_wakeup);
 | 
						|
 | 
						|
    /* 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);
 | 
						|
    mjit_add_class_serial(RCLASS_SERIAL(rb_cObject));
 | 
						|
    mjit_add_class_serial(RCLASS_SERIAL(CLASS_OF(rb_vm_top_self())));
 | 
						|
    if (RCLASS_CONST_TBL(rb_cObject)) {
 | 
						|
        rb_id_table_foreach(RCLASS_CONST_TBL(rb_cObject), valid_class_serials_add_i, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    /* Initialize worker thread */
 | 
						|
    start_worker();
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
stop_worker(void)
 | 
						|
{
 | 
						|
    rb_execution_context_t *ec = GET_EC();
 | 
						|
 | 
						|
    stop_worker_p = TRUE;
 | 
						|
    while (!worker_stopped) {
 | 
						|
        verbose(3, "Sending cancel signal to worker");
 | 
						|
        CRITICAL_SECTION_START(3, "in stop_worker");
 | 
						|
        rb_native_cond_broadcast(&mjit_worker_wakeup);
 | 
						|
        CRITICAL_SECTION_FINISH(3, "in stop_worker");
 | 
						|
        RUBY_VM_CHECK_INTS(ec);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* Stop JIT-compiling methods but compiled code is kept available. */
 | 
						|
VALUE
 | 
						|
mjit_pause(int wait_p)
 | 
						|
{
 | 
						|
    if (!mjit_enabled) {
 | 
						|
        rb_raise(rb_eRuntimeError, "MJIT is not enabled");
 | 
						|
    }
 | 
						|
    if (worker_stopped) {
 | 
						|
        return Qfalse;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Flush all queued units with no option or `wait: true` */
 | 
						|
    if (wait_p) {
 | 
						|
        struct timeval tv;
 | 
						|
        tv.tv_sec = 0;
 | 
						|
        tv.tv_usec = 1000;
 | 
						|
 | 
						|
        while (unit_queue.length > 0) {
 | 
						|
            CRITICAL_SECTION_START(3, "in mjit_pause for a worker wakeup");
 | 
						|
            rb_native_cond_broadcast(&mjit_worker_wakeup);
 | 
						|
            CRITICAL_SECTION_FINISH(3, "in mjit_pause for a worker wakeup");
 | 
						|
            rb_thread_wait_for(tv);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    stop_worker();
 | 
						|
    return Qtrue;
 | 
						|
}
 | 
						|
 | 
						|
/* Restart JIT-compiling methods after mjit_pause. */
 | 
						|
VALUE
 | 
						|
mjit_resume(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled) {
 | 
						|
        rb_raise(rb_eRuntimeError, "MJIT is not enabled");
 | 
						|
    }
 | 
						|
    if (!worker_stopped) {
 | 
						|
        return Qfalse;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!start_worker()) {
 | 
						|
        rb_raise(rb_eRuntimeError, "Failed to resume MJIT worker");
 | 
						|
    }
 | 
						|
    return Qtrue;
 | 
						|
}
 | 
						|
 | 
						|
/* Finish the threads processing units and creating PCH, finalize
 | 
						|
   and free MJIT data.  It should be called last during MJIT
 | 
						|
   life.  */
 | 
						|
void
 | 
						|
mjit_finish(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
 | 
						|
    /* Wait for pch finish */
 | 
						|
    verbose(2, "Stopping worker thread");
 | 
						|
    CRITICAL_SECTION_START(3, "in mjit_finish to wakeup from pch");
 | 
						|
    /* As our threads are detached, we could just cancel them.  But it
 | 
						|
       is a bad idea because OS processes (C compiler) started by
 | 
						|
       threads can produce temp files.  And even if the temp files are
 | 
						|
       removed, the used C compiler still complaint about their
 | 
						|
       absence.  So wait for a clean finish of the threads.  */
 | 
						|
    while (pch_status == PCH_NOT_READY) {
 | 
						|
        verbose(3, "Waiting wakeup from make_pch");
 | 
						|
        rb_native_cond_wait(&mjit_pch_wakeup, &mjit_engine_mutex);
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(3, "in mjit_finish to wakeup from pch");
 | 
						|
 | 
						|
    /* Stop worker */
 | 
						|
    stop_worker();
 | 
						|
 | 
						|
    rb_native_mutex_destroy(&mjit_engine_mutex);
 | 
						|
    rb_native_cond_destroy(&mjit_pch_wakeup);
 | 
						|
    rb_native_cond_destroy(&mjit_client_wakeup);
 | 
						|
    rb_native_cond_destroy(&mjit_worker_wakeup);
 | 
						|
    rb_native_cond_destroy(&mjit_gc_wakeup);
 | 
						|
 | 
						|
#ifndef _MSC_VER /* mswin has prebuilt precompiled header */
 | 
						|
    if (!mjit_opts.save_temps)
 | 
						|
        remove_file(pch_file);
 | 
						|
 | 
						|
    xfree(header_file); header_file = NULL;
 | 
						|
#endif
 | 
						|
    xfree(tmp_dir); tmp_dir = NULL;
 | 
						|
    xfree(pch_file); pch_file = NULL;
 | 
						|
 | 
						|
    mjit_call_p = FALSE;
 | 
						|
    free_list(&unit_queue);
 | 
						|
    free_list(&active_units);
 | 
						|
    free_list(&compact_units);
 | 
						|
    finish_conts();
 | 
						|
 | 
						|
    mjit_enabled = FALSE;
 | 
						|
    verbose(1, "Successful MJIT finish");
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
mjit_mark(void)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit_node *node;
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    RUBY_MARK_ENTER("mjit");
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_mark");
 | 
						|
    for (node = unit_queue.head; node != NULL; node = node->next) {
 | 
						|
        if (node->unit->iseq) { /* ISeq is still not GCed */
 | 
						|
            VALUE iseq = (VALUE)node->unit->iseq;
 | 
						|
            CRITICAL_SECTION_FINISH(4, "mjit_mark rb_gc_mark");
 | 
						|
 | 
						|
            /* Don't wrap critical section with this. This may trigger GC,
 | 
						|
               and in that case mjit_gc_start_hook causes deadlock. */
 | 
						|
            rb_gc_mark(iseq);
 | 
						|
 | 
						|
            CRITICAL_SECTION_START(4, "mjit_mark rb_gc_mark");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_mark");
 | 
						|
    RUBY_MARK_LEAVE("mjit");
 | 
						|
}
 | 
						|
 | 
						|
/* A hook to update valid_class_serials. */
 | 
						|
void
 | 
						|
mjit_add_class_serial(rb_serial_t class_serial)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
 | 
						|
    /* Do not wrap CRITICAL_SECTION here. This function is only called in main thread
 | 
						|
       and guarded by GVL, and `rb_hash_aset` may cause GC and deadlock in it. */
 | 
						|
    rb_hash_aset(valid_class_serials, LONG2FIX(class_serial), Qtrue);
 | 
						|
}
 | 
						|
 | 
						|
/* A hook to update valid_class_serials. */
 | 
						|
void
 | 
						|
mjit_remove_class_serial(rb_serial_t class_serial)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        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");
 | 
						|
}
 |