mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1002 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1002 lines
		
	
	
	
		
			32 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 "ruby/internal/config.h" // defines USE_MJIT
 | 
						|
 | 
						|
#if USE_MJIT
 | 
						|
 | 
						|
#include "constant.h"
 | 
						|
#include "id_table.h"
 | 
						|
#include "internal.h"
 | 
						|
#include "internal/class.h"
 | 
						|
#include "internal/cont.h"
 | 
						|
#include "internal/file.h"
 | 
						|
#include "internal/hash.h"
 | 
						|
#include "internal/warnings.h"
 | 
						|
#include "vm_sync.h"
 | 
						|
 | 
						|
#include "mjit_worker.c"
 | 
						|
 | 
						|
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++;
 | 
						|
    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_exit_hook(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_gc_exit_hook");
 | 
						|
    in_gc--;
 | 
						|
    RUBY_ASSERT_ALWAYS(in_gc >= 0);
 | 
						|
    if (!in_gc) {
 | 
						|
        verbose(4, "Sending wakeup signal to workers after GC");
 | 
						|
        rb_native_cond_broadcast(&mjit_gc_wakeup);
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_gc_exit_hook");
 | 
						|
}
 | 
						|
 | 
						|
// Deal with ISeq movement from compactor
 | 
						|
void
 | 
						|
mjit_update_references(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_update_references");
 | 
						|
    if (iseq->body->jit_unit) {
 | 
						|
        iseq->body->jit_unit->iseq = (rb_iseq_t *)rb_gc_location((VALUE)iseq->body->jit_unit->iseq);
 | 
						|
        // We need to invalidate JIT-ed code for the ISeq because it embeds pointer addresses.
 | 
						|
        // To efficiently do that, we use the same thing as TracePoint and thus everything is cancelled for now.
 | 
						|
        // See mjit.h and tool/ruby_vm/views/_mjit_compile_insn.erb for how `mjit_call_p` is used.
 | 
						|
        mjit_call_p = false; // TODO: instead of cancelling all, invalidate only this one and recompile it with some threshold.
 | 
						|
    }
 | 
						|
 | 
						|
    // Units in stale_units (list of over-speculated and invalidated code) are not referenced from
 | 
						|
    // `iseq->body->jit_unit` anymore (because new one replaces that). So we need to check them too.
 | 
						|
    // TODO: we should be able to reduce the number of units checked here.
 | 
						|
    struct rb_mjit_unit *unit = NULL;
 | 
						|
    list_for_each(&stale_units.head, unit, unode) {
 | 
						|
        if (unit->iseq == iseq) {
 | 
						|
            unit->iseq = (rb_iseq_t *)rb_gc_location((VALUE)unit->iseq);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_update_references");
 | 
						|
}
 | 
						|
 | 
						|
// 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");
 | 
						|
    RUBY_ASSERT_ALWAYS(in_gc);
 | 
						|
    RUBY_ASSERT_ALWAYS(!in_jit);
 | 
						|
    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;
 | 
						|
    }
 | 
						|
    // Units in stale_units (list of over-speculated and invalidated code) are not referenced from
 | 
						|
    // `iseq->body->jit_unit` anymore (because new one replaces that). So we need to check them too.
 | 
						|
    // TODO: we should be able to reduce the number of units checked here.
 | 
						|
    struct rb_mjit_unit *unit = NULL;
 | 
						|
    list_for_each(&stale_units.head, unit, unode) {
 | 
						|
        if (unit->iseq == iseq) {
 | 
						|
            unit->iseq = NULL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_free_iseq");
 | 
						|
}
 | 
						|
 | 
						|
// 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, bool close_handle_p)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit *unit = 0, *next;
 | 
						|
 | 
						|
    list_for_each_safe(&list->head, unit, next, unode) {
 | 
						|
        list_del(&unit->unode);
 | 
						|
        if (!close_handle_p) unit->handle = NULL; /* Skip dlclose in free_unit() */
 | 
						|
 | 
						|
        if (list == &stale_units) { // `free_unit(unit)` crashes after GC.compact on `stale_units`
 | 
						|
            /*
 | 
						|
             * TODO: REVERT THIS BRANCH
 | 
						|
             * Debug the crash on stale_units w/ GC.compact and just use `free_unit(unit)`!!
 | 
						|
             */
 | 
						|
            if (unit->handle && dlclose(unit->handle)) {
 | 
						|
                mjit_warning("failed to close handle for u%d: %s", unit->id, dlerror());
 | 
						|
            }
 | 
						|
            clean_temp_files(unit);
 | 
						|
            free(unit);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            free_unit(unit);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    list->length = 0;
 | 
						|
}
 | 
						|
 | 
						|
// Register a new continuation with execution context `ec`. Return MJIT info about
 | 
						|
// the continuation.
 | 
						|
struct mjit_cont *
 | 
						|
mjit_cont_new(rb_execution_context_t *ec)
 | 
						|
{
 | 
						|
    struct mjit_cont *cont;
 | 
						|
 | 
						|
    // We need to use calloc instead of something like ZALLOC to avoid triggering GC here.
 | 
						|
    // When this function is called from rb_thread_alloc through rb_threadptr_root_fiber_setup,
 | 
						|
    // the thread is still being prepared and marking it causes SEGV.
 | 
						|
    cont = calloc(1, sizeof(struct mjit_cont));
 | 
						|
    if (cont == NULL)
 | 
						|
        rb_memerror();
 | 
						|
    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");
 | 
						|
 | 
						|
    free(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`. This function may be called from an MJIT worker.
 | 
						|
static void
 | 
						|
create_unit(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit *unit;
 | 
						|
 | 
						|
    unit = calloc(1, sizeof(struct rb_mjit_unit));
 | 
						|
    if (unit == NULL)
 | 
						|
        return;
 | 
						|
 | 
						|
    unit->id = current_unit_num++;
 | 
						|
    unit->iseq = (rb_iseq_t *)iseq;
 | 
						|
    iseq->body->jit_unit = unit;
 | 
						|
}
 | 
						|
 | 
						|
// Return true if given ISeq body should be compiled by MJIT
 | 
						|
static inline int
 | 
						|
mjit_target_iseq_p(struct rb_iseq_constant_body *body)
 | 
						|
{
 | 
						|
    return (body->type == ISEQ_TYPE_METHOD || body->type == ISEQ_TYPE_BLOCK)
 | 
						|
        && !body->builtin_inline_p;
 | 
						|
}
 | 
						|
 | 
						|
// If recompile_p is true, the call is initiated by mjit_recompile.
 | 
						|
// This assumes the caller holds CRITICAL_SECTION when recompile_p is true.
 | 
						|
static void
 | 
						|
mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info, bool recompile_p)
 | 
						|
{
 | 
						|
    if (!mjit_enabled || pch_status == PCH_FAILED)
 | 
						|
        return;
 | 
						|
    if (!mjit_target_iseq_p(iseq->body)) {
 | 
						|
        iseq->body->jit_func = (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC; // skip mjit_wait
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!recompile_p) {
 | 
						|
        CRITICAL_SECTION_START(3, "in add_iseq_to_process");
 | 
						|
 | 
						|
        // This prevents multiple Ractors from enqueueing the same ISeq twice.
 | 
						|
        if (rb_multi_ractor_p() && (uintptr_t)iseq->body->jit_func != NOT_ADDED_JIT_ISEQ_FUNC) {
 | 
						|
            CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    RB_DEBUG_COUNTER_INC(mjit_add_iseq_to_process);
 | 
						|
    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;
 | 
						|
    if (compile_info != NULL)
 | 
						|
        iseq->body->jit_unit->compile_info = *compile_info;
 | 
						|
    add_to_list(iseq->body->jit_unit, &unit_queue);
 | 
						|
    if (active_units.length >= mjit_opts.max_cache_size) {
 | 
						|
        unload_requests++;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!recompile_p) {
 | 
						|
        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");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Add ISEQ to be JITed in parallel with the current thread.
 | 
						|
// Unload some JIT codes if there are too many of them.
 | 
						|
void
 | 
						|
rb_mjit_add_iseq_to_process(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    mjit_add_iseq_to_process(iseq, NULL, false);
 | 
						|
}
 | 
						|
 | 
						|
// For this timeout seconds, --jit-wait will wait for JIT compilation finish.
 | 
						|
#define MJIT_WAIT_TIMEOUT_SECONDS 60
 | 
						|
 | 
						|
static void
 | 
						|
mjit_wait(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 rb_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 rb_mjit_wait_call to set jit_func");
 | 
						|
            mjit_warning("timed out to wait for JIT finish");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        CRITICAL_SECTION_START(3, "in rb_mjit_wait_call for a client wakeup");
 | 
						|
        rb_native_cond_broadcast(&mjit_worker_wakeup);
 | 
						|
        CRITICAL_SECTION_FINISH(3, "in rb_mjit_wait_call for a client wakeup");
 | 
						|
        rb_thread_wait_for(tv);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
rb_mjit_wait_call(rb_execution_context_t *ec, struct rb_iseq_constant_body *body)
 | 
						|
{
 | 
						|
    if (worker_stopped)
 | 
						|
        return Qundef;
 | 
						|
 | 
						|
    mjit_wait(body);
 | 
						|
    if ((uintptr_t)body->jit_func <= (uintptr_t)LAST_JIT_ISEQ_FUNC) {
 | 
						|
        return Qundef;
 | 
						|
    }
 | 
						|
    return body->jit_func(ec, ec->cfp);
 | 
						|
}
 | 
						|
 | 
						|
struct rb_mjit_compile_info*
 | 
						|
rb_mjit_iseq_compile_info(const struct rb_iseq_constant_body *body)
 | 
						|
{
 | 
						|
    assert(body->jit_unit != NULL);
 | 
						|
    return &body->jit_unit->compile_info;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
mjit_recompile(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    if ((uintptr_t)iseq->body->jit_func <= (uintptr_t)LAST_JIT_ISEQ_FUNC)
 | 
						|
        return;
 | 
						|
 | 
						|
    verbose(1, "JIT recompile: %s@%s:%d", RSTRING_PTR(iseq->body->location.label),
 | 
						|
            RSTRING_PTR(rb_iseq_path(iseq)), FIX2INT(iseq->body->location.first_lineno));
 | 
						|
    assert(iseq->body->jit_unit != NULL);
 | 
						|
 | 
						|
    if (UNLIKELY(mjit_opts.wait)) {
 | 
						|
        CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq");
 | 
						|
        remove_from_list(iseq->body->jit_unit, &active_units);
 | 
						|
        add_to_list(iseq->body->jit_unit, &stale_units);
 | 
						|
        mjit_add_iseq_to_process(iseq, &iseq->body->jit_unit->compile_info, true);
 | 
						|
        CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq");
 | 
						|
        mjit_wait(iseq->body);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        // Lazily move active_units to stale_units to avoid race conditions around active_units with compaction.
 | 
						|
        // Also, it's lazily moved to unit_queue as well because otherwise it won't be added to stale_units properly.
 | 
						|
        // It's good to avoid a race condition between mjit_add_iseq_to_process and mjit_compile around jit_unit as well.
 | 
						|
        CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq");
 | 
						|
        iseq->body->jit_unit->stale_p = true;
 | 
						|
        iseq->body->jit_func = (mjit_func_t)NOT_READY_JIT_ISEQ_FUNC;
 | 
						|
        pending_stale_p = true;
 | 
						|
        CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Recompile iseq, disabling send optimization
 | 
						|
void
 | 
						|
rb_mjit_recompile_send(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    rb_mjit_iseq_compile_info(iseq->body)->disable_send_cache = true;
 | 
						|
    mjit_recompile(iseq);
 | 
						|
}
 | 
						|
 | 
						|
// Recompile iseq, disabling ivar optimization
 | 
						|
void
 | 
						|
rb_mjit_recompile_ivar(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    rb_mjit_iseq_compile_info(iseq->body)->disable_ivar_cache = true;
 | 
						|
    mjit_recompile(iseq);
 | 
						|
}
 | 
						|
 | 
						|
// Recompile iseq, disabling exivar optimization
 | 
						|
void
 | 
						|
rb_mjit_recompile_exivar(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    rb_mjit_iseq_compile_info(iseq->body)->disable_exivar_cache = true;
 | 
						|
    mjit_recompile(iseq);
 | 
						|
}
 | 
						|
 | 
						|
// Recompile iseq, disabling method inlining
 | 
						|
void
 | 
						|
rb_mjit_recompile_inlining(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    rb_mjit_iseq_compile_info(iseq->body)->disable_inlining = true;
 | 
						|
    mjit_recompile(iseq);
 | 
						|
}
 | 
						|
 | 
						|
// Recompile iseq, disabling getconstant inlining
 | 
						|
void
 | 
						|
rb_mjit_recompile_const(const rb_iseq_t *iseq)
 | 
						|
{
 | 
						|
    rb_mjit_iseq_compile_info(iseq->body)->disable_const_cache = true;
 | 
						|
    mjit_recompile(iseq);
 | 
						|
}
 | 
						|
 | 
						|
extern VALUE ruby_archlibdir_path, ruby_prefix_path;
 | 
						|
 | 
						|
// Initialize header_file, pch_file, libruby_pathflag. Return true on success.
 | 
						|
static bool
 | 
						|
init_header_filename(void)
 | 
						|
{
 | 
						|
    int fd;
 | 
						|
#ifdef LOAD_RELATIVE
 | 
						|
    // Root path of the running ruby process. Equal to RbConfig::TOPDIR.
 | 
						|
    VALUE basedir_val;
 | 
						|
#endif
 | 
						|
    const char *basedir = "";
 | 
						|
    size_t baselen = 0;
 | 
						|
    char *p;
 | 
						|
#ifdef _WIN32
 | 
						|
    static const char libpathflag[] =
 | 
						|
# ifdef _MSC_VER
 | 
						|
        "-LIBPATH:"
 | 
						|
# else
 | 
						|
        "-L"
 | 
						|
# endif
 | 
						|
        ;
 | 
						|
    const size_t libpathflag_len = sizeof(libpathflag) - 1;
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef LOAD_RELATIVE
 | 
						|
    basedir_val = ruby_prefix_path;
 | 
						|
    basedir = StringValuePtr(basedir_val);
 | 
						|
    baselen = RSTRING_LEN(basedir_val);
 | 
						|
#else
 | 
						|
    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.
 | 
						|
 | 
						|
        struct stat st;
 | 
						|
        const char *hdr = dlsym(RTLD_DEFAULT, "MJIT_HEADER");
 | 
						|
        if (!hdr) {
 | 
						|
            verbose(1, "No MJIT_HEADER");
 | 
						|
        }
 | 
						|
        else if (hdr[0] != '/') {
 | 
						|
            verbose(1, "Non-absolute header file path: %s", hdr);
 | 
						|
        }
 | 
						|
        else if (stat(hdr, &st) || !S_ISREG(st.st_mode)) {
 | 
						|
            verbose(1, "Non-file header file path: %s", hdr);
 | 
						|
        }
 | 
						|
        else if ((st.st_uid != getuid()) || (st.st_mode & 022) ||
 | 
						|
                 !rb_path_check(hdr)) {
 | 
						|
            verbose(1, "Unsafe header file: uid=%ld mode=%#o %s",
 | 
						|
                    (long)st.st_uid, (unsigned)st.st_mode, hdr);
 | 
						|
            return FALSE;
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            // Do not pass PRELOADENV to child processes, on
 | 
						|
            // multi-arch environment
 | 
						|
            verbose(3, "PRELOADENV("PRELOADENV")=%s", getenv(PRELOADENV));
 | 
						|
            // assume no other PRELOADENV in test-all
 | 
						|
            unsetenv(PRELOADENV);
 | 
						|
            verbose(3, "MJIT_HEADER: %s", hdr);
 | 
						|
            header_file = ruby_strdup(hdr);
 | 
						|
            if (!header_file) return false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
#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_HEADER_INSTALL_DIR "/" 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");
 | 
						|
#else
 | 
						|
    {
 | 
						|
        static const char pch_name[] = MJIT_HEADER_INSTALL_DIR "/" 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;
 | 
						|
}
 | 
						|
 | 
						|
#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
 | 
						|
}
 | 
						|
 | 
						|
// Minimum value for JIT cache size.
 | 
						|
#define MIN_CACHE_SIZE 10
 | 
						|
// Default permitted number of units with a JIT code kept in memory.
 | 
						|
#define DEFAULT_MAX_CACHE_SIZE 10000
 | 
						|
// A default threshold used to add iseq to JIT.
 | 
						|
#define DEFAULT_MIN_CALLS_TO_ADD 10000
 | 
						|
 | 
						|
// Start MJIT worker. Return TRUE if worker is successfully started.
 | 
						|
static bool
 | 
						|
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;
 | 
						|
}
 | 
						|
 | 
						|
// There's no strndup on Windows
 | 
						|
static char*
 | 
						|
ruby_strndup(const char *str, size_t n)
 | 
						|
{
 | 
						|
    char *ret = xmalloc(n + 1);
 | 
						|
    memcpy(ret, str, n);
 | 
						|
    ret[n] = '\0';
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
// Convert "foo bar" to {"foo", "bar", NULL} array. Caller is responsible for
 | 
						|
// freeing a returned buffer and its elements.
 | 
						|
static char **
 | 
						|
split_flags(const char *flags)
 | 
						|
{
 | 
						|
    char *buf[MAXPATHLEN];
 | 
						|
    int i = 0;
 | 
						|
    char *next;
 | 
						|
    for (; flags != NULL; flags = next) {
 | 
						|
        next = strchr(flags, ' ');
 | 
						|
        if (next == NULL) {
 | 
						|
            if (strlen(flags) > 0)
 | 
						|
                buf[i++] = strdup(flags);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            if (next > flags)
 | 
						|
                buf[i++] = ruby_strndup(flags, next - flags);
 | 
						|
            next++; // skip space
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    char **ret = xmalloc(sizeof(char *) * (i + 1));
 | 
						|
    memcpy(ret, buf, sizeof(char *) * i);
 | 
						|
    ret[i] = NULL;
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
// Initialize MJIT.  Start a thread creating the precompiled header and
 | 
						|
// processing ISeqs.  The function should be called first for using MJIT.
 | 
						|
// If everything is successful, MJIT_INIT_P will be TRUE.
 | 
						|
void
 | 
						|
mjit_init(const 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_MAX_CACHE_SIZE;
 | 
						|
    if (mjit_opts.max_cache_size < MIN_CACHE_SIZE)
 | 
						|
        mjit_opts.max_cache_size = MIN_CACHE_SIZE;
 | 
						|
 | 
						|
    // 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_COMMON_ARGS[0];
 | 
						|
    verbose(2, "MJIT: CC defaults to %s", cc_path);
 | 
						|
    cc_common_args = xmalloc(sizeof(CC_COMMON_ARGS));
 | 
						|
    memcpy((void *)cc_common_args, CC_COMMON_ARGS, sizeof(CC_COMMON_ARGS));
 | 
						|
    cc_added_args = split_flags(opts->debug_flags);
 | 
						|
    xfree(opts->debug_flags);
 | 
						|
#if MJIT_CFLAGS_PIPE
 | 
						|
    // eliminate a flag incompatible with `-pipe`
 | 
						|
    for (size_t i = 0, j = 0; i < sizeof(CC_COMMON_ARGS) / sizeof(char *); i++) {
 | 
						|
        if (CC_COMMON_ARGS[i] && strncmp("-save-temps", CC_COMMON_ARGS[i], strlen("-save-temps")) == 0)
 | 
						|
            continue; // skip -save-temps flag
 | 
						|
        cc_common_args[j] = CC_COMMON_ARGS[i];
 | 
						|
        j++;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
    pch_owner_pid = getpid();
 | 
						|
 | 
						|
    // 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);
 | 
						|
 | 
						|
    // Make sure the saved_ec of the initial thread's root_fiber is scanned by mark_ec_units.
 | 
						|
    //
 | 
						|
    // rb_threadptr_root_fiber_setup for the initial thread is called before mjit_init,
 | 
						|
    // meaning mjit_cont_new is skipped for the root_fiber. Therefore we need to call
 | 
						|
    // rb_fiber_init_mjit_cont again with mjit_enabled=true to set the root_fiber's mjit_cont.
 | 
						|
    rb_fiber_init_mjit_cont(GET_EC()->fiber_ptr);
 | 
						|
 | 
						|
    // Initialize worker thread
 | 
						|
    start_worker();
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
stop_worker(void)
 | 
						|
{
 | 
						|
    rb_execution_context_t *ec = GET_EC();
 | 
						|
 | 
						|
    while (!worker_stopped) {
 | 
						|
        verbose(3, "Sending cancel signal to worker");
 | 
						|
        CRITICAL_SECTION_START(3, "in stop_worker");
 | 
						|
        stop_worker_p = true; // Setting this inside loop because RUBY_VM_CHECK_INTS may make this false.
 | 
						|
        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(bool 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 && active_units.length < mjit_opts.max_cache_size) { // inverse of condition that waits for mjit_worker_wakeup
 | 
						|
            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;
 | 
						|
}
 | 
						|
 | 
						|
// Skip calling `clean_temp_files` for units which currently exist in the list.
 | 
						|
static void
 | 
						|
skip_cleaning_object_files(struct rb_mjit_unit_list *list)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit *unit = NULL, *next;
 | 
						|
 | 
						|
    // No mutex for list, assuming MJIT worker does not exist yet since it's immediately after fork.
 | 
						|
    list_for_each_safe(&list->head, unit, next, unode) {
 | 
						|
#if defined(_WIN32) // mswin doesn't reach here either. This is for MinGW.
 | 
						|
        if (unit->so_file) unit->so_file = NULL;
 | 
						|
#endif
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// This is called after fork initiated by Ruby's method to launch MJIT worker thread
 | 
						|
// for child Ruby process.
 | 
						|
//
 | 
						|
// In multi-process Ruby applications, child Ruby processes do most of the jobs.
 | 
						|
// Thus we want child Ruby processes to enqueue ISeqs to MJIT worker's queue and
 | 
						|
// call the JIT-ed code.
 | 
						|
//
 | 
						|
// But unfortunately current MJIT-generated code is process-specific. After the fork,
 | 
						|
// JIT-ed code created by parent Ruby process cannot be used in child Ruby process
 | 
						|
// because the code could rely on inline cache values (ivar's IC, send's CC) which
 | 
						|
// may vary between processes after fork or embed some process-specific addresses.
 | 
						|
//
 | 
						|
// So child Ruby process can't request parent process to JIT an ISeq and use the code.
 | 
						|
// Instead of that, MJIT worker thread is created for all child Ruby processes, even
 | 
						|
// while child processes would end up with compiling the same ISeqs.
 | 
						|
void
 | 
						|
mjit_child_after_fork(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
 | 
						|
    /* Let parent process delete the already-compiled object files.
 | 
						|
       This must be done before starting MJIT worker on child process. */
 | 
						|
    skip_cleaning_object_files(&active_units);
 | 
						|
 | 
						|
    /* MJIT worker thread is not inherited on fork. Start it for this child process. */
 | 
						|
    start_worker();
 | 
						|
}
 | 
						|
 | 
						|
// Edit 0 to 1 to enable this feature for investigating hot methods
 | 
						|
#define MJIT_COUNTER 0
 | 
						|
#if MJIT_COUNTER
 | 
						|
static void
 | 
						|
mjit_dump_total_calls(void)
 | 
						|
{
 | 
						|
    struct rb_mjit_unit *unit;
 | 
						|
    fprintf(stderr, "[MJIT_COUNTER] total_calls of active_units:\n");
 | 
						|
    list_for_each(&active_units.head, unit, unode) {
 | 
						|
        const rb_iseq_t *iseq = unit->iseq;
 | 
						|
        fprintf(stderr, "%8ld: %s@%s:%d\n", iseq->body->total_calls, RSTRING_PTR(iseq->body->location.label),
 | 
						|
                RSTRING_PTR(rb_iseq_path(iseq)), FIX2INT(iseq->body->location.first_lineno));
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
// Finish the threads processing units and creating PCH, finalize
 | 
						|
// and free MJIT data.  It should be called last during MJIT
 | 
						|
// life.
 | 
						|
//
 | 
						|
// If close_handle_p is true, it calls dlclose() for JIT-ed code. So it should be false
 | 
						|
// if the code can still be on stack. ...But it means to leak JIT-ed handle forever (FIXME).
 | 
						|
void
 | 
						|
mjit_finish(bool close_handle_p)
 | 
						|
{
 | 
						|
    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);
 | 
						|
 | 
						|
#if MJIT_COUNTER
 | 
						|
    mjit_dump_total_calls();
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef _MSC_VER // mswin has prebuilt precompiled header
 | 
						|
    if (!mjit_opts.save_temps && getpid() == pch_owner_pid)
 | 
						|
        remove_file(pch_file);
 | 
						|
 | 
						|
    xfree(header_file); header_file = NULL;
 | 
						|
#endif
 | 
						|
    xfree((void *)cc_common_args); cc_common_args = NULL;
 | 
						|
    for (char **flag = cc_added_args; *flag != NULL; flag++)
 | 
						|
        xfree(*flag);
 | 
						|
    xfree((void *)cc_added_args); cc_added_args = NULL;
 | 
						|
    xfree(tmp_dir); tmp_dir = NULL;
 | 
						|
    xfree(pch_file); pch_file = NULL;
 | 
						|
 | 
						|
    mjit_call_p = false;
 | 
						|
    free_list(&unit_queue, close_handle_p);
 | 
						|
    free_list(&active_units, close_handle_p);
 | 
						|
    free_list(&compact_units, close_handle_p);
 | 
						|
    free_list(&stale_units, close_handle_p);
 | 
						|
    finish_conts();
 | 
						|
 | 
						|
    mjit_enabled = false;
 | 
						|
    verbose(1, "Successful MJIT finish");
 | 
						|
}
 | 
						|
 | 
						|
// Called by rb_vm_mark().
 | 
						|
//
 | 
						|
// Mark an ISeq being compiled to prevent its CCs from being GC-ed, which
 | 
						|
// an MJIT worker may concurrently see.
 | 
						|
//
 | 
						|
// Also mark active_units so that we do not GC ISeq which may still be
 | 
						|
// referred to by mjit_recompile() or compact_all_jit_code().
 | 
						|
void
 | 
						|
mjit_mark(void)
 | 
						|
{
 | 
						|
    if (!mjit_enabled)
 | 
						|
        return;
 | 
						|
    RUBY_MARK_ENTER("mjit");
 | 
						|
 | 
						|
    // We need to release a lock when calling rb_gc_mark to avoid doubly acquiring
 | 
						|
    // a lock by by mjit_gc_start_hook inside rb_gc_mark.
 | 
						|
    //
 | 
						|
    // Because an MJIT worker may modify active_units anytime, we need to convert
 | 
						|
    // the linked list to an array to safely loop its ISeqs without keeping a lock.
 | 
						|
    CRITICAL_SECTION_START(4, "mjit_mark");
 | 
						|
    int length = 0;
 | 
						|
    if (compiling_iseqs != NULL) {
 | 
						|
        while (compiling_iseqs[length]) length++;
 | 
						|
    }
 | 
						|
    length += active_units.length;
 | 
						|
    const rb_iseq_t **iseqs = ALLOCA_N(const rb_iseq_t *, length);
 | 
						|
 | 
						|
    struct rb_mjit_unit *unit = NULL;
 | 
						|
    int i = 0;
 | 
						|
    if (compiling_iseqs != NULL) {
 | 
						|
        while (compiling_iseqs[i]) {
 | 
						|
            iseqs[i] = compiling_iseqs[i];
 | 
						|
            i++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    list_for_each(&active_units.head, unit, unode) {
 | 
						|
        iseqs[i] = unit->iseq;
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
    assert(i == length);
 | 
						|
    CRITICAL_SECTION_FINISH(4, "mjit_mark");
 | 
						|
 | 
						|
    for (i = 0; i < length; i++) {
 | 
						|
        if (iseqs[i] == NULL) // ISeq is GC-ed
 | 
						|
            continue;
 | 
						|
        rb_gc_mark((VALUE)iseqs[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    RUBY_MARK_LEAVE("mjit");
 | 
						|
}
 | 
						|
 | 
						|
// Called by rb_iseq_mark() to mark cc_entries captured for MJIT
 | 
						|
void
 | 
						|
mjit_mark_cc_entries(const struct rb_iseq_constant_body *const body)
 | 
						|
{
 | 
						|
    const struct rb_callcache **cc_entries;
 | 
						|
    if (body->jit_unit && (cc_entries = body->jit_unit->cc_entries) != NULL) {
 | 
						|
        // It must be `body->jit_unit->cc_entries_size` instead of `body->ci_size` to mark children's cc_entries
 | 
						|
        for (unsigned int i = 0; i < body->jit_unit->cc_entries_size; i++) {
 | 
						|
            const struct rb_callcache *cc = cc_entries[i];
 | 
						|
            if (cc != NULL && vm_cc_markable(cc)) {
 | 
						|
                // Pin `cc` and `cc->cme` against GC.compact as their addresses may be written in JIT-ed code.
 | 
						|
                rb_gc_mark((VALUE)cc);
 | 
						|
                rb_gc_mark((VALUE)vm_cc_cme(cc));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#endif // USE_MJIT
 |