YJIT: Always define method codegen table at boot (#8807)

This commit is contained in:
Takashi Kokubun 2023-11-02 09:15:48 -07:00 committed by GitHub
parent 5f130e2111
commit ad4f973ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 103 deletions

3
ruby.c
View File

@ -1793,8 +1793,7 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
rb_rjit_init(&opt->rjit);
#endif
#if USE_YJIT
if (opt->yjit)
rb_yjit_init();
rb_yjit_init(opt->yjit);
#endif
ruby_set_script_name(opt->script_name);

View File

@ -85,6 +85,16 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
def test_yjit_enable_with_monkey_patch
assert_separately(%w[--yjit-disable], <<~RUBY)
# This lets rb_method_entry_at(rb_mKernel, ...) return NULL
Kernel.prepend(Module.new)
# This must not crash with "undefined optimized method!"
RubyVM::YJIT.enable
RUBY
end
def test_yjit_stats_and_v_no_error
_stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true)
refute_includes(stderr, "NoMethodError")

4
yjit.h
View File

@ -35,7 +35,7 @@ void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme);
void rb_yjit_collect_binding_alloc(void);
void rb_yjit_collect_binding_set(void);
void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception);
void rb_yjit_init(void);
void rb_yjit_init(bool yjit_enabled);
void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
void rb_yjit_constant_state_changed(ID id);
void rb_yjit_iseq_mark(void *payload);
@ -57,7 +57,7 @@ static inline void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme) {}
static inline void rb_yjit_collect_binding_alloc(void) {}
static inline void rb_yjit_collect_binding_set(void) {}
static inline void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
static inline void rb_yjit_init(void) {}
static inline void rb_yjit_init(bool yjit_enabled) {}
static inline void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
static inline void rb_yjit_constant_state_changed(ID id) {}
static inline void rb_yjit_iseq_mark(void *payload) {}

View File

