1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
use std::{ffi::{CStr, CString}, ptr::null};
use crate::backend::current::TEMP_REGS;
use std::os::raw::{c_char, c_int, c_uint};
// Call threshold for small deployments and command-line apps
pub static SMALL_CALL_THRESHOLD: u64 = 30;
// Call threshold for larger deployments and production-sized applications
pub static LARGE_CALL_THRESHOLD: u64 = 120;
// Number of live ISEQs after which we consider an app to be large
pub static LARGE_ISEQ_COUNT: u64 = 40_000;
// This option is exposed to the C side in a global variable for performance, see vm.c
// Number of method calls after which to start generating code
// Threshold==1 means compile on first execution
#[no_mangle]
pub static mut rb_yjit_call_threshold: u64 = SMALL_CALL_THRESHOLD;
// This option is exposed to the C side in a global variable for performance, see vm.c
// Number of execution requests after which a method is no longer
// considered hot. Raising this results in more generated code.
#[no_mangle]
pub static mut rb_yjit_cold_threshold: u64 = 200_000;
// Command-line options
#[derive(Clone, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct Options {
// Size of the executable memory block to allocate in bytes
// Note that the command line argument is expressed in MiB and not bytes
pub exec_mem_size: usize,
// Disable the propagation of type information
pub no_type_prop: bool,
// Maximum number of versions per block
// 1 means always create generic versions
pub max_versions: usize,
// The number of registers allocated for stack temps
pub num_temp_regs: usize,
// Capture stats
pub gen_stats: bool,
// Print stats on exit (when gen_stats is also true)
pub print_stats: bool,
// Trace locations of exits
pub gen_trace_exits: bool,
// how often to sample exit trace data
pub trace_exits_sample_rate: usize,
// Whether to enable YJIT at boot. This option prevents other
// YJIT tuning options from enabling YJIT at boot.
pub disable: bool,
/// Dump compiled and executed instructions for debugging
pub dump_insns: bool,
/// Dump all compiled instructions of target cbs.
pub dump_disasm: Option<DumpDisasm>,
/// Print when specific ISEQ items are compiled or invalidated
pub dump_iseq_disasm: Option<String>,
/// Verify context objects (debug mode only)
pub verify_ctx: bool,
/// Enable generating frame pointers (for x86. arm64 always does this)
pub frame_pointer: bool,
/// Run code GC when exec_mem_size is reached.
pub code_gc: bool,
/// Enable writing /tmp/perf-{pid}.map for Linux perf
pub perf_map: bool,
}
// Initialize the options to default values
pub static mut OPTIONS: Options = Options {
exec_mem_size: 128 * 1024 * 1024,
no_type_prop: false,
max_versions: 4,
num_temp_regs: 5,
gen_stats: false,
gen_trace_exits: false,
print_stats: true,
trace_exits_sample_rate: 0,
disable: false,
dump_insns: false,
dump_disasm: None,
verify_ctx: false,
dump_iseq_disasm: None,
frame_pointer: false,
code_gc: false,
perf_map: false,
};
/// YJIT option descriptions for `ruby --help`.
static YJIT_OPTIONS: [(&str, &str); 8] = [
("--yjit-stats", "Enable collecting YJIT statistics"),
("--yjit-trace-exits", "Record Ruby source location when exiting from generated code"),
("--yjit-trace-exits-sample-rate", "Trace exit locations only every Nth occurrence"),
("--yjit-exec-mem-size=num", "Size of executable memory block in MiB (default: 128)"),
("--yjit-code-gc", "Run code GC when the code size reaches the limit"),
("--yjit-call-threshold=num", "Number of calls to trigger JIT"),
("--yjit-cold-threshold=num", "Global call after which ISEQs not compiled (default: 200K)"),
("--yjit-perf", "Enable frame pointers and perf profiling"),
];
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum DumpDisasm {
// Dump to stdout
Stdout,
// Dump to "yjit_{pid}.log" file under the specified directory
File(String),
}
/// Macro to get an option value by name
macro_rules! get_option {
// Unsafe is ok here because options are initialized
// once before any Ruby code executes
($option_name:ident) => {
{
// Make this a statement since attributes on expressions are experimental
#[allow(unused_unsafe)]
let ret = unsafe { OPTIONS.$option_name };
ret
}
};
}
pub(crate) use get_option;
/// Macro to reference an option value by name; we assume it's a cloneable type like String or an Option of same.
macro_rules! get_option_ref {
// Unsafe is ok here because options are initialized
// once before any Ruby code executes
($option_name:ident) => {
unsafe { &($crate::options::OPTIONS.$option_name) }
};
}
pub(crate) use get_option_ref;
/// Expected to receive what comes after the third dash in "--yjit-*".
/// Empty string means user passed only "--yjit". C code rejects when
/// they pass exact "--yjit-".
pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
let c_str: &CStr = unsafe { CStr::from_ptr(str_ptr) };
let opt_str: &str = c_str.to_str().ok()?;
//println!("{}", opt_str);
// Split the option name and value strings
// Note that some options do not contain an assignment
let parts = opt_str.split_once('=');
let (opt_name, opt_val) = match parts {
Some((before_eq, after_eq)) => (before_eq, after_eq),
None => (opt_str, ""),
};
// Match on the option name and value strings
match (opt_name, opt_val) {
("", "") => (), // Simply --yjit
("exec-mem-size", _) => match opt_val.parse::<usize>() {
Ok(n) => {
if n == 0 || n > 2 * 1024 * 1024 {
return None
}
// Convert from MiB to bytes internally for convenience
unsafe { OPTIONS.exec_mem_size = n * 1024 * 1024 }
}
Err(_) => {
return None;
}
},
("call-threshold", _) => match opt_val.parse() {
Ok(n) => unsafe { rb_yjit_call_threshold = n },
Err(_) => {
return None;
}
},
("cold-threshold", _) => match opt_val.parse() {
Ok(n) => unsafe { rb_yjit_cold_threshold = n },
Err(_) => {
return None;
}
},
("max-versions", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.max_versions = n },
Err(_) => {
return None;
}
},
("disable", "") => unsafe {
OPTIONS.disable = true;
},
("temp-regs", _) => match opt_val.parse() {
Ok(n) => {
assert!(n <= TEMP_REGS.len(), "--yjit-temp-regs must be <= {}", TEMP_REGS.len());
unsafe { OPTIONS.num_temp_regs = n }
}
Err(_) => {
return None;
}
},
("code-gc", _) => unsafe {
OPTIONS.code_gc = true;
},
("perf", _) => match opt_val {
"" => unsafe {
OPTIONS.frame_pointer = true;
OPTIONS.perf_map = true;
},
"fp" => unsafe { OPTIONS.frame_pointer = true },
"map" => unsafe { OPTIONS.perf_map = true },
_ => return None,
},
("dump-disasm", _) => match opt_val {
"" => unsafe { OPTIONS.dump_disasm = Some(DumpDisasm::Stdout) },
directory => {
let pid = std::process::id();
let path = format!("{directory}/yjit_{pid}.log");
eprintln!("YJIT disasm dump: {path}");
unsafe { OPTIONS.dump_disasm = Some(DumpDisasm::File(path)) }
}
},
("dump-iseq-disasm", _) => unsafe {
OPTIONS.dump_iseq_disasm = Some(opt_val.to_string());
},
("no-type-prop", "") => unsafe { OPTIONS.no_type_prop = true },
("stats", _) => match opt_val {
"" => unsafe { OPTIONS.gen_stats = true },
"quiet" => {
unsafe { OPTIONS.gen_stats = true }
unsafe { OPTIONS.print_stats = false }
},
_ => {
return None;
}
},
("trace-exits", "") => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = 0 },
("trace-exits-sample-rate", sample_rate) => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = sample_rate.parse().unwrap(); },
("dump-insns", "") => unsafe { OPTIONS.dump_insns = true },
("verify-ctx", "") => unsafe { OPTIONS.verify_ctx = true },
// Option name not recognized
_ => {
return None;
}
}
// before we continue, check that sample_rate is either 0 or a prime number
let trace_sample_rate = unsafe { OPTIONS.trace_exits_sample_rate };
if trace_sample_rate > 1 {
let mut i = 2;
while i*i <= trace_sample_rate {
if trace_sample_rate % i == 0 {
println!("Warning: using a non-prime number as your sampling rate can result in less accurate sampling data");
return Some(());
}
i += 1;
}
}
// dbg!(unsafe {OPTIONS});
// Option successfully parsed
return Some(());
}
/// Print YJIT options for `ruby --help`. `width` is width of option parts, and
/// `columns` is indent width of descriptions.
#[no_mangle]
pub extern "C" fn rb_yjit_show_usage(help: c_int, highlight: c_int, width: c_uint, columns: c_int) {
for &(name, description) in YJIT_OPTIONS.iter() {
extern "C" {
fn ruby_show_usage_line(name: *const c_char, secondary: *const c_char, description: *const c_char,
help: c_int, highlight: c_int, width: c_uint, columns: c_int);
}
let name = CString::new(name).unwrap();
let description = CString::new(description).unwrap();
unsafe { ruby_show_usage_line(name.as_ptr(), null(), description.as_ptr(), help, highlight, width, columns) }
}
}
|