Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion ruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,16 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
GET_VM()->running = 1;
memset(ruby_vm_redefined_flag, 0, sizeof(ruby_vm_redefined_flag));

// Register JIT-optimized builtin CMEs before the prelude, which may
// redefine core methods (e.g. Kernel.prepend via bundler/setup).
#if USE_YJIT
rb_yjit_init_builtin_cmes();
#endif
#if USE_ZJIT
extern void rb_zjit_init_builtin_cmes(void);
rb_zjit_init_builtin_cmes();
#endif

ruby_init_prelude();

/* Initialize the main box after loading libraries (including rubygems)
Expand All @@ -1827,7 +1837,7 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
rb_initialize_main_box();
rb_box_init_done();

// Initialize JITs after ruby_init_prelude() because JITing prelude is typically not optimal.
// Enable JITs after ruby_init_prelude() to avoid JITing prelude code.
#if USE_YJIT
rb_yjit_init(opt->yjit);
#endif
Expand Down
11 changes: 11 additions & 0 deletions test/ruby/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,17 @@ def test_yjit_dump_insns
assert_not_predicate(status, :signaled?)
end

def test_yjit_prelude_kernel_prepend
# Simulate what bundler/setup can do: prepend a module to Kernel during
# the prelude via the BUNDLER_SETUP mechanism in rubygems.rb:
# require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)
Tempfile.create(["kernel_prepend", ".rb"]) do |f|
f.write("Kernel.prepend(Module.new)\n")
f.flush
assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--yjit"], "", ignore_stderr: true)
end
end

private

def code_gc_helpers
Expand Down
11 changes: 11 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ def test_zjit_disable
RUBY
end

def test_zjit_prelude_kernel_prepend
# Simulate what bundler/setup can do: prepend a module to Kernel during
# the prelude via the BUNDLER_SETUP mechanism in rubygems.rb:
# require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)
Tempfile.create(["kernel_prepend", ".rb"]) do |f|
f.write("Kernel.prepend(Module.new)\n")
f.flush
assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--zjit"], "", ignore_stderr: true)
end
end

def test_zjit_enable_respects_existing_options
assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY)
refute_predicate RubyVM::ZJIT, :enabled?
Expand Down
2 changes: 2 additions & 0 deletions yjit.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +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_builtin_cmes(void);
void rb_yjit_init(bool yjit_enabled);
void rb_yjit_free_at_exit(void);
void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
Expand Down Expand Up @@ -63,6 +64,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_builtin_cmes(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) {}
Expand Down
11 changes: 8 additions & 3 deletions yjit/src/yjit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ pub fn yjit_enabled_p() -> bool {
unsafe { rb_yjit_enabled_p }
}

/// This function is called from C code
/// Register specialized codegen for builtin C method entries.
/// Must be called at boot before ruby_init_prelude() since the prelude
/// could redefine core methods (e.g. Kernel.prepend via bundler).
#[no_mangle]
pub extern "C" fn rb_yjit_init(yjit_enabled: bool) {
// Register the method codegen functions. This must be done at boot.
pub extern "C" fn rb_yjit_init_builtin_cmes() {
yjit_reg_method_codegen_fns();
}

/// This function is called from C code
#[no_mangle]
pub extern "C" fn rb_yjit_init(yjit_enabled: bool) {
// If --yjit-disable, yjit_init() will not be called until RubyVM::YJIT.enable.
if yjit_enabled {
yjit_init();
Expand Down
15 changes: 9 additions & 6 deletions zjit/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,25 +292,28 @@ impl ZJITState {
}
}

/// Initialize ZJIT at boot. This is called even if ZJIT is disabled.
/// Initialize IDs and annotate builtin C method entries.
/// Must be called at boot before ruby_init_prelude() since the prelude
/// could redefine core methods (e.g. Kernel.prepend via bundler).
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_init(zjit_enabled: bool) {
pub extern "C" fn rb_zjit_init_builtin_cmes() {
use InitializationState::*;

debug_assert!(
matches!(unsafe { &ZJIT_STATE }, Uninitialized),
"rb_zjit_init should only be called once during boot",
"rb_zjit_init_builtin_cmes should only be called once during boot",
);

// Initialize IDs and method annotations.
// cruby_methods::init() must be called at boot,
// as cmes could have been re-defined after boot.
cruby::ids::init();

let method_annotations = cruby_methods::init();

unsafe { ZJIT_STATE = Initialized(method_annotations); }
}

/// Initialize ZJIT at boot. This is called even if ZJIT is disabled.
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_init(zjit_enabled: bool) {
// If --zjit, enable ZJIT immediately
if zjit_enabled {
zjit_enable();
Expand Down