Resurrect asm comment support

This commit is contained in:
Takashi Kokubun 2025-02-14 17:49:53 -08:00
parent 53bee25068
commit 562c35a560
Notes: git 2025-04-18 13:48:36 +00:00
11 changed files with 110 additions and 29 deletions

12
zjit.c
View File

@ -156,6 +156,18 @@ rb_zjit_reserve_addr_space(uint32_t mem_size)
#endif #endif
} }
unsigned long
rb_RSTRING_LEN(VALUE str)
{
return RSTRING_LEN(str);
}
char *
rb_RSTRING_PTR(VALUE str)
{
return RSTRING_PTR(str);
}
void void
rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
{ {

View File

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
//use std::fmt; //use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
@ -34,6 +35,12 @@ pub struct CodeBlock {
// Current writing position // Current writing position
write_pos: usize, write_pos: usize,
// A switch for keeping comments. They take up memory.
keep_comments: bool,
// Comments for assembly instructions, if that feature is enabled
asm_comments: BTreeMap<usize, Vec<String>>,
// Set if the CodeBlock is unable to output some instructions, // Set if the CodeBlock is unable to output some instructions,
// for example, when there is not enough space or when a jump // for example, when there is not enough space or when a jump
// target is too far away. // target is too far away.
@ -42,14 +49,37 @@ pub struct CodeBlock {
impl CodeBlock { impl CodeBlock {
/// Make a new CodeBlock /// Make a new CodeBlock
pub fn new(mem_block: Rc<RefCell<VirtualMem>>) -> Self { pub fn new(mem_block: Rc<RefCell<VirtualMem>>, keep_comments: bool) -> Self {
Self { Self {
mem_block, mem_block,
write_pos: 0, write_pos: 0,
keep_comments,
asm_comments: BTreeMap::new(),
dropped_bytes: false, dropped_bytes: false,
} }
} }
/// Add an assembly comment if the feature is on.
pub fn add_comment(&mut self, comment: &str) {
if !self.keep_comments {
return;
}
let cur_ptr = self.get_write_ptr().raw_addr(self);
// If there's no current list of comments for this line number, add one.
let this_line_comments = self.asm_comments.entry(cur_ptr).or_default();
// Unless this comment is the same as the last one at this same line, add it.
if this_line_comments.last().map(String::as_str) != Some(comment) {
this_line_comments.push(comment.to_string());
}
}
pub fn comments_at(&self, pos: usize) -> Option<&Vec<String>> {
self.asm_comments.get(&pos)
}
pub fn get_write_pos(&self) -> usize { pub fn get_write_pos(&self) -> usize {
self.write_pos self.write_pos
} }

View File

@ -935,9 +935,8 @@ impl Assembler
let mut insn_gc_offsets: Vec<u32> = Vec::new(); let mut insn_gc_offsets: Vec<u32> = Vec::new();
match insn { match insn {
Insn::Comment(_text) => { Insn::Comment(text) => {
//cb.add_comment(text); cb.add_comment(text);
unimplemented!("comments are not supported yet");
}, },
Insn::Label(_target) => { Insn::Label(_target) => {
//cb.write_label(target.unwrap_label_idx()); //cb.write_label(target.unwrap_label_idx());

View File

@ -1657,7 +1657,7 @@ impl Assembler
#[cfg(feature = "disasm")] #[cfg(feature = "disasm")]
if get_option!(dump_disasm) { if get_option!(dump_disasm) {
let end_addr = cb.get_write_ptr(); let end_addr = cb.get_write_ptr();
let disasm = crate::disasm::disasm_addr_range(start_addr.raw_ptr(cb) as usize, end_addr.raw_ptr(cb) as usize); let disasm = crate::disasm::disasm_addr_range(cb, start_addr.raw_ptr(cb) as usize, end_addr.raw_ptr(cb) as usize);
println!("{}", disasm); println!("{}", disasm);
} }
ret ret
@ -2153,16 +2153,14 @@ impl Assembler {
/// Macro to use format! for Insn::Comment, which skips a format! call /// Macro to use format! for Insn::Comment, which skips a format! call
/// when not dumping disassembly. /// when not dumping disassembly.
/*
macro_rules! asm_comment { macro_rules! asm_comment {
($asm:expr, $($fmt:tt)*) => { ($asm:expr, $($fmt:tt)*) => {
if $crate::options::get_option_ref!(dump_disasm).is_some() { if $crate::options::get_option!(dump_disasm) {
$asm.push_insn(Insn::Comment(format!($($fmt)*))); $asm.push_insn(Insn::Comment(format!($($fmt)*)));
} }
}; };
} }
pub(crate) use asm_comment; pub(crate) use asm_comment;
*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -477,9 +477,8 @@ impl Assembler
let mut insn_gc_offsets: Vec<u32> = Vec::new(); let mut insn_gc_offsets: Vec<u32> = Vec::new();
match insn { match insn {
Insn::Comment(_text) => { Insn::Comment(text) => {
unimplemented!("comments are not supported yet"); cb.add_comment(text);
//cb.add_comment(text);
}, },
// Write the label at the current position // Write the label at the current position

View File

@ -10,10 +10,10 @@ use crate::{
use crate::get_option; use crate::get_option;
/// Compile SSA IR into machine code /// Compile SSA IR into machine code
pub fn gen_function(cb: &mut CodeBlock, function: &Function) -> Option<CodePtr> { pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> Option<CodePtr> {
// Set up special registers // Set up special registers
let mut asm = Assembler::new(); let mut asm = Assembler::new();
gen_entry_prologue(&mut asm); gen_entry_prologue(&mut asm, iseq);
// Compile each instruction in the IR // Compile each instruction in the IR
for insn in function.insns.iter() { for insn in function.insns.iter() {
@ -28,22 +28,15 @@ pub fn gen_function(cb: &mut CodeBlock, function: &Function) -> Option<CodePtr>
} }
// Generate code if everything can be compiled // Generate code if everything can be compiled
let start_ptr = cb.get_write_ptr(); let start_ptr = asm.compile(cb).map(|(start_ptr, _)| start_ptr);
asm.compile_with_regs(cb, Assembler::get_alloc_regs()); // TODO: resurrect cache busting for arm64
cb.mark_all_executable(); cb.mark_all_executable();
#[cfg(feature = "disasm")] start_ptr
if get_option!(dump_disasm) {
let end_ptr = cb.get_write_ptr();
let disasm = crate::disasm::disasm_addr_range(start_ptr.raw_ptr(cb) as usize, end_ptr.raw_ptr(cb) as usize);
println!("{}", disasm);
}
Some(start_ptr)
} }
/// Compile an interpreter entry block to be inserted into an ISEQ /// Compile an interpreter entry block to be inserted into an ISEQ
fn gen_entry_prologue(asm: &mut Assembler) { fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
asm_comment!(asm, "YJIT entry point: {}", iseq_get_location(iseq, 0));
asm.frame_setup(); asm.frame_setup();
// Save the registers we'll use for CFP, EP, SP // Save the registers we'll use for CFP, EP, SP

View File

@ -630,6 +630,44 @@ pub fn cstr_to_rust_string(c_char_ptr: *const c_char) -> Option<String> {
} }
} }
// Location is the file defining the method, colon, method name.
// Filenames are sometimes internal strings supplied to eval,
// so be careful with them.
pub fn iseq_get_location(iseq: IseqPtr, pos: u16) -> String {
let iseq_label = unsafe { rb_iseq_label(iseq) };
let iseq_path = unsafe { rb_iseq_path(iseq) };
let iseq_lineno = unsafe { rb_iseq_line_no(iseq, pos as usize) };
let mut s = if iseq_label == Qnil {
"None".to_string()
} else {
ruby_str_to_rust(iseq_label)
};
s.push_str("@");
if iseq_path == Qnil {
s.push_str("None");
} else {
s.push_str(&ruby_str_to_rust(iseq_path));
}
s.push_str(":");
s.push_str(&iseq_lineno.to_string());
s
}
// Convert a CRuby UTF-8-encoded RSTRING into a Rust string.
// This should work fine on ASCII strings and anything else
// that is considered legal UTF-8, including embedded nulls.
fn ruby_str_to_rust(v: VALUE) -> String {
let str_ptr = unsafe { rb_RSTRING_PTR(v) } as *mut u8;
let str_len: usize = unsafe { rb_RSTRING_LEN(v) }.try_into().unwrap();
let str_slice: &[u8] = unsafe { std::slice::from_raw_parts(str_ptr, str_len) };
match String::from_utf8(str_slice.to_vec()) {
Ok(utf8) => utf8,
Err(_) => String::new(),
}
}
/// A location in Rust code for integrating with debugging facilities defined in C. /// A location in Rust code for integrating with debugging facilities defined in C.
/// Use the [src_loc!] macro to crate an instance. /// Use the [src_loc!] macro to crate an instance.
pub struct SourceLocation { pub struct SourceLocation {

View File

@ -908,6 +908,8 @@ extern "C" {
pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void);
pub fn rb_zjit_get_page_size() -> u32; pub fn rb_zjit_get_page_size() -> u32;
pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8;
pub fn rb_RSTRING_LEN(str_: VALUE) -> ::std::os::raw::c_ulong;
pub fn rb_RSTRING_PTR(str_: VALUE) -> *mut ::std::os::raw::c_char;
pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint;
pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int;
pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE;

View File

@ -1,5 +1,9 @@
#[cfg(feature = "disasm")] use crate::asm::CodeBlock;
pub fn disasm_addr_range(start_addr: usize, end_addr: usize) -> String {
pub const BOLD_BEGIN: &str = "\x1b[1m";
pub const BOLD_END: &str = "\x1b[22m";
pub fn disasm_addr_range(cb: &CodeBlock, start_addr: usize, end_addr: usize) -> String {
use std::fmt::Write; use std::fmt::Write;
let mut out = String::from(""); let mut out = String::from("");
@ -34,7 +38,12 @@ pub fn disasm_addr_range(start_addr: usize, end_addr: usize) -> String {
// For each instruction in this block // For each instruction in this block
for insn in insns.as_ref() { for insn in insns.as_ref() {
// TODO: support comments // Comments for this block
if let Some(comment_list) = cb.comments_at(insn.address() as usize) {
for comment in comment_list {
writeln!(&mut out, " {BOLD_BEGIN}# {comment}{BOLD_END}").unwrap();
}
}
writeln!(&mut out, " {insn}").unwrap(); writeln!(&mut out, " {insn}").unwrap();
} }

View File

@ -10,6 +10,7 @@ mod cast;
mod virtualmem; mod virtualmem;
mod asm; mod asm;
mod backend; mod backend;
#[cfg(feature = "disasm")]
mod disasm; mod disasm;
mod options; mod options;
@ -93,7 +94,7 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *co
// Compile SSA IR into machine code // Compile SSA IR into machine code
let cb = ZJITState::get_code_block(); let cb = ZJITState::get_code_block();
match gen_function(cb, &ssa) { match gen_function(cb, &ssa, iseq) {
Some(start_ptr) => start_ptr.raw_ptr(cb), Some(start_ptr) => start_ptr.raw_ptr(cb),
// Compilation failed, continue executing in the interpreter only // Compilation failed, continue executing in the interpreter only

View File

@ -50,7 +50,7 @@ impl ZJITState {
); );
let mem_block = Rc::new(RefCell::new(mem_block)); let mem_block = Rc::new(RefCell::new(mem_block));
CodeBlock::new(mem_block.clone()) CodeBlock::new(mem_block.clone(), options.dump_disasm)
}; };
#[cfg(test)] #[cfg(test)]
let cb = CodeBlock::new_dummy(); let cb = CodeBlock::new_dummy();