mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-22 09:08:00 +00:00
b238913a7d
This PR fixes and enhances debug info generation: 1. Adds line information for each lowered YUL builtin and the `if` statement. 2. Fixes the debug info source path to match the YUL file of the contract dumped to the `--debug-output-dir`. This improves inspection of the generated code a lot. Excerpt from `llvm-objdump -Sl /tmp/dbg/contracts_EndpointV2.sol.EndpointV2.so`: ``` ; /tmp/dbg/contracts_EndpointV2.sol.EndpointV2.yul:203 ; let _1 := memoryguard(0x80) 13c3e: 3aa5b023 sd a0, 0x3a0(a1) 13c42: 38a5bc23 sd a0, 0x398(a1) 13c46: 38a5b823 sd a0, 0x390(a1) 13c4a: 08000513 li a0, 0x80 13c4e: 38a5b423 sd a0, 0x388(a1) ; /tmp/dbg/contracts_EndpointV2.sol.EndpointV2.yul:204 ; mstore(64, _1) 13c52: 3885b503 ld a0, 0x388(a1) 13c56: 3905b603 ld a2, 0x390(a1) 13c5a: 3985b683 ld a3, 0x398(a1) 13c5e: 3a05b703 ld a4, 0x3a0(a1) 13c62: 38e5b023 sd a4, 0x380(a1) 13c66: 36d5bc23 sd a3, 0x378(a1) 13c6a: 36c5b823 sd a2, 0x370(a1) 13c6e: 36a5b423 sd a0, 0x368(a1) 13c72: 04000513 li a0, 0x40 13c76: 65d9 lui a1, 0x16 13c78: a605859b addiw a1, a1, -0x5a0 13c7c: 95a6 add a1, a1, s1 13c7e: 00000097 auipc ra, 0x0 13c82: 000080e7 jalr ra <__runtime+0x1de> 0000000000013c86 <.Lpcrel_hi27>: ; /tmp/dbg/contracts_EndpointV2.sol.EndpointV2.yul:205 ; if iszero(lt(calldatasize(), 4)) 13c86: 00000517 auipc a0, 0x0 13c8a: 00053503 ld a0, 0x0(a0) 13c8e: 4108 lw a0, 0x0(a0) 13c90: 4591 li a1, 0x4 13c92: 06b56263 bltu a0, a1, 0x13cf6 <.Lpcrel_hi27+0x70> 13c96: a009 j 0x13c98 <.Lpcrel_hi27+0x12> ``` --------- Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
169 lines
5.6 KiB
Rust
169 lines
5.6 KiB
Rust
//! The debug configuration.
|
|
|
|
pub mod ir_type;
|
|
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
|
|
use self::ir_type::IRType;
|
|
|
|
/// The debug configuration.
|
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
pub struct DebugConfig {
|
|
/// The directory to dump the IRs to.
|
|
pub output_directory: Option<PathBuf>,
|
|
/// Whether debug info should be emitted.
|
|
pub emit_debug_info: bool,
|
|
/// The YUL debug output file path.
|
|
///
|
|
/// Is expected to be configured when running in YUL mode.
|
|
pub contract_path: Option<PathBuf>,
|
|
/// The YUL input file path.
|
|
///
|
|
/// Is expected to be configured when not running in YUL mode.
|
|
pub yul_path: Option<PathBuf>,
|
|
}
|
|
|
|
impl DebugConfig {
|
|
/// A shortcut constructor.
|
|
pub const fn new(output_directory: Option<PathBuf>, emit_debug_info: bool) -> Self {
|
|
Self {
|
|
output_directory,
|
|
emit_debug_info,
|
|
contract_path: None,
|
|
yul_path: None,
|
|
}
|
|
}
|
|
|
|
/// Set the current YUL path.
|
|
pub fn set_yul_path(&mut self, yul_path: &Path) {
|
|
self.yul_path = yul_path.to_path_buf().into();
|
|
}
|
|
|
|
/// Set the current contract path.
|
|
pub fn set_contract_path(&mut self, contract_path: &str) {
|
|
self.contract_path = self.yul_source_path(contract_path);
|
|
}
|
|
|
|
/// Returns with the following precedence:
|
|
/// 1. The YUL source path if it was configured.
|
|
/// 2. The source YUL path from the debug output dir if it was configured.
|
|
/// 3. `None` if there is no debug output directory.
|
|
pub fn yul_source_path(&self, contract_path: &str) -> Option<PathBuf> {
|
|
if let Some(path) = self.yul_path.as_ref() {
|
|
return Some(path.clone());
|
|
}
|
|
|
|
self.output_directory.as_ref().map(|output_directory| {
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
|
|
file_path.push(full_file_name);
|
|
file_path
|
|
})
|
|
}
|
|
|
|
/// Dumps the Yul IR.
|
|
pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
|
|
if let Some(file_path) = self.yul_source_path(contract_path) {
|
|
std::fs::write(file_path, code)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Dumps the unoptimized LLVM IR.
|
|
pub fn dump_llvm_ir_unoptimized(
|
|
&self,
|
|
contract_path: &str,
|
|
module: &inkwell::module::Module,
|
|
) -> anyhow::Result<()> {
|
|
if let Some(output_directory) = self.output_directory.as_ref() {
|
|
let llvm_code = module.print_to_string().to_string();
|
|
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name =
|
|
Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
|
|
file_path.push(full_file_name);
|
|
std::fs::write(file_path, llvm_code)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Dumps the optimized LLVM IR.
|
|
pub fn dump_llvm_ir_optimized(
|
|
&self,
|
|
contract_path: &str,
|
|
module: &inkwell::module::Module,
|
|
) -> anyhow::Result<()> {
|
|
if let Some(output_directory) = self.output_directory.as_ref() {
|
|
let llvm_code = module.print_to_string().to_string();
|
|
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name =
|
|
Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
|
|
file_path.push(full_file_name);
|
|
std::fs::write(file_path, llvm_code)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Dumps the assembly.
|
|
pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
|
|
if let Some(output_directory) = self.output_directory.as_ref() {
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
|
|
file_path.push(full_file_name);
|
|
std::fs::write(file_path, code)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Dumps the code object.
|
|
pub fn dump_object(&self, contract_path: &str, code: &[u8]) -> anyhow::Result<()> {
|
|
if let Some(output_directory) = self.output_directory.as_ref() {
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name = Self::full_file_name(contract_path, None, IRType::SO);
|
|
file_path.push(full_file_name);
|
|
std::fs::write(file_path, code)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Dumps the stage output as a json file suitable for use with --recursive-process
|
|
#[cfg(debug_assertions)]
|
|
pub fn dump_stage_output(
|
|
&self,
|
|
contract_path: &str,
|
|
contract_suffix: Option<&str>,
|
|
stage_json: &Vec<u8>,
|
|
) -> anyhow::Result<()> {
|
|
if let Some(output_directory) = self.output_directory.as_ref() {
|
|
let mut file_path = output_directory.to_owned();
|
|
let full_file_name = Self::full_file_name(contract_path, contract_suffix, IRType::JSON);
|
|
file_path.push(full_file_name);
|
|
std::fs::write(file_path, stage_json)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Creates a full file name, given the contract full path, suffix, and extension.
|
|
fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String {
|
|
let mut full_file_name = contract_path.replace('/', "_").replace(':', ".");
|
|
if let Some(suffix) = suffix {
|
|
full_file_name.push('.');
|
|
full_file_name.push_str(suffix);
|
|
}
|
|
full_file_name.push('.');
|
|
full_file_name.push_str(ir_type.file_extension());
|
|
full_file_name
|
|
}
|
|
}
|