diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs index a78553aa9e..67e676581f 100644 --- a/cranelift/codegen/src/context.rs +++ b/cranelift/codegen/src/context.rs @@ -91,18 +91,21 @@ impl Context { /// /// This function calls `compile` and `emit_to_memory`, taking care to resize `mem` as /// needed, so it provides a safe interface. + /// + /// Returns the size of the function's code and the size of the read-only data. pub fn compile_and_emit( &mut self, isa: &TargetIsa, mem: &mut Vec, relocs: &mut RelocSink, traps: &mut TrapSink, - ) -> CodegenResult<()> { - let code_size = self.compile(isa)?; + ) -> CodegenResult<(CodeOffset, CodeOffset)> { + let total_size = self.compile(isa)?; let old_len = mem.len(); - mem.resize(old_len + code_size as usize, 0); - unsafe { self.emit_to_memory(isa, mem.as_mut_ptr().add(old_len), relocs, traps) }; - Ok(()) + mem.resize(old_len + total_size as usize, 0); + let code_size = + unsafe { self.emit_to_memory(isa, mem.as_mut_ptr().add(old_len), relocs, traps) }; + Ok((code_size, total_size - code_size)) } /// Compile the function. @@ -111,7 +114,7 @@ impl Context { /// represented by `isa`. This does not include the final step of emitting machine code into a /// code sink. /// - /// Returns the size of the function's code. + /// Returns the size of the function's code and read-only data. pub fn compile(&mut self, isa: &TargetIsa) -> CodegenResult { let _tt = timing::compile(); self.verify_if(isa)?; @@ -155,15 +158,19 @@ impl Context { /// /// This function is unsafe since it does not perform bounds checking on the memory buffer, /// and it can't guarantee that the `mem` pointer is valid. + /// + /// Returns the size of the function's code. pub unsafe fn emit_to_memory( &self, isa: &TargetIsa, mem: *mut u8, relocs: &mut RelocSink, traps: &mut TrapSink, - ) { + ) -> CodeOffset { let _tt = timing::binemit(); - isa.emit_function_to_memory(&self.func, &mut MemoryCodeSink::new(mem, relocs, traps)); + let mut sink = MemoryCodeSink::new(mem, relocs, traps); + isa.emit_function_to_memory(&self.func, &mut sink); + sink.code_size as CodeOffset } /// Run the verifier on the function. diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs index 4c53cc8828..d51687b4fe 100755 --- a/cranelift/src/clif-util.rs +++ b/cranelift/src/clif-util.rs @@ -30,6 +30,7 @@ use std::process; mod cat; mod compile; +mod disasm; mod print_cfg; mod utils; @@ -69,6 +70,19 @@ fn add_time_flag<'a>() -> clap::Arg<'a, 'a> { .help("Print pass timing report for test") } +fn add_size_flag<'a>() -> clap::Arg<'a, 'a> { + Arg::with_name("print-size") + .short("X") + .help("Print bytecode size") +} + +fn add_disasm_flag<'a>() -> clap::Arg<'a, 'a> { + Arg::with_name("disasm") + .long("disasm") + .short("D") + .help("Print machine code disassembly") +} + fn add_set_flag<'a>() -> clap::Arg<'a, 'a> { Arg::with_name("set") .long("set") @@ -120,6 +134,8 @@ fn add_wasm_or_compile<'a>(cmd: &str) -> clap::App<'a, 'a> { .arg(add_verbose_flag()) .arg(add_print_flag()) .arg(add_time_flag()) + .arg(add_size_flag()) + .arg(add_disasm_flag()) .arg(add_set_flag()) .arg(add_target_flag()) .arg(add_input_file_arg()) @@ -226,6 +242,7 @@ fn main() { compile::run( get_vec(rest_cmd.values_of("file")), rest_cmd.is_present("print"), + rest_cmd.is_present("disasm"), rest_cmd.is_present("time-passes"), &get_vec(rest_cmd.values_of("set")), target_val, @@ -247,6 +264,7 @@ fn main() { rest_cmd.is_present("just-decode"), rest_cmd.is_present("check-translation"), rest_cmd.is_present("print"), + rest_cmd.is_present("disasm"), &get_vec(rest_cmd.values_of("set")), target_val, rest_cmd.is_present("print-size"), diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 9903dd6161..f5e34c6417 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -1,67 +1,19 @@ //! CLI tool to read Cranelift IR files and compile them into native code. +use crate::disasm::{print_all, PrintRelocs, PrintTraps}; use crate::utils::{parse_sets_and_triple, read_to_string}; -use cfg_if::cfg_if; -use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::timing; use cranelift_codegen::Context; -use cranelift_codegen::{binemit, ir}; use cranelift_reader::parse_test; use std::path::Path; use std::path::PathBuf; -struct PrintRelocs { - flag_print: bool, -} - -impl binemit::RelocSink for PrintRelocs { - fn reloc_ebb( - &mut self, - where_: binemit::CodeOffset, - r: binemit::Reloc, - offset: binemit::CodeOffset, - ) { - if self.flag_print { - println!("reloc_ebb: {} {} at {}", r, offset, where_); - } - } - - fn reloc_external( - &mut self, - where_: binemit::CodeOffset, - r: binemit::Reloc, - name: &ir::ExternalName, - addend: binemit::Addend, - ) { - if self.flag_print { - println!("reloc_external: {} {} {} at {}", r, name, addend, where_); - } - } - - fn reloc_jt(&mut self, where_: binemit::CodeOffset, r: binemit::Reloc, jt: ir::JumpTable) { - if self.flag_print { - println!("reloc_jt: {} {} at {}", r, jt, where_); - } - } -} - -struct PrintTraps { - flag_print: bool, -} - -impl binemit::TrapSink for PrintTraps { - fn trap(&mut self, offset: binemit::CodeOffset, _srcloc: ir::SourceLoc, code: ir::TrapCode) { - if self.flag_print { - println!("trap: {} at {}", code, offset); - } - } -} - pub fn run( files: Vec, flag_print: bool, + flag_disasm: bool, flag_report_times: bool, flag_set: &[String], flag_isa: &str, @@ -73,6 +25,7 @@ pub fn run( let name = String::from(path.as_os_str().to_string_lossy()); handle_module( flag_print, + flag_disasm, flag_report_times, &path.to_path_buf(), &name, @@ -84,6 +37,7 @@ pub fn run( fn handle_module( flag_print: bool, + flag_disasm: bool, flag_report_times: bool, path: &PathBuf, name: &str, @@ -106,39 +60,21 @@ fn handle_module( let mut context = Context::new(); context.func = func; - // Compile and encode the result to machine code. - let total_size = context - .compile(isa) - .map_err(|err| pretty_error(&context.func, Some(isa), err))?; + let mut relocs = PrintRelocs::new(flag_print); + let mut traps = PrintTraps::new(flag_print); + let mut mem = vec![]; - let mut mem = vec![0; total_size as usize]; - let mut relocs = PrintRelocs { flag_print }; - let mut traps = PrintTraps { flag_print }; - let mut code_sink: binemit::MemoryCodeSink; - unsafe { - code_sink = binemit::MemoryCodeSink::new(mem.as_mut_ptr(), &mut relocs, &mut traps); - } - isa.emit_function_to_memory(&context.func, &mut code_sink); + // Compile and encode the result to machine code. + let (code_size, rodata_size) = context + .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps) + .map_err(|err| pretty_error(&context.func, Some(isa), err))?; if flag_print { println!("{}", context.func.display(isa)); } - if flag_print { - print!(".byte "); - let mut first = true; - for byte in &mem { - if first { - first = false; - } else { - print!(", "); - } - print!("{}", byte); - } - - println!(); - print_disassembly(isa, &mem[0..code_sink.code_size as usize])?; - print_readonly_data(&mem[code_sink.code_size as usize..total_size as usize]); + if flag_disasm { + print_all(isa, &mem, code_size, rodata_size, &relocs, &traps)?; } } @@ -148,99 +84,3 @@ fn handle_module( Ok(()) } - -fn print_readonly_data(mem: &[u8]) { - if mem.is_empty() { - return; - } - - println!("\nFollowed by {} bytes of read-only data:", mem.len()); - - for (i, byte) in mem.iter().enumerate() { - if i % 16 == 0 { - if i != 0 { - println!(); - } - print!("{:4}: ", i); - } - if i % 4 == 0 { - print!(" "); - } - print!("{:02x} ", byte); - } - println!(); -} - -cfg_if! { - if #[cfg(feature = "disas")] { - use capstone::prelude::*; - use target_lexicon::Architecture; - use std::fmt::Write; - - fn get_disassembler(isa: &TargetIsa) -> Result { - let cs = match isa.triple().architecture { - Architecture::Riscv32 | Architecture::Riscv64 => { - return Err(String::from("No disassembler for RiscV")) - } - Architecture::I386 | Architecture::I586 | Architecture::I686 => Capstone::new() - .x86() - .mode(arch::x86::ArchMode::Mode32) - .build(), - Architecture::X86_64 => Capstone::new() - .x86() - .mode(arch::x86::ArchMode::Mode64) - .build(), - Architecture::Arm - | Architecture::Armv4t - | Architecture::Armv5te - | Architecture::Armv7 - | Architecture::Armv7s => Capstone::new().arm().mode(arch::arm::ArchMode::Arm).build(), - Architecture::Thumbv6m | Architecture::Thumbv7em | Architecture::Thumbv7m => Capstone::new( - ).arm() - .mode(arch::arm::ArchMode::Thumb) - .build(), - Architecture::Aarch64 => Capstone::new() - .arm64() - .mode(arch::arm64::ArchMode::Arm) - .build(), - _ => return Err(String::from("Unknown ISA")), - }; - - cs.map_err(|err| err.to_string()) - } - - fn print_disassembly(isa: &TargetIsa, mem: &[u8]) -> Result<(), String> { - let mut cs = get_disassembler(isa)?; - - println!("\nDisassembly of {} bytes:", mem.len()); - let insns = cs.disasm_all(&mem, 0x0).unwrap(); - for i in insns.iter() { - let mut line = String::new(); - - write!(&mut line, "{:4x}:\t", i.address()).unwrap(); - - let mut bytes_str = String::new(); - for b in i.bytes() { - write!(&mut bytes_str, "{:02x} ", b).unwrap(); - } - write!(&mut line, "{:21}\t", bytes_str).unwrap(); - - if let Some(s) = i.mnemonic() { - write!(&mut line, "{}\t", s).unwrap(); - } - - if let Some(s) = i.op_str() { - write!(&mut line, "{}", s).unwrap(); - } - - println!("{}", line); - } - Ok(()) - } - } else { - fn print_disassembly(_: &TargetIsa, _: &[u8]) -> Result<(), String> { - println!("\nNo disassembly available."); - Ok(()) - } - } -} diff --git a/cranelift/src/disasm.rs b/cranelift/src/disasm.rs new file mode 100644 index 0000000000..fa55ab58a2 --- /dev/null +++ b/cranelift/src/disasm.rs @@ -0,0 +1,215 @@ +use cfg_if::cfg_if; +use cranelift_codegen::isa::TargetIsa; +use cranelift_codegen::{binemit, ir}; +use std::fmt::Write; + +pub struct PrintRelocs { + pub flag_print: bool, + pub text: String, +} + +impl PrintRelocs { + pub fn new(flag_print: bool) -> PrintRelocs { + Self { + flag_print, + text: String::new(), + } + } +} + +impl binemit::RelocSink for PrintRelocs { + fn reloc_ebb( + &mut self, + where_: binemit::CodeOffset, + r: binemit::Reloc, + offset: binemit::CodeOffset, + ) { + if self.flag_print { + write!( + &mut self.text, + "reloc_ebb: {} {} at {}\n", + r, offset, where_ + ) + .unwrap(); + } + } + + fn reloc_external( + &mut self, + where_: binemit::CodeOffset, + r: binemit::Reloc, + name: &ir::ExternalName, + addend: binemit::Addend, + ) { + if self.flag_print { + write!( + &mut self.text, + "reloc_external: {} {} {} at {}\n", + r, name, addend, where_ + ) + .unwrap(); + } + } + + fn reloc_jt(&mut self, where_: binemit::CodeOffset, r: binemit::Reloc, jt: ir::JumpTable) { + if self.flag_print { + write!(&mut self.text, "reloc_jt: {} {} at {}\n", r, jt, where_).unwrap(); + } + } +} + +pub struct PrintTraps { + pub flag_print: bool, + pub text: String, +} + +impl PrintTraps { + pub fn new(flag_print: bool) -> PrintTraps { + Self { + flag_print, + text: String::new(), + } + } +} + +impl binemit::TrapSink for PrintTraps { + fn trap(&mut self, offset: binemit::CodeOffset, _srcloc: ir::SourceLoc, code: ir::TrapCode) { + if self.flag_print { + write!(&mut self.text, "trap: {} at {}\n", code, offset).unwrap(); + } + } +} + +cfg_if! { + if #[cfg(feature = "disas")] { + use capstone::prelude::*; + use target_lexicon::Architecture; + + fn get_disassembler(isa: &TargetIsa) -> Result { + let cs = match isa.triple().architecture { + Architecture::Riscv32 | Architecture::Riscv64 => { + return Err(String::from("No disassembler for RiscV")) + } + Architecture::I386 | Architecture::I586 | Architecture::I686 => Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode32) + .build(), + Architecture::X86_64 => Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode64) + .build(), + Architecture::Arm + | Architecture::Armv4t + | Architecture::Armv5te + | Architecture::Armv7 + | Architecture::Armv7s => Capstone::new().arm().mode(arch::arm::ArchMode::Arm).build(), + Architecture::Thumbv6m | Architecture::Thumbv7em | Architecture::Thumbv7m => Capstone::new( + ).arm() + .mode(arch::arm::ArchMode::Thumb) + .build(), + Architecture::Aarch64 => Capstone::new() + .arm64() + .mode(arch::arm64::ArchMode::Arm) + .build(), + _ => return Err(String::from("Unknown ISA")), + }; + + cs.map_err(|err| err.to_string()) + } + + pub fn print_disassembly(isa: &TargetIsa, mem: &[u8]) -> Result<(), String> { + let mut cs = get_disassembler(isa)?; + + println!("\nDisassembly of {} bytes:", mem.len()); + let insns = cs.disasm_all(&mem, 0x0).unwrap(); + for i in insns.iter() { + let mut line = String::new(); + + write!(&mut line, "{:4x}:\t", i.address()).unwrap(); + + let mut bytes_str = String::new(); + let mut len = 0; + let mut first = true; + for b in i.bytes() { + write!(&mut bytes_str, "{:02x}", b).unwrap(); + if !first { + write!(&mut bytes_str, " ").unwrap(); + } + len += 1; + first = false; + } + write!(&mut line, "{:21}\t", bytes_str).unwrap(); + if len > 8 { + write!(&mut line, "\n\t\t\t\t").unwrap(); + } + + if let Some(s) = i.mnemonic() { + write!(&mut line, "{}\t", s).unwrap(); + } + + if let Some(s) = i.op_str() { + write!(&mut line, "{}", s).unwrap(); + } + + println!("{}", line); + } + Ok(()) + } + } else { + pub fn print_disassembly(_: &TargetIsa, _: &[u8]) -> Result<(), String> { + println!("\nNo disassembly available."); + Ok(()) + } + } +} + +pub fn print_all( + isa: &TargetIsa, + mem: &[u8], + code_size: u32, + rodata_size: u32, + relocs: &PrintRelocs, + traps: &PrintTraps, +) -> Result<(), String> { + print_bytes(&mem); + print_disassembly(isa, &mem[0..code_size as usize])?; + print_readonly_data(&mem[code_size as usize..(code_size + rodata_size) as usize]); + println!("\n{}\n{}", &relocs.text, &traps.text); + Ok(()) +} + +pub fn print_bytes(mem: &[u8]) { + print!(".byte "); + let mut first = true; + for byte in mem.iter() { + if first { + first = false; + } else { + print!(", "); + } + print!("{}", byte); + } + println!(); +} + +pub fn print_readonly_data(mem: &[u8]) { + if mem.is_empty() { + return; + } + + println!("\nFollowed by {} bytes of read-only data:", mem.len()); + + for (i, byte) in mem.iter().enumerate() { + if i % 16 == 0 { + if i != 0 { + println!(); + } + print!("{:4}: ", i); + } + if i % 4 == 0 { + print!(" "); + } + print!("{:02x} ", byte); + } + println!(); +} diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index af8e818697..67d0225f49 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -7,6 +7,7 @@ allow(clippy::too_many_arguments, clippy::cyclomatic_complexity) )] +use crate::disasm::{print_all, PrintRelocs, PrintTraps}; use crate::utils::{parse_sets_and_triple, read_to_end}; use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error}; use cranelift_codegen::settings::FlagsOrIsa; @@ -41,6 +42,7 @@ pub fn run( flag_just_decode: bool, flag_check_translation: bool, flag_print: bool, + flag_print_disasm: bool, flag_set: &[String], flag_triple: &str, flag_print_size: bool, @@ -57,6 +59,7 @@ pub fn run( flag_check_translation, flag_print, flag_print_size, + flag_print_disasm, flag_report_times, &path.to_path_buf(), &name, @@ -72,6 +75,7 @@ fn handle_module( flag_check_translation: bool, flag_print: bool, flag_print_size: bool, + flag_print_disasm: bool, flag_report_times: bool, path: &PathBuf, name: &str, @@ -100,7 +104,7 @@ fn handle_module( None => { return Err(String::from( "Error: the wasm command requires an explicit isa.", - )) + )); } }; @@ -157,27 +161,36 @@ fn handle_module( for (def_index, func) in dummy_environ.info.function_bodies.iter() { context.func = func.clone(); + let mut saved_sizes = None; let func_index = num_func_imports + def_index.index(); + let mut mem = vec![]; + let mut relocs = PrintRelocs::new(flag_print); + let mut traps = PrintTraps::new(flag_print); if flag_check_translation { if let Err(errors) = context.verify(fisa) { return Err(pretty_verifier_error(&context.func, fisa.isa, None, errors)); } } else { - let compiled_size = context - .compile(isa) + let (code_size, rodata_size) = context + .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps) .map_err(|err| pretty_error(&context.func, fisa.isa, err))?; + if flag_print_size { println!( "Function #{} code size: {} bytes", - func_index, compiled_size + func_index, code_size + rodata_size ); - total_module_code_size += compiled_size; + total_module_code_size += code_size + rodata_size; println!( "Function #{} bytecode size: {} bytes", func_index, dummy_environ.func_bytecode_sizes[def_index.index()] ); } + + if flag_print_disasm { + saved_sizes = Some((code_size, rodata_size)); + } } if flag_print { @@ -196,6 +209,10 @@ fn handle_module( vprintln!(flag_verbose, ""); } + if let Some((code_size, rodata_size)) = saved_sizes { + print_all(isa, &mem, code_size, rodata_size, &relocs, &traps)?; + } + context.clear(); }