From b238913a7d90608af74b5d987ea1559600c89bd5 Mon Sep 17 00:00:00 2001 From: xermicus Date: Fri, 4 Jul 2025 19:56:52 +0200 Subject: [PATCH] emit YUL builtins debug line info and fix debug info source file (#358) 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 --- CHANGELOG.md | 12 ++++++ crates/llvm-context/src/debug_config/mod.rs | 43 +++++++++++++++++-- .../src/polkavm/context/debug_info.rs | 13 +++++- .../llvm-context/src/polkavm/context/mod.rs | 2 +- crates/resolc/src/lib.rs | 3 +- crates/resolc/src/project/contract/mod.rs | 3 +- .../statement/expression/function_call/mod.rs | 1 + .../src/parser/statement/if_conditional.rs | 1 + 8 files changed, 69 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c2afb..967bb91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ This is a development pre-release. Supported `polkadot-sdk` rev: `2503.0.1` +## v0.4.0 + +This is a development pre-release. + +Supported `polkadot-sdk` rev: `2503.0.1` + +### Added +- Line debug information per YUL builtin and for `if` statements. + +### Fixed +- The debug info source file matches the YUL path in `--debug-output-dir`, allowing tools to display the source line. + ## v0.3.0 This is a development pre-release. diff --git a/crates/llvm-context/src/debug_config/mod.rs b/crates/llvm-context/src/debug_config/mod.rs index e85c14d..af32a65 100644 --- a/crates/llvm-context/src/debug_config/mod.rs +++ b/crates/llvm-context/src/debug_config/mod.rs @@ -2,6 +2,7 @@ pub mod ir_type; +use std::path::Path; use std::path::PathBuf; use serde::Deserialize; @@ -16,6 +17,14 @@ pub struct DebugConfig { pub output_directory: Option, /// 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, + /// The YUL input file path. + /// + /// Is expected to be configured when not running in YUL mode. + pub yul_path: Option, } impl DebugConfig { @@ -24,15 +33,41 @@ impl DebugConfig { 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 { + 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(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::Yul); - file_path.push(full_file_name); + if let Some(file_path) = self.yul_source_path(contract_path) { std::fs::write(file_path, code)?; } diff --git a/crates/llvm-context/src/polkavm/context/debug_info.rs b/crates/llvm-context/src/polkavm/context/debug_info.rs index 5c61f04..595b788 100644 --- a/crates/llvm-context/src/polkavm/context/debug_info.rs +++ b/crates/llvm-context/src/polkavm/context/debug_info.rs @@ -51,11 +51,20 @@ pub struct DebugInfo<'ctx> { impl<'ctx> DebugInfo<'ctx> { /// A shortcut constructor. - pub fn new(module: &inkwell::module::Module<'ctx>) -> Self { + pub fn new( + module: &inkwell::module::Module<'ctx>, + debug_config: &crate::debug_config::DebugConfig, + ) -> Self { + let module_name = module.get_name().to_string_lossy(); + let yul_name = debug_config + .contract_path + .as_ref() + .map(|path| path.display().to_string()); + let (builder, compile_unit) = module.create_debug_info_builder( true, inkwell::debug_info::DWARFSourceLanguage::C, - module.get_name().to_string_lossy().as_ref(), + yul_name.as_deref().unwrap_or_else(|| module_name.as_ref()), "", "", false, diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 368c390..4560bc2 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -247,7 +247,7 @@ where let intrinsics = Intrinsics::new(llvm, &module); let llvm_runtime = LLVMRuntime::new(llvm, &module, &optimizer); let debug_info = debug_config.emit_debug_info.then(|| { - let debug_info = DebugInfo::new(&module); + let debug_info = DebugInfo::new(&module, &debug_config); debug_info.initialize_module(llvm, &module); debug_info }); diff --git a/crates/resolc/src/lib.rs b/crates/resolc/src/lib.rs index 145f89f..698e8b9 100644 --- a/crates/resolc/src/lib.rs +++ b/crates/resolc/src/lib.rs @@ -54,7 +54,7 @@ pub fn yul( solc: &mut T, optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, - debug_config: revive_llvm_context::DebugConfig, + mut debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], memory_config: SolcStandardJsonInputSettingsPolkaVMMemory, ) -> anyhow::Result { @@ -77,6 +77,7 @@ pub fn yul( let solc_validator = Some(&*solc); let project = Project::try_from_yul_path(path, solc_validator)?; + debug_config.set_yul_path(path); let build = project.compile( optimizer_settings, include_metadata_hash, diff --git a/crates/resolc/src/project/contract/mod.rs b/crates/resolc/src/project/contract/mod.rs index 334e0ad..c179afc 100644 --- a/crates/resolc/src/project/contract/mod.rs +++ b/crates/resolc/src/project/contract/mod.rs @@ -77,7 +77,7 @@ impl Contract { project: Project, optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, - debug_config: revive_llvm_context::DebugConfig, + mut debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], memory_config: SolcStandardJsonInputSettingsPolkaVMMemory, ) -> anyhow::Result { @@ -117,6 +117,7 @@ impl Contract { _ => llvm.create_module(self.path.as_str()), }; + debug_config.set_contract_path(&self.path); let mut context = revive_llvm_context::PolkaVMContext::new( &llvm, module, diff --git a/crates/yul/src/parser/statement/expression/function_call/mod.rs b/crates/yul/src/parser/statement/expression/function_call/mod.rs index 44bbf98..b825ca4 100644 --- a/crates/yul/src/parser/statement/expression/function_call/mod.rs +++ b/crates/yul/src/parser/statement/expression/function_call/mod.rs @@ -123,6 +123,7 @@ impl FunctionCall { D: revive_llvm_context::PolkaVMDependency + Clone, { let location = self.location; + context.set_debug_location(location.line, 0, None)?; match self.name { Name::UserDefined(name) => { diff --git a/crates/yul/src/parser/statement/if_conditional.rs b/crates/yul/src/parser/statement/if_conditional.rs index 6703152..a833b8b 100644 --- a/crates/yul/src/parser/statement/if_conditional.rs +++ b/crates/yul/src/parser/statement/if_conditional.rs @@ -53,6 +53,7 @@ where D: revive_llvm_context::PolkaVMDependency + Clone, { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> { + context.set_debug_location(self.location.line, 0, None)?; let condition = self .condition .into_llvm(context)?