Skip to content

Commit e4c5105

Browse files
committed
YJIT: dump-disasm: Print comments and bytes in release builds
This change implements a fallback mode for the `--yjit-dump-disasm` development command-line option to make it usable in release builds. Previously, using the option with release builds of YJIT yielded only a warning asking the user to build with `--enable-yjit=dev`. While builds that use the `disasm` feature still give the best output, just having the comments is useful enough for many kinds of debugging. Having it usable in release builds is nice for new hackers, too, since this allows for tinkering without having to learn how to build YJIT in development mode. Sample output on A64: ``` # regenerate_branch # Insn: 0001 opt_send_without_block (stack_size: 1) # guard known object with singleton class 0x11f7e0034: 4b 00 00 58 03 00 00 14 08 ce 9c 04 01 00 00 0x11f7e0043: 00 3f 00 0b eb 81 06 01 54 1f 20 03 d5 # RUBY_VM_CHECK_INTS(ec) 0x11f7e0050: 8b 02 42 b8 cb 07 01 35 # stack overflow check 0x11f7e0058: ab 62 02 91 7f 02 0b eb 69 07 01 54 # save PC to CFP 0x11f7e0064: 0b 3b 9a d2 2b 2f a0 f2 0b 00 cc f2 6b 02 00 0x11f7e0073: f8 ab 82 00 91 ``` To ensure this feature doesn't incur too much cost when running without the `--yjit-dump-disasm` option, I checked that there is no significant impact to compile time and memory usage with the `compile_time_ns` and `yjit_alloc_size` entry in `RubyVM::YJIT.runtime_stats`. For each sample, I ran 3 iterations of the `lobsters` YJIT benchmark. The statistics summary and done with the `summary` function in R. Compile time, sample size of 60, lower is better: ``` Before After Min. :2.054e+09 Min. :2.028e+09 1st Qu.:2.069e+09 1st Qu.:2.044e+09 Median :2.081e+09 Median :2.060e+09 Mean :2.089e+09 Mean :2.066e+09 3rd Qu.:2.109e+09 3rd Qu.:2.085e+09 Max. :2.146e+09 Max. :2.144e+09 ``` Allocation size, sample size of 20, lower is better: ``` Before After Min. :21804742 Min. :21794082 1st Qu.:21826682 1st Qu.:21816282 Median :21844042 Median :21826814 Mean :21960664 Mean :22026291 3rd Qu.:21861228 3rd Qu.:22040439 Max. :22587426 Max. :22930614 ``` The `yjit_alloc_size` samples are noisy, but since the average increased by only 0.3%, and the median is lower, I feel safe saying that there is no significant change.
1 parent 3407565 commit e4c5105

File tree

7 files changed

+63
-40
lines changed

7 files changed

+63
-40
lines changed

yjit/src/asm/mod.rs

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ use std::cell::RefCell;
22
use std::fmt;
33
use std::mem;
44
use std::rc::Rc;
5+
use std::collections::BTreeMap;
6+
57
use crate::core::IseqPayload;
68
use crate::core::for_each_off_stack_iseq_payload;
79
use crate::core::for_each_on_stack_iseq_payload;
810
use crate::invariants::rb_yjit_tracing_invalidate_all;
911
use crate::stats::incr_counter;
1012
use crate::virtualmem::WriteError;
11-
12-
#[cfg(feature = "disasm")]
13-
use std::collections::BTreeMap;
14-
1513
use crate::codegen::CodegenGlobals;
1614
use crate::virtualmem::{VirtualMem, CodePtr};
1715

