Skip to content

Commit 3f5b301

Browse files
author
Noah Gibbs
authored
Add comments to YJIT disassembly (ruby#231)
* Add comments to YJIT disassembly. Also add checks to avoid panics when disassembling without YJIT active. * Fix test to use new asm comments interface * Use self.enabled? instead of primitive expr * Comments on appending to Vec<String> in add_comment * Directly use the global inline codeblock in disasm, don't abstract it * Use usize instead of u64 for addresses
1 parent 757930f commit 3f5b301

File tree

4 files changed

+57
-27
lines changed

4 files changed

+57
-27
lines changed

yjit.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ def self.disasm(iseq)
3434
# If a method or proc is passed in, get its iseq
3535
iseq = RubyVM::InstructionSequence.of(iseq)
3636

37-
# Produce the disassembly string
38-
# Include the YARV iseq disasm in the string for additional context
39-
iseq.disasm + "\n" + Primitive.rb_yjit_disasm_iseq(iseq)
37+
if self.enabled?
38+
# Produce the disassembly string
39+
# Include the YARV iseq disasm in the string for additional context
40+
iseq.disasm + "\n" + Primitive.rb_yjit_disasm_iseq(iseq)
41+
else
42+
iseq.disasm
43+
end
4044
end
4145

4246
#def self.simulate_oom!

yjit/src/asm/mod.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::mem;
2+
use std::collections::{BTreeMap};
23

34
pub mod x86_64;
45

@@ -19,6 +20,11 @@ impl CodePtr {
1920
let CodePtr(ptr) = self;
2021
*ptr as i64
2122
}
23+
24+
fn into_usize(&self) -> usize {
25+
let CodePtr(ptr) = self;
26+
*ptr as usize
27+
}
2228
}
2329

