Add a --disasm option to clif-util wasm and compile (#713)

- Both the `wasm` and `compile` commands get this new subcommand, and it defaults to false.  This means that test runs with `wasm` can request disassembly (the main reason I am doing this) while test runs with `compile` now must request it, this changes current behavior.
- Switch to using context.compile_and_emit directly, and make the reloc and trap printers just accumulate output, not print it.  This allows us to factor the printing code into the disasm module.
This commit is contained in:
Lars T Hansen
2019-03-27 12:57:13 +01:00
committed by Benjamin Bouvier
parent 82c6867155
commit 141ccb9e9d
5 changed files with 283 additions and 186 deletions

View File

@@ -91,18 +91,21 @@ impl Context {
/// ///
/// This function calls `compile` and `emit_to_memory`, taking care to resize `mem` as /// This function calls `compile` and `emit_to_memory`, taking care to resize `mem` as
/// needed, so it provides a safe interface. /// 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( pub fn compile_and_emit(
&mut self, &mut self,
isa: &TargetIsa, isa: &TargetIsa,
mem: &mut Vec<u8>, mem: &mut Vec<u8>,
relocs: &mut RelocSink, relocs: &mut RelocSink,
traps: &mut TrapSink, traps: &mut TrapSink,
) -> CodegenResult<()> { ) -> CodegenResult<(CodeOffset, CodeOffset)> {
let code_size = self.compile(isa)?; let total_size = self.compile(isa)?;
let old_len = mem.len(); let old_len = mem.len();
mem.resize(old_len + code_size as usize, 0); 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) }; unsafe { self.emit_to_memory(isa, mem.as_mut_ptr().add(old_len), relocs, traps) };
Ok(()) Ok((code_size, total_size - code_size))
} }
/// Compile the function. /// 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 /// represented by `isa`. This does not include the final step of emitting machine code into a
/// code sink. /// 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<CodeOffset> { pub fn compile(&mut self, isa: &TargetIsa) -> CodegenResult<CodeOffset> {
let _tt = timing::compile(); let _tt = timing::compile();
self.verify_if(isa)?; 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, /// 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. /// 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( pub unsafe fn emit_to_memory(
&self, &self,
isa: &TargetIsa, isa: &TargetIsa,
mem: *mut u8, mem: *mut u8,
relocs: &mut RelocSink, relocs: &mut RelocSink,
traps: &mut TrapSink, traps: &mut TrapSink,
) { ) -> CodeOffset {
let _tt = timing::binemit(); 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. /// Run the verifier on the function.

View File

@@ -30,6 +30,7 @@ use std::process;
mod cat; mod cat;
mod compile; mod compile;
mod disasm;
mod print_cfg; mod print_cfg;
mod utils; mod utils;
@@ -69,6 +70,19 @@ fn add_time_flag<'a>() -> clap::Arg<'a, 'a> {
.help("Print pass timing report for test") .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> { fn add_set_flag<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("set") Arg::with_name("set")
.long("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_verbose_flag())
.arg(add_print_flag()) .arg(add_print_flag())
.arg(add_time_flag()) .arg(add_time_flag())
.arg(add_size_flag())
.arg(add_disasm_flag())
.arg(add_set_flag()) .arg(add_set_flag())
.arg(add_target_flag()) .arg(add_target_flag())
.arg(add_input_file_arg()) .arg(add_input_file_arg())
@@ -226,6 +242,7 @@ fn main() {
compile::run( compile::run(
get_vec(rest_cmd.values_of("file")), get_vec(rest_cmd.values_of("file")),
rest_cmd.is_present("print"), rest_cmd.is_present("print"),
rest_cmd.is_present("disasm"),
rest_cmd.is_present("time-passes"), rest_cmd.is_present("time-passes"),
&get_vec(rest_cmd.values_of("set")), &get_vec(rest_cmd.values_of("set")),
target_val, target_val,
@@ -247,6 +264,7 @@ fn main() {
rest_cmd.is_present("just-decode"), rest_cmd.is_present("just-decode"),
rest_cmd.is_present("check-translation"), rest_cmd.is_present("check-translation"),
rest_cmd.is_present("print"), rest_cmd.is_present("print"),
rest_cmd.is_present("disasm"),
&get_vec(rest_cmd.values_of("set")), &get_vec(rest_cmd.values_of("set")),
target_val, target_val,
rest_cmd.is_present("print-size"), rest_cmd.is_present("print-size"),

View File

@@ -1,67 +1,19 @@
//! CLI tool to read Cranelift IR files and compile them into native code. //! 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 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::print_errors::pretty_error;
use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::settings::FlagsOrIsa;
use cranelift_codegen::timing; use cranelift_codegen::timing;
use cranelift_codegen::Context; use cranelift_codegen::Context;
use cranelift_codegen::{binemit, ir};
use cranelift_reader::parse_test; use cranelift_reader::parse_test;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; 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( pub fn run(
files: Vec<String>, files: Vec<String>,
flag_print: bool, flag_print: bool,
flag_disasm: bool,
flag_report_times: bool, flag_report_times: bool,
flag_set: &[String], flag_set: &[String],
flag_isa: &str, flag_isa: &str,
@@ -73,6 +25,7 @@ pub fn run(
let name = String::from(path.as_os_str().to_string_lossy()); let name = String::from(path.as_os_str().to_string_lossy());
handle_module( handle_module(
flag_print, flag_print,
flag_disasm,
flag_report_times, flag_report_times,
&path.to_path_buf(), &path.to_path_buf(),
&name, &name,
@@ -84,6 +37,7 @@ pub fn run(
fn handle_module( fn handle_module(
flag_print: bool, flag_print: bool,
flag_disasm: bool,
flag_report_times: bool, flag_report_times: bool,
path: &PathBuf, path: &PathBuf,
name: &str, name: &str,
@@ -106,39 +60,21 @@ fn handle_module(
let mut context = Context::new(); let mut context = Context::new();
context.func = func; context.func = func;
// Compile and encode the result to machine code. let mut relocs = PrintRelocs::new(flag_print);
let total_size = context let mut traps = PrintTraps::new(flag_print);
.compile(isa) let mut mem = vec![];
.map_err(|err| pretty_error(&context.func, Some(isa), err))?;
let mut mem = vec![0; total_size as usize]; // Compile and encode the result to machine code.
let mut relocs = PrintRelocs { flag_print }; let (code_size, rodata_size) = context
let mut traps = PrintTraps { flag_print }; .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps)
let mut code_sink: binemit::MemoryCodeSink; .map_err(|err| pretty_error(&context.func, Some(isa), err))?;
unsafe {
code_sink = binemit::MemoryCodeSink::new(mem.as_mut_ptr(), &mut relocs, &mut traps);
}
isa.emit_function_to_memory(&context.func, &mut code_sink);
if flag_print { if flag_print {
println!("{}", context.func.display(isa)); println!("{}", context.func.display(isa));
} }
if flag_print { if flag_disasm {
print!(".byte "); print_all(isa, &mem, code_size, rodata_size, &relocs, &traps)?;
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]);
} }
} }
@@ -148,99 +84,3 @@ fn handle_module(
Ok(()) 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<Capstone, String> {
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(())
}
}
}

215
cranelift/src/disasm.rs Normal file
View File

@@ -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<Capstone, String> {
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!();
}

View File

@@ -7,6 +7,7 @@
allow(clippy::too_many_arguments, clippy::cyclomatic_complexity) 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 crate::utils::{parse_sets_and_triple, read_to_end};
use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error}; use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error};
use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::settings::FlagsOrIsa;
@@ -41,6 +42,7 @@ pub fn run(
flag_just_decode: bool, flag_just_decode: bool,
flag_check_translation: bool, flag_check_translation: bool,
flag_print: bool, flag_print: bool,
flag_print_disasm: bool,
flag_set: &[String], flag_set: &[String],
flag_triple: &str, flag_triple: &str,
flag_print_size: bool, flag_print_size: bool,
@@ -57,6 +59,7 @@ pub fn run(
flag_check_translation, flag_check_translation,
flag_print, flag_print,
flag_print_size, flag_print_size,
flag_print_disasm,
flag_report_times, flag_report_times,
&path.to_path_buf(), &path.to_path_buf(),
&name, &name,
@@ -72,6 +75,7 @@ fn handle_module(
flag_check_translation: bool, flag_check_translation: bool,
flag_print: bool, flag_print: bool,
flag_print_size: bool, flag_print_size: bool,
flag_print_disasm: bool,
flag_report_times: bool, flag_report_times: bool,
path: &PathBuf, path: &PathBuf,
name: &str, name: &str,
@@ -100,7 +104,7 @@ fn handle_module(
None => { None => {
return Err(String::from( return Err(String::from(
"Error: the wasm command requires an explicit isa.", "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() { for (def_index, func) in dummy_environ.info.function_bodies.iter() {
context.func = func.clone(); context.func = func.clone();
let mut saved_sizes = None;
let func_index = num_func_imports + def_index.index(); 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 flag_check_translation {
if let Err(errors) = context.verify(fisa) { if let Err(errors) = context.verify(fisa) {
return Err(pretty_verifier_error(&context.func, fisa.isa, None, errors)); return Err(pretty_verifier_error(&context.func, fisa.isa, None, errors));
} }
} else { } else {
let compiled_size = context let (code_size, rodata_size) = context
.compile(isa) .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps)
.map_err(|err| pretty_error(&context.func, fisa.isa, err))?; .map_err(|err| pretty_error(&context.func, fisa.isa, err))?;
if flag_print_size { if flag_print_size {
println!( println!(
"Function #{} code size: {} bytes", "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!( println!(
"Function #{} bytecode size: {} bytes", "Function #{} bytecode size: {} bytes",
func_index, func_index,
dummy_environ.func_bytecode_sizes[def_index.index()] dummy_environ.func_bytecode_sizes[def_index.index()]
); );
} }
if flag_print_disasm {
saved_sizes = Some((code_size, rodata_size));
}
} }
if flag_print { if flag_print {
@@ -196,6 +209,10 @@ fn handle_module(
vprintln!(flag_verbose, ""); vprintln!(flag_verbose, "");
} }
if let Some((code_size, rodata_size)) = saved_sizes {
print_all(isa, &mem, code_size, rodata_size, &relocs, &traps)?;
}
context.clear(); context.clear();
} }