@@ -77,8 +75,10 @@ pub struct CodeBlock {
7775
// References to labels
7876
label_refs: Vec<LabelRef>,
7977

78+
// A switch for keeping comments. They take up memory.
79+
allow_comments: bool,
80+
8081
// Comments for assembly instructions, if that feature is enabled
81-
#[cfg(feature = "disasm")]
8282
asm_comments: BTreeMap<usize, Vec<String>>,
8383

8484
// True for OutlinedCb
@@ -107,7 +107,7 @@ impl CodeBlock {
107107
const PREFERRED_CODE_PAGE_SIZE: usize = 16 * 1024;
108108

109109
/// Make a new CodeBlock
110-
pub fn new(mem_block: Rc<RefCell<VirtualMem>>, outlined: bool, freed_pages: Rc<Option<Vec<usize>>>) -> Self {
110+
pub fn new(mem_block: Rc<RefCell<VirtualMem>>, outlined: bool, freed_pages: Rc<Option<Vec<usize>>>, allow_comments: bool) -> Self {
111111
// Pick the code page size
112112
let system_page_size = mem_block.borrow().system_page_size();
113113
let page_size = if 0 == Self::PREFERRED_CODE_PAGE_SIZE % system_page_size {
@@ -128,7 +128,7 @@ impl CodeBlock {
128128
label_addrs: Vec::new(),
129129
label_names: Vec::new(),
130130
label_refs: Vec::new(),
131-
#[cfg(feature = "disasm")]
131+
allow_comments,
132132
asm_comments: BTreeMap::new(),
133133
outlined,
134134
dropped_bytes: false,
@@ -366,9 +366,11 @@ impl CodeBlock {
366366
}
367367

368368
/// Add an assembly comment if the feature is on.
369-
/// If not, this becomes an inline no-op.
370-
#[cfg(feature = "disasm")]
371369
pub fn add_comment(&mut self, comment: &str) {
370+
if !self.allow_comments {
371+
return;
372+
}
373+
372374
let cur_ptr = self.get_write_ptr().raw_addr(self);
373375

374376
// If there's no current list of comments for this line number, add one.
@@ -379,28 +381,21 @@ impl CodeBlock {
379381
this_line_comments.push(comment.to_string());
380382
}
381383
}
382-
#[cfg(not(feature = "disasm"))]
383-
#[inline]
384-
pub fn add_comment(&mut self, _: &str) {}
385384

386-
#[cfg(feature = "disasm")]
387385
pub fn comments_at(&self, pos: usize) -> Option<&Vec<String>> {
388386
self.asm_comments.get(&pos)
389387
}
390388

391-
#[allow(unused_variables)]
392-
#[cfg(feature = "disasm")]
393389
pub fn remove_comments(&mut self, start_addr: CodePtr, end_addr: CodePtr) {
390+
if self.asm_comments.is_empty() {
391+
return;
392+
}
394393
for addr in start_addr.raw_addr(self)..end_addr.raw_addr(self) {
395394
self.asm_comments.remove(&addr);
396395
}
397396
}
398-
#[cfg(not(feature = "disasm"))]
399-
#[inline]
400-
pub fn remove_comments(&mut self, _: CodePtr, _: CodePtr) {}
401397

402398
pub fn clear_comments(&mut self) {
403-
#[cfg(feature = "disasm")]
404399
self.asm_comments.clear();
405400
}
406401

@@ -693,7 +688,7 @@ impl CodeBlock {
693688
let mem_start: *const u8 = alloc.mem_start();
694689
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size);
695690

696-
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(None))
691+
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(None), true)
697692
}
698693

699694
/// Stubbed CodeBlock for testing conditions that can arise due to code GC. Can't execute generated code.
@@ -711,7 +706,7 @@ impl CodeBlock {
711706
let mem_start: *const u8 = alloc.mem_start();
712707
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size);
713708

714-
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(Some(freed_pages)))
709+
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(Some(freed_pages)), true)
715710
}
716711
}
717712

yjit/src/backend/arm64/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,9 +924,7 @@ impl Assembler
924924