2430
impl From<*mut u8> for CodePtr {
@@ -88,7 +94,7 @@ pub struct CodeBlock
8894
label_refs: Vec<LabelRef>,
8995

9096
// Comments for assembly instructions, if that feature is enabled
91-
asm_comments: Vec<(usize, String)>,
97+
asm_comments: BTreeMap<usize, Vec<String>>,
9298

9399
// Keep track of the current aligned write position.
94100
// Used for changing protection when writing to the JIT buffer
@@ -119,7 +125,7 @@ impl CodeBlock
119125
label_addrs: Vec::new(),
120126
label_names: Vec::new(),
121127
label_refs: Vec::new(),
122-
asm_comments: Vec::new(),
128+
asm_comments: BTreeMap::new(),
123129
current_aligned_write_pos: ALIGNED_WRITE_POSITION_NONE,
124130
page_size: 4096,
125131
dropped_bytes: false
@@ -135,7 +141,7 @@ impl CodeBlock
135141
label_addrs: Vec::new(),
136142
label_names: Vec::new(),
137143
label_refs: Vec::new(),
138-
asm_comments: Vec::new(),
144+
asm_comments: BTreeMap::new(),
139145
current_aligned_write_pos: ALIGNED_WRITE_POSITION_NONE,
140146
page_size,
141147
dropped_bytes: false
@@ -153,26 +159,26 @@ impl CodeBlock
153159
pub fn add_comment(&mut self, comment: &str) {
154160
#[cfg(feature="asm_comments")]
155161
{
156-
let is_dup = if let Some((last_pos, last_comment)) = self.asm_comments.last() {
157-
*last_pos == self.write_pos && *last_comment == comment
158-
} else { false };
159-
if !is_dup {
160-
self.asm_comments.push((self.write_pos, String::from(comment)));
162+
let cur_ptr = self.get_write_ptr().into_usize();
163+
let this_line_comments = self.asm_comments.get(&cur_ptr);
164+
165+
// If there's no current list of comments for this line number, add one.
166+
if this_line_comments.is_none() {
167+
let new_comments = Vec::new();
168+
self.asm_comments.insert(cur_ptr, new_comments);
161169
}
162-
}
163-
}
170+
let this_line_comments = self.asm_comments.get_mut(&cur_ptr).unwrap();
164171

165-
/// Add an assembly comment at a specific byte offset if the feature is on.
166-
/// If not, this becomes an inline no-op.
167-
#[inline]
168-
pub fn add_comment_at(&mut self, pos:usize, comment: &str) {
169-
#[cfg(feature="asm_comments")]
170-
self.asm_comments.push((pos, String::from(comment)));
172+
// Unless this comment is the same as the last one at this same line, add it.
173+
let string_comment = String::from(comment);
174+
if this_line_comments.last() != Some(&string_comment) {
175+
this_line_comments.push(string_comment);
176+
}
177+
}
171178
}
172179

173-
/// Get a slice (readonly ref) of assembly comments - if the feature is off, this will be empty.
174-
pub fn get_comments(&self) -> &[(usize, String)] {
175-
return self.asm_comments.as_slice();
180+
pub fn comments_at(&self, pos: usize) -> Option<&Vec<String>> {
181+
self.asm_comments.get(&pos)
176182
}
177183

178184
pub fn get_write_pos(&self) -> usize {

yjit/src/asm/x86_64/tests.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,18 @@ fn basic_capstone_usage() -> std::result::Result<(), capstone::Error> {
430430
fn block_comments() {
431431
let mut cb = super::CodeBlock::new_dummy(4096);
432432

433+
let first_write_ptr = cb.get_write_ptr().into_usize();
433434
cb.add_comment("Beginning");
434435
xor(&mut cb, EAX, EAX); // 2 bytes long
436+
let second_write_ptr = cb.get_write_ptr().into_usize();
435437
cb.add_comment("Two bytes in");
436-
cb.add_comment("Two bytes in"); // Duplicate, should be ignored
438+
cb.add_comment("Still two bytes in");
439+
cb.add_comment("Still two bytes in"); // Duplicate, should be ignored
437440
test(&mut cb, mem_opnd(64, RSI, 64), imm_opnd(!0x08)); // 8 bytes long
441+
let third_write_ptr = cb.get_write_ptr().into_usize();
438442
cb.add_comment("Ten bytes in");
439443

440-
let comments = cb.get_comments();
441-
let expected:Vec<(usize,String)> = vec!( (0, "Beginning".to_string()), (2, "Two bytes in".to_string()), (10, "Ten bytes in".to_string()) );
442-
assert_eq!(expected, comments);
444+
assert_eq!(&vec!( "Beginning".to_string() ), cb.comments_at(first_write_ptr).unwrap());
445+
assert_eq!(&vec!( "Two bytes in".to_string(), "Still two bytes in".to_string() ), cb.comments_at(second_write_ptr).unwrap());
446+
assert_eq!(&vec!( "Ten bytes in".to_string() ), cb.comments_at(third_write_ptr).unwrap());
443447
}

yjit/src/disasm.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::fmt::Write;
22
use crate::cruby::*;
3+
use crate::codegen::*;
4+
use crate::asm::*;
35
use crate::core::*;
6+
use crate::yjit::yjit_enabled_p;
47

58
/// Primitive called in yjit.rb
69
/// Produce a string representing the disassembly for an ISEQ
@@ -19,6 +22,10 @@ pub extern "C" fn rb_yjit_disasm_iseq(ec: EcPtr, ruby_self: VALUE, iseqw: VALUE)
1922
// return Qnil;
2023
//}
2124

25+
if !yjit_enabled_p() {
26+
return Qnil;
27+
}
28+
2229
// Get the iseq pointer from the wrapper
2330
let iseq = unsafe { rb_iseqw_to_iseq(iseqw) };
2431

@@ -35,6 +42,9 @@ fn disasm_iseq(iseq: IseqPtr) -> String {
3542
// Get a list of block versions generated for this iseq
3643
let mut block_list = get_iseq_block_list(iseq);
3744

45+
// Get a list of codeblocks relevant to this iseq
46+
let global_cb = CodegenGlobals::get_inline_cb();
47+
3848
// Sort the blocks by increasing start addresses
3949
block_list.sort_by(|a, b| {
4050
use std::cmp::Ordering;
@@ -57,7 +67,7 @@ fn disasm_iseq(iseq: IseqPtr) -> String {
5767
// Compute total code size in bytes for all blocks in the function
5868
let mut total_code_size = 0;
5969
for blockref in &block_list {
60-
total_code_size = blockref.borrow().code_size();
70+
total_code_size += blockref.borrow().code_size();
6171
}
6272

6373
// Initialize capstone
@@ -99,6 +109,12 @@ fn disasm_iseq(iseq: IseqPtr) -> String {
99109

100110
// For each instruction in this block
101111
for insn in insns.as_ref() {
112+
// Comments for this block
113+
if let Some(comment_list) = global_cb.comments_at(insn.address() as usize) {
114+
for comment in comment_list {
115+
out.push_str(&format!(" // {}\n", comment));
116+
}
117+
}
102118
out.push_str(&format!(" {}\n", insn));
103119
}
104120

0 commit comments

Comments
 (0)