@ -5141,8 +5141,13 @@ fn jit_thread_s_current(
// Check if we know how to codegen for a particular cfunc method
fn lookup_cfunc_codegen(def: *const rb_method_definition_t) -> Option<MethodGenFn> {
let method_serial = unsafe { get_def_method_serial(def) };
let table = unsafe { METHOD_CODEGEN_TABLE.as_ref().unwrap() };
CodegenGlobals::look_up_codegen_method(method_serial)
let option_ref = table.get(&method_serial);
match option_ref {
None => None,
Some(&mgf) => Some(mgf), // Deref
}
}
// Is anyone listening for :c_call and :c_return event currently?
@ -8676,6 +8681,83 @@ type MethodGenFn = fn(
known_recv_class: *const VALUE,
) -> bool;
/// Methods for generating code for hardcoded (usually C) methods
static mut METHOD_CODEGEN_TABLE: Option<HashMap<usize, MethodGenFn>> = None;
/// Register codegen functions for some Ruby core methods
pub fn yjit_reg_method_codegen_fns() {
unsafe {
assert!(METHOD_CODEGEN_TABLE.is_none());
METHOD_CODEGEN_TABLE = Some(HashMap::default());
// Specialization for C methods. See yjit_reg_method() for details.
yjit_reg_method(rb_cBasicObject, "!", jit_rb_obj_not);
yjit_reg_method(rb_cNilClass, "nil?", jit_rb_true);
yjit_reg_method(rb_mKernel, "nil?", jit_rb_false);
yjit_reg_method(rb_mKernel, "is_a?", jit_rb_kernel_is_a);
yjit_reg_method(rb_mKernel, "kind_of?", jit_rb_kernel_is_a);
yjit_reg_method(rb_mKernel, "instance_of?", jit_rb_kernel_instance_of);
yjit_reg_method(rb_cBasicObject, "==", jit_rb_obj_equal);
yjit_reg_method(rb_cBasicObject, "equal?", jit_rb_obj_equal);
yjit_reg_method(rb_cBasicObject, "!=", jit_rb_obj_not_equal);
yjit_reg_method(rb_mKernel, "eql?", jit_rb_obj_equal);
yjit_reg_method(rb_cModule, "==", jit_rb_obj_equal);
yjit_reg_method(rb_cModule, "===", jit_rb_mod_eqq);
yjit_reg_method(rb_cSymbol, "==", jit_rb_obj_equal);
yjit_reg_method(rb_cSymbol, "===", jit_rb_obj_equal);
yjit_reg_method(rb_cInteger, "==", jit_rb_int_equal);
yjit_reg_method(rb_cInteger, "===", jit_rb_int_equal);
yjit_reg_method(rb_cInteger, "/", jit_rb_int_div);
yjit_reg_method(rb_cInteger, "<<", jit_rb_int_lshift);
yjit_reg_method(rb_cInteger, "[]", jit_rb_int_aref);
yjit_reg_method(rb_cString, "empty?", jit_rb_str_empty_p);
yjit_reg_method(rb_cString, "to_s", jit_rb_str_to_s);
yjit_reg_method(rb_cString, "to_str", jit_rb_str_to_s);
yjit_reg_method(rb_cString, "bytesize", jit_rb_str_bytesize);
yjit_reg_method(rb_cString, "getbyte", jit_rb_str_getbyte);
yjit_reg_method(rb_cString, "<<", jit_rb_str_concat);
yjit_reg_method(rb_cString, "+@", jit_rb_str_uplus);
yjit_reg_method(rb_cArray, "empty?", jit_rb_ary_empty_p);
yjit_reg_method(rb_cArray, "<<", jit_rb_ary_push);
yjit_reg_method(rb_mKernel, "respond_to?", jit_obj_respond_to);
yjit_reg_method(rb_mKernel, "block_given?", jit_rb_f_block_given_p);
yjit_reg_method(rb_singleton_class(rb_cThread), "current", jit_thread_s_current);
}
}
// Register a specialized codegen function for a particular method. Note that
// the if the function returns true, the code it generates runs without a
// control frame and without interrupt checks. To avoid creating observable
// behavior changes, the codegen function should only target simple code paths
// that do not allocate and do not make method calls.
fn yjit_reg_method(klass: VALUE, mid_str: &str, gen_fn: MethodGenFn) {
let id_string = std::ffi::CString::new(mid_str).expect("couldn't convert to CString!");
let mid = unsafe { rb_intern(id_string.as_ptr()) };
let me = unsafe { rb_method_entry_at(klass, mid) };
if me.is_null() {
panic!("undefined optimized method!: {mid_str}");
}
// For now, only cfuncs are supported
//RUBY_ASSERT(me && me->def);
//RUBY_ASSERT(me->def->type == VM_METHOD_TYPE_CFUNC);
let method_serial = unsafe {
let def = (*me).def;
get_def_method_serial(def)
};
unsafe { METHOD_CODEGEN_TABLE.as_mut().unwrap().insert(method_serial, gen_fn); }
}
/// Global state needed for code generation
pub struct CodegenGlobals {
/// Inline code block (fast path)
@ -8706,9 +8788,6 @@ pub struct CodegenGlobals {
/// For implementing global code invalidation
global_inval_patches: Vec<CodepagePatch>,
// Methods for generating code for hardcoded (usually C) methods
method_codegen_table: HashMap<usize, MethodGenFn>,
/// Page indexes for outlined code that are not associated to any ISEQ.
ocb_pages: Vec<usize>,
}
@ -8792,7 +8871,7 @@ impl CodegenGlobals {
cb.mark_all_executable();
ocb.unwrap().mark_all_executable();
let mut codegen_globals = CodegenGlobals {
let codegen_globals = CodegenGlobals {
inline_cb: cb,
outlined_cb: ocb,
leave_exit_code,
@ -8802,97 +8881,15 @@ impl CodegenGlobals {
branch_stub_hit_trampoline,
entry_stub_hit_trampoline,
global_inval_patches: Vec::new(),
method_codegen_table: HashMap::new(),
ocb_pages,
};
// Register the method codegen functions
codegen_globals.reg_method_codegen_fns();
// Initialize the codegen globals instance
unsafe {
CODEGEN_GLOBALS = Some(codegen_globals);
}
}
// Register a specialized codegen function for a particular method. Note that
// the if the function returns true, the code it generates runs without a
// control frame and without interrupt checks. To avoid creating observable
// behavior changes, the codegen function should only target simple code paths
// that do not allocate and do not make method calls.
fn yjit_reg_method(&mut self, klass: VALUE, mid_str: &str, gen_fn: MethodGenFn) {
let id_string = std::ffi::CString::new(mid_str).expect("couldn't convert to CString!");
let mid = unsafe { rb_intern(id_string.as_ptr()) };
let me = unsafe { rb_method_entry_at(klass, mid) };
if me.is_null() {
panic!("undefined optimized method!");
}
// For now, only cfuncs are supported
//RUBY_ASSERT(me && me->def);
//RUBY_ASSERT(me->def->type == VM_METHOD_TYPE_CFUNC);
let method_serial = unsafe {
let def = (*me).def;
get_def_method_serial(def)
};
self.method_codegen_table.insert(method_serial, gen_fn);
}
/// Register codegen functions for some Ruby core methods
fn reg_method_codegen_fns(&mut self) {
unsafe {
// Specialization for C methods. See yjit_reg_method() for details.
self.yjit_reg_method(rb_cBasicObject, "!", jit_rb_obj_not);
self.yjit_reg_method(rb_cNilClass, "nil?", jit_rb_true);
self.yjit_reg_method(rb_mKernel, "nil?", jit_rb_false);
self.yjit_reg_method(rb_mKernel, "is_a?", jit_rb_kernel_is_a);
self.yjit_reg_method(rb_mKernel, "kind_of?", jit_rb_kernel_is_a);
self.yjit_reg_method(rb_mKernel, "instance_of?", jit_rb_kernel_instance_of);
self.yjit_reg_method(rb_cBasicObject, "==", jit_rb_obj_equal);
self.yjit_reg_method(rb_cBasicObject, "equal?", jit_rb_obj_equal);
self.yjit_reg_method(rb_cBasicObject, "!=", jit_rb_obj_not_equal);
self.yjit_reg_method(rb_mKernel, "eql?", jit_rb_obj_equal);
self.yjit_reg_method(rb_cModule, "==", jit_rb_obj_equal);
self.yjit_reg_method(rb_cModule, "===", jit_rb_mod_eqq);
self.yjit_reg_method(rb_cSymbol, "==", jit_rb_obj_equal);
self.yjit_reg_method(rb_cSymbol, "===", jit_rb_obj_equal);
self.yjit_reg_method(rb_cInteger, "==", jit_rb_int_equal);
self.yjit_reg_method(rb_cInteger, "===", jit_rb_int_equal);
self.yjit_reg_method(rb_cInteger, "/", jit_rb_int_div);
self.yjit_reg_method(rb_cInteger, "<<", jit_rb_int_lshift);
self.yjit_reg_method(rb_cInteger, "[]", jit_rb_int_aref);
// rb_str_to_s() methods in string.c
self.yjit_reg_method(rb_cString, "empty?", jit_rb_str_empty_p);
self.yjit_reg_method(rb_cString, "to_s", jit_rb_str_to_s);
self.yjit_reg_method(rb_cString, "to_str", jit_rb_str_to_s);
self.yjit_reg_method(rb_cString, "bytesize", jit_rb_str_bytesize);
self.yjit_reg_method(rb_cString, "getbyte", jit_rb_str_getbyte);
self.yjit_reg_method(rb_cString, "<<", jit_rb_str_concat);
self.yjit_reg_method(rb_cString, "+@", jit_rb_str_uplus);
// rb_ary_empty_p() method in array.c
self.yjit_reg_method(rb_cArray, "empty?", jit_rb_ary_empty_p);
self.yjit_reg_method(rb_cArray, "<<", jit_rb_ary_push);
self.yjit_reg_method(rb_mKernel, "respond_to?", jit_obj_respond_to);
self.yjit_reg_method(rb_mKernel, "block_given?", jit_rb_f_block_given_p);
// Thread.current
self.yjit_reg_method(
rb_singleton_class(rb_cThread),
"current",
jit_thread_s_current,
);
}
}
/// Get a mutable reference to the codegen globals instance
pub fn get_instance() -> &'static mut CodegenGlobals {
unsafe { CODEGEN_GLOBALS.as_mut().unwrap() }
@ -8952,16 +8949,6 @@ impl CodegenGlobals {
CodegenGlobals::get_instance().entry_stub_hit_trampoline
}
pub fn look_up_codegen_method(method_serial: usize) -> Option<MethodGenFn> {
let table = &CodegenGlobals::get_instance().method_codegen_table;
let option_ref = table.get(&method_serial);
match option_ref {
None => None,
Some(&mgf) => Some(mgf), // Deref
}
}
pub fn get_ocb_pages() -> &'static Vec<usize> {
&CodegenGlobals::get_instance().ocb_pages
}

View File

@ -29,9 +29,12 @@ pub fn yjit_enabled_p() -> bool {
/// This function is called from C code
#[no_mangle]
pub extern "C" fn rb_yjit_init() {
pub extern "C" fn rb_yjit_init(yjit_enabled: bool) {
// Register the method codegen functions. This must be done at boot.
yjit_reg_method_codegen_fns();
// If --yjit-disable, yjit_init() will not be called until RubyVM::YJIT.enable.
if !get_option!(disable) {
if yjit_enabled && !get_option!(disable) {
yjit_init();
}
}