925925
match insn {
926926
Insn::Comment(text) => {
927-
if cfg!(feature = "disasm") {
928-
cb.add_comment(text);
929-
}
927+
cb.add_comment(text);
930928
},
931929
Insn::Label(target) => {
932930
cb.write_label(target.unwrap_label_idx());

yjit/src/backend/ir.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,13 +1565,10 @@ impl Assembler
15651565
#[must_use]
15661566
pub fn compile(self, cb: &mut CodeBlock, ocb: Option<&mut OutlinedCb>) -> Option<(CodePtr, Vec<u32>)>
15671567
{
1568-
#[cfg(feature = "disasm")]
15691568
let start_addr = cb.get_write_ptr();
1570-
15711569
let alloc_regs = Self::get_alloc_regs();
15721570
let ret = self.compile_with_regs(cb, ocb, alloc_regs);
15731571

1574-
#[cfg(feature = "disasm")]
15751572
if let Some(dump_disasm) = get_option_ref!(dump_disasm) {
15761573
use crate::disasm::dump_disasm_addr_range;
15771574
let end_addr = cb.get_write_ptr();
@@ -2057,10 +2054,10 @@ impl Assembler {
20572054
}
20582055

20592056
/// Macro to use format! for Insn::Comment, which skips a format! call
2060-
/// when disasm is not supported.
2057+
/// when not dumping disassembly.
20612058
macro_rules! asm_comment {
20622059
($asm:expr, $($fmt:tt)*) => {
2063-
if cfg!(feature = "disasm") {
2060+
if $crate::options::get_option_ref!(dump_disasm).is_some() {
20642061
$asm.push_insn(Insn::Comment(format!($($fmt)*)));
20652062
}
20662063
};

yjit/src/backend/x86_64/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -492,9 +492,7 @@ impl Assembler
492492

493493
match insn {
494494
Insn::Comment(text) => {
495-
if cfg!(feature = "disasm") {
496-
cb.add_comment(text);
497-
}
495+
cb.add_comment(text);
498496
},
499497

500498
// Write the label at the current position

yjit/src/codegen.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10322,8 +10322,10 @@ impl CodegenGlobals {
1032210322
let mem_block = Rc::new(RefCell::new(mem_block));
1032310323

1032410324
let freed_pages = Rc::new(None);
10325-
let cb = CodeBlock::new(mem_block.clone(), false, freed_pages.clone());
10326-
let ocb = OutlinedCb::wrap(CodeBlock::new(mem_block, true, freed_pages));
10325+
10326+
let asm_comments = get_option_ref!(dump_disasm).is_some();
10327+
let cb = CodeBlock::new(mem_block.clone(), false, freed_pages.clone(), asm_comments);
10328+
let ocb = OutlinedCb::wrap(CodeBlock::new(mem_block, true, freed_pages, asm_comments));
1032710329

1032810330
(cb, ocb)
1032910331
};

yjit/src/disasm.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
use crate::core::*;
22
use crate::cruby::*;
33
use crate::yjit::yjit_enabled_p;
4-
#[cfg(feature = "disasm")]
54
use crate::asm::CodeBlock;
6-
#[cfg(feature = "disasm")]
75
use crate::codegen::CodePtr;
8-
#[cfg(feature = "disasm")]
96
use crate::options::DumpDisasm;
107

11-
#[cfg(feature = "disasm")]
128
use std::fmt::Write;
139

1410
/// Primitive called in yjit.rb
@@ -116,7 +112,6 @@ pub fn disasm_iseq_insn_range(iseq: IseqPtr, start_idx: u16, end_idx: u16) -> St
116112
}
117113

118114
/// Dump dissassembly for a range in a [CodeBlock]. VM lock required.
119-
#[cfg(feature = "disasm")]
120115
pub fn dump_disasm_addr_range(cb: &CodeBlock, start_addr: CodePtr, end_addr: CodePtr, dump_disasm: &DumpDisasm) {
121116
for (start_addr, end_addr) in cb.writable_addrs(start_addr, end_addr) {
122117
let disasm = disasm_addr_range(cb, start_addr, end_addr);
@@ -192,6 +187,44 @@ pub fn disasm_addr_range(cb: &CodeBlock, start_addr: usize, end_addr: usize) ->
192187
return out;
193188
}
194189

190+
#[cfg(not(feature = "disasm"))]
191+
pub fn disasm_addr_range(cb: &CodeBlock, start_addr: usize, end_addr: usize) -> String {
192+
let mut out = String::new();
193+
let mut line_byte_idx = 0;
194+
const MAX_BYTES_PER_LINE: usize = 16;
195+
196+
for addr in start_addr..end_addr {
197+
if let Some(comment_list) = cb.comments_at(addr) {
198+
// Start a new line if we're in the middle of one
199+
if line_byte_idx != 0 {
200+
writeln!(&mut out).unwrap();
201+
line_byte_idx = 0;
202+
}
203+
for comment in comment_list {
204+
writeln!(&mut out, " \x1b[1m# {comment}\x1b[22m").unwrap(); // Make comments bold
205+
}
206+
}
207+
if line_byte_idx == 0 {
208+
write!(&mut out, " 0x{addr:x}: ").unwrap();
209+
} else {
210+
write!(&mut out, " ").unwrap();
211+
}
212+
let byte = unsafe { (addr as *const u8).read() };
213+
write!(&mut out, "{byte:02x}").unwrap();
214+
line_byte_idx += 1;
215+
if line_byte_idx == MAX_BYTES_PER_LINE - 1 {
216+
writeln!(&mut out).unwrap();
217+
line_byte_idx = 0;
218+
}
219+
}
220+
221+
if !out.is_empty() {
222+
writeln!(&mut out).unwrap();
223+
}
224+
225+
out
226+
}
227+
195228
/// Assert that CodeBlock has the code specified with hex. In addition, if tested with
196229
/// `cargo test --all-features`, it also checks it generates the specified disasm.
197230
#[cfg(test)]

yjit/src/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
250250

251251
("dump-disasm", _) => {
252252
if !cfg!(feature = "disasm") {
253-
eprintln!("WARNING: the {} option is only available when YJIT is built in dev mode, i.e. ./configure --enable-yjit=dev", opt_name);
253+
eprintln!("WARNING: the {} option works best when YJIT is built in dev mode, i.e. ./configure --enable-yjit=dev", opt_name);
254254
}
255255

256256
match opt_val {

0 commit comments

Comments
 (0)