diff options
author | Takashi Kokubun <[email protected]> | 2023-10-19 10:54:35 -0700 |
---|---|---|
committer | <[email protected]> | 2023-10-19 10:54:35 -0700 |
commit | 6beb09c2c99a2575027bdbc60a6fbb099416f74d () | |
tree | dc0033f88b48f9cfd7ecaa67ca055a09a4437f96 | |
parent | 62e340251b577e3a9d11ac5c2b75ad49b8036294 (diff) |
YJIT: Add RubyVM::YJIT.enable (#8705)
-rw-r--r-- | cont.c | 29 | ||||
-rw-r--r-- | ruby.c | 4 | ||||
-rw-r--r-- | test/ruby/test_yjit.rb | 37 | ||||
-rw-r--r-- | version.c | 26 | ||||
-rw-r--r-- | vm.c | 7 | ||||
-rw-r--r-- | vm_method.c | 2 | ||||
-rw-r--r-- | yjit.c | 11 | ||||
-rw-r--r-- | yjit.h | 6 | ||||
-rw-r--r-- | yjit.rb | 8 | ||||
-rw-r--r-- | yjit/src/options.rs | 12 | ||||
-rw-r--r-- | yjit/src/yjit.rs | 76 |
11 files changed, 110 insertions, 108 deletions
@@ -71,8 +71,6 @@ static VALUE rb_cFiberPool; #define FIBER_POOL_ALLOCATION_FREE #endif -#define jit_cont_enabled (rb_rjit_enabled || rb_yjit_enabled_p()) - enum context_type { CONTINUATION_CONTEXT = 0, FIBER_CONTEXT = 1 @@ -1062,10 +1060,8 @@ cont_free(void *ptr) RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr); - if (jit_cont_enabled) { - VM_ASSERT(cont->jit_cont != NULL); - jit_cont_free(cont->jit_cont); - } /* free rb_cont_t or rb_fiber_t */ ruby_xfree(ptr); RUBY_FREE_LEAVE("cont"); @@ -1311,9 +1307,6 @@ rb_jit_cont_each_iseq(rb_iseq_callback callback, void *data) void rb_jit_cont_finish(void) { - if (!jit_cont_enabled) - return; - struct rb_jit_cont *cont, *next; for (cont = first_jit_cont; cont != NULL; cont = next) { next = cont->next; @@ -1326,9 +1319,8 @@ static void cont_init_jit_cont(rb_context_t *cont) { VM_ASSERT(cont->jit_cont == NULL); - if (jit_cont_enabled) { - cont->jit_cont = jit_cont_new(&(cont->saved_ec)); - } } struct rb_execution_context_struct * @@ -1375,15 +1367,11 @@ rb_fiberptr_blocking(struct rb_fiber_struct *fiber) return fiber->blocking; } -// Start working with jit_cont. void rb_jit_cont_init(void) { - if (!jit_cont_enabled) - return; - rb_native_mutex_initialize(&jit_cont_lock); - cont_init_jit_cont(&GET_EC()->fiber_ptr->cont); } #if 0 @@ -2564,10 +2552,9 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) fiber->killed = 0; fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ th->ec = &fiber->cont.saved_ec; - // When rb_threadptr_root_fiber_setup is called for the first time, rb_rjit_enabled and - // rb_yjit_enabled_p() are still false. So this does nothing and rb_jit_cont_init() that is - // called later will take care of it. However, you still have to call cont_init_jit_cont() - // here for other Ractors, which are not initialized by rb_jit_cont_init(). cont_init_jit_cont(&fiber->cont); } @@ -1796,10 +1796,6 @@ ruby_opt_init(ruby_cmdline_options_t *opt) if (opt->yjit) rb_yjit_init(); #endif - // rb_threadptr_root_fiber_setup for the initial thread is called before rb_yjit_enabled_p() - // or rjit_enabled becomes true, meaning jit_cont_new is skipped for the initial root fiber. - // Therefore we need to call this again here to set the initial root fiber's jit_cont. - rb_jit_cont_init(); // must be after rjit_enabled = true and rb_yjit_init() ruby_set_script_name(opt->script_name); require_libraries(&opt->req_list); @@ -51,27 +51,36 @@ class TestYJIT < Test::Unit::TestCase #assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/) end - def test_starting_paused - program = <<~RUBY def not_compiled = nil def will_compile = nil - def compiled_counts = RubyVM::YJIT.runtime_stats[:compiled_iseq_count] - counts = [] not_compiled - counts << compiled_counts - RubyVM::YJIT.resume will_compile - counts << compiled_counts - - if counts[0] == 0 && counts[1] > 0 - p :ok - end RUBY - assert_in_out_err(%w[--yjit-pause --yjit-stats --yjit-call-threshold=1], program, success: true) do |stdout, stderr| - assert_equal([":ok"], stdout) - end end def test_yjit_stats_and_v_no_error @@ -141,8 +141,8 @@ Init_version(void) int ruby_mn_threads_enabled; -void -Init_ruby_description(ruby_cmdline_options_t *opt) { static char desc[ sizeof(ruby_description) @@ -150,11 +150,6 @@ Init_ruby_description(ruby_cmdline_options_t *opt) + rb_strlen_lit(" +MN") ]; - const char *const jit_opt = - RJIT_OPTS_ON ? " +RJIT" : - YJIT_OPTS_ON ? YJIT_DESCRIPTION : - ""; - const char *const threads_opt = ruby_mn_threads_enabled ? " +MN" : ""; int n = snprintf(desc, sizeof(desc), @@ -177,6 +172,23 @@ Init_ruby_description(ruby_cmdline_options_t *opt) } void ruby_show_version(void) { puts(rb_dynamic_description); @@ -426,15 +426,14 @@ jit_compile(rb_execution_context_t *ec) { const rb_iseq_t *iseq = ec->cfp->iseq; struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); - bool yjit_enabled = rb_yjit_compile_new_iseqs(); - if (!(yjit_enabled || rb_rjit_call_p)) { return NULL; } // Increment the ISEQ's call counter and trigger JIT compilation if not compiled if (body->jit_entry == NULL) { body->jit_entry_calls++; - if (yjit_enabled) { if (rb_yjit_threshold_hit(iseq, body->jit_entry_calls)) { rb_yjit_compile_iseq(iseq, ec, false); } @@ -476,7 +475,7 @@ jit_compile_exception(rb_execution_context_t *ec) { const rb_iseq_t *iseq = ec->cfp->iseq; struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); - if (!rb_yjit_compile_new_iseqs()) { return NULL; } @@ -200,7 +200,7 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) struct rb_id_table *cm_tbl; if ((cm_tbl = RCLASS_CALLABLE_M_TBL(klass)) != NULL) { VALUE cme; - if (rb_yjit_enabled_p() && rb_id_table_lookup(cm_tbl, mid, &cme)) { rb_yjit_cme_invalidate((rb_callable_method_entry_t *)cme); } if (rb_rjit_enabled && rb_id_table_lookup(cm_tbl, mid, &cme)) { @@ -1171,20 +1171,15 @@ VALUE rb_yjit_insns_compiled(rb_execution_context_t *ec, VALUE self, VALUE iseq) VALUE rb_yjit_code_gc(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_simulate_oom_bang(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_get_exit_locations(rb_execution_context_t *ec, VALUE self); -VALUE rb_yjit_resume(rb_execution_context_t *ec, VALUE self); // Preprocessed yjit.rb generated during build #include "yjit.rbinc" -// Can raise RuntimeError void -rb_yjit_init(void) { - // Call the Rust initialization code - void rb_yjit_init_rust(void); - rb_yjit_init_rust(); - - // Initialize the GC hooks. Do this second as some code depend on Rust initialization. struct yjit_root_struct *root; VALUE yjit_root = TypedData_Make_Struct(0, struct yjit_root_struct, &yjit_root_type, root); rb_gc_register_mark_object(yjit_root); @@ -28,9 +28,8 @@ extern uint64_t rb_yjit_call_threshold; extern uint64_t rb_yjit_cold_threshold; extern uint64_t rb_yjit_live_iseq_count; void rb_yjit_incr_counter(const char *counter_name); -bool rb_yjit_enabled_p(void); -bool rb_yjit_compile_new_iseqs(void); void rb_yjit_invalidate_all_method_lookup_assumptions(void); void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme); void rb_yjit_collect_binding_alloc(void); @@ -51,9 +50,8 @@ void rb_yjit_show_usage(int help, int highlight, unsigned int width, int columns // !USE_YJIT // In these builds, YJIT could never be turned on. Provide dummy implementations. static inline void rb_yjit_incr_counter(const char *counter_name) {} -static inline bool rb_yjit_enabled_p(void) { return false; } -static inline bool rb_yjit_compile_new_iseqs(void) { return false; } static inline void rb_yjit_invalidate_all_method_lookup_assumptions(void) {} static inline void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme) {} static inline void rb_yjit_collect_binding_alloc(void) {} @@ -11,7 +11,7 @@ module RubyVM::YJIT # Check if YJIT is enabled def self.enabled? - Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())' end # Check if --yjit-stats is used. @@ -29,9 +29,9 @@ module RubyVM::YJIT Primitive.rb_yjit_reset_stats_bang end - # Resume YJIT compilation after paused on startup with --yjit-pause - def self.resume - Primitive.rb_yjit_resume end # If --yjit-trace-exits is enabled parse the hashes from @@ -47,9 +47,9 @@ pub struct Options { // how often to sample exit trace data pub trace_exits_sample_rate: usize, - // Whether to start YJIT in paused state (initialize YJIT but don't - // compile anything) - pub pause: bool, /// Dump compiled and executed instructions for debugging pub dump_insns: bool, @@ -81,7 +81,7 @@ pub static mut OPTIONS: Options = Options { gen_trace_exits: false, print_stats: true, trace_exits_sample_rate: 0, - pause: false, dump_insns: false, dump_disasm: None, verify_ctx: false, @@ -186,8 +186,8 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { } }, - ("pause", "") => unsafe { - OPTIONS.pause = true; }, ("temp-regs", _) => match opt_val.parse() { @@ -8,16 +8,12 @@ use crate::stats::incr_counter; use crate::stats::with_compile_time; use std::os::raw; -use std::sync::atomic::{AtomicBool, Ordering}; -/// For tracking whether the user enabled YJIT through command line arguments or environment -/// variables. AtomicBool to avoid `unsafe`. On x86 it compiles to simple movs. -/// See <https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html> -/// See [rb_yjit_enabled_p] -static YJIT_ENABLED: AtomicBool = AtomicBool::new(false); - -/// When false, we don't compile new iseqs, but might still service existing branch stubs. -static COMPILE_NEW_ISEQS: AtomicBool = AtomicBool::new(false); /// Parse one command-line option. /// This is called from ruby.c @@ -26,29 +22,22 @@ pub extern "C" fn rb_yjit_parse_option(str_ptr: *const raw::c_char) -> bool { return parse_option(str_ptr).is_some(); } -/// Is YJIT on? The interpreter uses this function to decide whether to increment -/// ISEQ call counters. See jit_exec(). -/// This is used frequently since it's used on every method call in the interpreter. -#[no_mangle] -pub extern "C" fn rb_yjit_enabled_p() -> raw::c_int { - // Note that we might want to call this function from signal handlers so - // might need to ensure signal-safety(7). - YJIT_ENABLED.load(Ordering::Acquire).into() -} - -#[no_mangle] -pub extern "C" fn rb_yjit_compile_new_iseqs() -> bool { - COMPILE_NEW_ISEQS.load(Ordering::Acquire).into() -} - /// Like rb_yjit_enabled_p, but for Rust code. pub fn yjit_enabled_p() -> bool { - YJIT_ENABLED.load(Ordering::Acquire) } /// This function is called from C code #[no_mangle] -pub extern "C" fn rb_yjit_init_rust() { // TODO: need to make sure that command-line options have been // initialized by CRuby @@ -63,13 +52,12 @@ pub extern "C" fn rb_yjit_init_rust() { rb_bug_panic_hook(); // YJIT enabled and initialized successfully - YJIT_ENABLED.store(true, Ordering::Release); - - COMPILE_NEW_ISEQS.store(!get_option!(pause), Ordering::Release); }); if let Err(_) = result { - println!("YJIT: rb_yjit_init_rust() panicked. Aborting."); std::process::abort(); } @@ -79,6 +67,12 @@ pub extern "C" fn rb_yjit_init_rust() { let _ = std::fs::remove_file(&perf_map); println!("YJIT perf map: {perf_map}"); } } /// At the moment, we abort in all cases we panic. @@ -161,13 +155,25 @@ pub extern "C" fn rb_yjit_code_gc(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { Qnil } #[no_mangle] -pub extern "C" fn rb_yjit_resume(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if yjit_enabled_p() { - COMPILE_NEW_ISEQS.store(true, Ordering::Release); - } - Qnil } /// Simulate a situation where we are out of executable memory |