Skip to content

Commit 54a42ec

Browse files
maximecbXrXrk0kubun
authored andcommitted
YJIT: Add --yjit-pause and RubyVM::YJIT.resume (ruby#7609)
* YJIT: Add --yjit-pause and RubyVM::YJIT.resume This allows booting YJIT in a suspended state. We chose to add a new command line option as opposed to simply allowing YJIT.resume to work without any command line option because it allows for combining with YJIT tuning command line options. It also simpifies implementation. Paired with Kokubun and Maxime. * Update yjit.rb Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> --------- Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
1 parent c0fa0c8 commit 54a42ec

File tree

7 files changed

+60
-1
lines changed

7 files changed

+60
-1
lines changed

test/ruby/test_yjit.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ def test_command_line_switches
5151
#assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
5252
end
5353

54+
def test_starting_paused
55+
program = <<~RUBY
56+
def not_compiled = nil
57+
def will_compile = nil
58+
def compiled_counts = RubyVM::YJIT.runtime_stats[:compiled_iseq_count]
59+
counts = []
60+
not_compiled
61+
counts << compiled_counts
62+
63+
RubyVM::YJIT.resume
64+
65+
will_compile
66+
counts << compiled_counts
67+
68+
if counts[0] == 0 && counts[1] > 0
69+
p :ok
70+
end
71+
RUBY
72+
assert_in_out_err(%w[--yjit-pause --yjit-stats --yjit-call-threshold=1], program, success: true) do |stdout, stderr|
73+
assert_equal([":ok"], stdout)
74+
end
75+
end
76+
5477
def test_yjit_stats_and_v_no_error
5578
_stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-stats), '', true, true)
5679
refute_includes(stderr, "NoMethodError")

vm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ jit_exec(rb_execution_context_t *ec)
416416
// Increment the ISEQ's call counter
417417
const rb_iseq_t *iseq = ec->cfp->iseq;
418418
struct rb_iseq_constant_body *body = ISEQ_BODY(iseq);
419-
bool yjit_enabled = rb_yjit_enabled_p();
419+
bool yjit_enabled = rb_yjit_compile_new_iseqs();
420420
if (yjit_enabled || mjit_call_p) {
421421
body->total_calls++;
422422
}

yjit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ VALUE rb_yjit_insns_compiled(rb_execution_context_t *ec, VALUE self, VALUE iseq)
10901090
VALUE rb_yjit_code_gc(rb_execution_context_t *ec, VALUE self);
10911091
VALUE rb_yjit_simulate_oom_bang(rb_execution_context_t *ec, VALUE self);
10921092
VALUE rb_yjit_get_exit_locations(rb_execution_context_t *ec, VALUE self);
1093+
VALUE rb_yjit_resume(rb_execution_context_t *ec, VALUE self);
10931094

10941095
// Preprocessed yjit.rb generated during build
10951096
#include "yjit.rbinc"

yjit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
// Expose these as declarations since we are building YJIT.
2828
bool rb_yjit_enabled_p(void);
29+
bool rb_yjit_compile_new_iseqs(void);
2930
unsigned rb_yjit_call_threshold(void);
3031
void rb_yjit_invalidate_all_method_lookup_assumptions(void);
3132
void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme);
@@ -47,6 +48,7 @@ void rb_yjit_tracing_invalidate_all(void);
4748
// In these builds, YJIT could never be turned on. Provide dummy implementations.
4849

4950
static inline bool rb_yjit_enabled_p(void) { return false; }
51+
static inline bool rb_yjit_compile_new_iseqs(void) { return false; }
5052
static inline unsigned rb_yjit_call_threshold(void) { return UINT_MAX; }
5153
static inline void rb_yjit_invalidate_all_method_lookup_assumptions(void) {}
5254
static inline void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme) {}

yjit.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def self.reset_stats!
2929
Primitive.rb_yjit_reset_stats_bang
3030
end
3131

32+
# Resume YJIT compilation after paused on startup with --yjit-pause
33+
def self.resume
34+
Primitive.rb_yjit_resume
35+
end
36+
3237
# If --yjit-trace-exits is enabled parse the hashes from
3338
# Primitive.rb_yjit_get_exit_locations into a format readable
3439
# by Stackprof. This will allow us to find the exact location of a

yjit/src/options.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub struct Options {
3131
// Trace locations of exits
3232
pub gen_trace_exits: bool,
3333

34+
// Whether to start YJIT in paused state (initialize YJIT but don't
35+
// compile anything)
36+
pub pause: bool,
37+
3438
/// Dump compiled and executed instructions for debugging
3539
pub dump_insns: bool,
3640

@@ -60,6 +64,7 @@ pub static mut OPTIONS: Options = Options {
6064
gen_stats: false,
6165
gen_trace_exits: false,
6266
print_stats: true,
67+
pause: false,
6368
dump_insns: false,
6469
dump_disasm: None,
6570
verify_ctx: false,
@@ -143,6 +148,10 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
143148
}
144149
},
145150

151+
("pause", "") => unsafe {
152+
OPTIONS.pause = true;
153+
},
154+
146155
("dump-disasm", _) => match opt_val.to_string().as_str() {
147156
"" => unsafe { OPTIONS.dump_disasm = Some(DumpDisasm::Stdout) },
148157
directory => {

yjit/src/yjit.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
1414
/// See [rb_yjit_enabled_p]
1515
static YJIT_ENABLED: AtomicBool = AtomicBool::new(false);
1616

17+
/// When false, we don't compile new iseqs, but might still service existing branch stubs.
18+
static COMPILE_NEW_ISEQS: AtomicBool = AtomicBool::new(false);
19+
1720
/// Parse one command-line option.
1821
/// This is called from ruby.c
1922
#[no_mangle]
@@ -31,6 +34,11 @@ pub extern "C" fn rb_yjit_enabled_p() -> raw::c_int {
3134
YJIT_ENABLED.load(Ordering::Acquire).into()
3235
}
3336

37+
#[no_mangle]
38+
pub extern "C" fn rb_yjit_compile_new_iseqs() -> bool {
39+
COMPILE_NEW_ISEQS.load(Ordering::Acquire).into()
40+
}
41+
3442
/// Like rb_yjit_enabled_p, but for Rust code.
3543
pub fn yjit_enabled_p() -> bool {
3644
YJIT_ENABLED.load(Ordering::Acquire)
@@ -59,6 +67,8 @@ pub extern "C" fn rb_yjit_init_rust() {
5967

6068
// YJIT enabled and initialized successfully
6169
YJIT_ENABLED.store(true, Ordering::Release);
70+
71+
COMPILE_NEW_ISEQS.store(!get_option!(pause), Ordering::Release);
6272
});
6373

6474
if let Err(_) = result {
@@ -116,6 +126,15 @@ pub extern "C" fn rb_yjit_code_gc(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
116126
Qnil
117127
}
118128

129+
#[no_mangle]
130+
pub extern "C" fn rb_yjit_resume(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
131+
if yjit_enabled_p() {
132+
COMPILE_NEW_ISEQS.store(true, Ordering::Release);
133+
}
134+
135+
Qnil
136+
}
137+
119138
/// Simulate a situation where we are out of executable memory
120139
#[no_mangle]
121140
pub extern "C" fn rb_yjit_simulate_oom_bang(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {

0 commit comments

Comments
 (0)