diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs index d5c59206..0d01cf84 100644 --- a/crates/llvm-context/src/lib.rs +++ b/crates/llvm-context/src/lib.rs @@ -16,6 +16,7 @@ pub use self::polkavm::context::argument::Argument as PolkaVMArgument; pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute; pub use self::polkavm::context::build::Build as PolkaVMBuild; pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType; +pub use self::polkavm::context::debug_info::DebugInfo; pub use self::polkavm::context::evmla_data::EVMLAData as PolkaVMContextEVMLAData; pub use self::polkavm::context::function::block::evmla_data::key::Key as PolkaVMFunctionBlockKey; pub use self::polkavm::context::function::block::evmla_data::EVMLAData as PolkaVMFunctionBlockEVMLAData; diff --git a/crates/llvm-context/src/optimizer/settings/mod.rs b/crates/llvm-context/src/optimizer/settings/mod.rs index 8d8df1ba..2fc11d18 100644 --- a/crates/llvm-context/src/optimizer/settings/mod.rs +++ b/crates/llvm-context/src/optimizer/settings/mod.rs @@ -9,7 +9,7 @@ use itertools::Itertools; use self::size_level::SizeLevel; -/// The LLVM optimizer settings. +/// The LLVM optimizer and code-gen settings. #[derive(Debug, Serialize, Deserialize, Clone, Eq)] pub struct Settings { /// The middle-end optimization level. @@ -28,6 +28,9 @@ pub struct Settings { pub is_verify_each_enabled: bool, /// Whether the LLVM `debug logging` option is enabled. pub is_debug_logging_enabled: bool, + + /// Whether to generate source-level debug information. + pub emit_debug_info: bool, } impl Settings { @@ -47,6 +50,8 @@ impl Settings { is_verify_each_enabled: false, is_debug_logging_enabled: false, + + emit_debug_info: false, } } @@ -58,6 +63,8 @@ impl Settings { is_verify_each_enabled: bool, is_debug_logging_enabled: bool, + + emit_debug_info: bool, ) -> Self { Self { level_middle_end, @@ -69,6 +76,8 @@ impl Settings { is_verify_each_enabled, is_debug_logging_enabled, + + emit_debug_info, } } @@ -220,6 +229,11 @@ impl Settings { pub fn is_system_request_memoization_disabled(&self) -> bool { self.is_system_request_memoization_disabled } + + /// Whether source-level debug information should be emitted. + pub fn emit_debug_info(&self) -> bool { + self.emit_debug_info + } } impl PartialEq for Settings { diff --git a/crates/llvm-context/src/polkavm/context/debug_info.rs b/crates/llvm-context/src/polkavm/context/debug_info.rs index d90ad01a..ef0a4853 100644 --- a/crates/llvm-context/src/polkavm/context/debug_info.rs +++ b/crates/llvm-context/src/polkavm/context/debug_info.rs @@ -1,7 +1,43 @@ //! The LLVM debug information. +use std::cell::RefCell; + use inkwell::debug_info::AsDIScope; -use num::Zero; +use inkwell::debug_info::DIScope; + +/// Debug info scope stack +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ScopeStack<'ctx> { + stack: Vec>, +} + +// Abstract the type of the DIScope stack. +impl<'ctx> ScopeStack<'ctx> { + pub fn from(item: DIScope<'ctx>) -> Self { + Self { stack: vec![item] } + } + + /// Return the top of the scope stack, or None if the stack is empty. + pub fn top(&self) -> Option> { + self.stack.last().copied() + } + + /// Push a scope onto the stack. + pub fn push(&mut self, scope: DIScope<'ctx>) { + self.stack.push(scope) + } + + /// Pop the scope at the top of the stack and return it. + /// Return None if the stack is empty. + pub fn pop(&mut self) -> Option> { + self.stack.pop() + } + + /// Return the number of scopes on the stack. + pub fn len(&self) -> usize { + self.stack.len() + } +} /// The LLVM debug information. pub struct DebugInfo<'ctx> { @@ -9,6 +45,10 @@ pub struct DebugInfo<'ctx> { compile_unit: inkwell::debug_info::DICompileUnit<'ctx>, /// The debug info builder. builder: inkwell::debug_info::DebugInfoBuilder<'ctx>, + /// Enclosing debug info scopes. + scope_stack: RefCell>, + // Names of enclosing objects, functions and other namespaces. + namespace_stack: RefCell>, } impl<'ctx> DebugInfo<'ctx> { @@ -35,19 +75,44 @@ impl<'ctx> DebugInfo<'ctx> { Self { compile_unit, builder, + scope_stack: RefCell::new(ScopeStack::from(compile_unit.as_debug_info_scope())), + namespace_stack: RefCell::new(vec![]), } } + /// Prepare an LLVM-IR module for debug-info generation + pub fn initialize_module( + &self, + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + ) { + let debug_metadata_value = llvm + .i32_type() + .const_int(inkwell::debug_info::debug_metadata_version() as u64, false); + module.add_basic_value_flag( + "Debug Info Version", + inkwell::module::FlagBehavior::Warning, + debug_metadata_value, + ); + self.push_scope(self.compilation_unit().get_file().as_debug_info_scope()); + } + + /// Finalize debug-info for an LLVM-IR module. + pub fn finalize_module(&self) { + self.builder().finalize() + } + /// Creates a function info. pub fn create_function( &self, name: &str, ) -> anyhow::Result> { + let flags = inkwell::debug_info::DIFlagsConstants::ZERO; let subroutine_type = self.builder.create_subroutine_type( self.compile_unit.get_file(), - Some(self.create_type(revive_common::BIT_LENGTH_FIELD)?), + Some(self.create_word_type(Some(flags))?.as_type()), &[], - inkwell::debug_info::DIFlags::zero(), + flags, ); let function = self.builder.create_function( @@ -60,7 +125,7 @@ impl<'ctx> DebugInfo<'ctx> { true, false, 1, - inkwell::debug_info::DIFlags::zero(), + flags, false, ); @@ -74,24 +139,91 @@ impl<'ctx> DebugInfo<'ctx> { Ok(function) } - /// Creates a primitive type info. - pub fn create_type( + /// Creates primitive integer type debug-info. + pub fn create_primitive_type( &self, bit_length: usize, - ) -> anyhow::Result> { + flags: Option, + ) -> anyhow::Result> { + let di_flags = flags.unwrap_or(inkwell::debug_info::DIFlagsConstants::ZERO); + let di_encoding: u32 = 0; + let type_name = String::from("U") + bit_length.to_string().as_str(); self.builder - .create_basic_type( - "U256", - bit_length as u64, - 0, - inkwell::debug_info::DIFlags::zero(), - ) - .map(|basic_type| basic_type.as_type()) + .create_basic_type(type_name.as_str(), bit_length as u64, di_encoding, di_flags) .map_err(|error| anyhow::anyhow!("Debug info error: {}", error)) } - /// Finalizes the builder. - pub fn finalize(&self) { - self.builder.finalize(); + /// Returns the debug-info model of word-sized integer types. + pub fn create_word_type( + &self, + flags: Option, + ) -> anyhow::Result> { + self.create_primitive_type(revive_common::BIT_LENGTH_WORD, flags) + } + + /// Return the DIBuilder. + pub fn builder(&self) -> &inkwell::debug_info::DebugInfoBuilder<'ctx> { + &self.builder + } + + /// Return the compilation unit. { + pub fn compilation_unit(&self) -> &inkwell::debug_info::DICompileUnit<'ctx> { + &self.compile_unit + } + + /// Push a debug-info scope onto the stack. + pub fn push_scope(&self, scope: DIScope<'ctx>) { + self.scope_stack.borrow_mut().push(scope) + } + + /// Pop the top of the debug-info scope stack and return it. + pub fn pop_scope(&self) -> Option> { + self.scope_stack.borrow_mut().pop() + } + + /// Return the top of the debug-info scope stack. + pub fn top_scope(&self) -> Option> { + self.scope_stack.borrow().top() + } + + /// Return the number of debug-info scopes on the scope stack. + pub fn num_scopes(&self) -> usize { + self.scope_stack.borrow().len() + } + + /// Push a name onto the namespace stack. + pub fn push_namespace(&self, name: String) { + self.namespace_stack.borrow_mut().push(name); + } + + /// Pop the top name off the namespace stack and return it. + pub fn pop_namespace(&self) -> Option { + self.namespace_stack.borrow_mut().pop() + } + + /// Return the top of the namespace stack. + pub fn top_namespace(&self) -> Option { + self.namespace_stack.borrow().last().cloned() + } + + // Get a string representation of the namespace stack. Optionally append the given name. + pub fn namespace_as_identifier(&self, name: Option<&str>) -> String { + let separator = "::"; + let mut ret = String::new(); + let mut sep = false; + for s in self.namespace_stack.borrow().iter() { + if sep { + ret.push_str(separator); + }; + sep = true; + ret.push_str(s) + } + if let Some(n) = name { + if sep { + ret.push_str(separator); + }; + ret.push_str(n); + } + ret } } diff --git a/crates/llvm-context/src/polkavm/context/function/declaration.rs b/crates/llvm-context/src/polkavm/context/function/declaration.rs index c4c41f61..f95ea2d2 100644 --- a/crates/llvm-context/src/polkavm/context/function/declaration.rs +++ b/crates/llvm-context/src/polkavm/context/function/declaration.rs @@ -17,4 +17,8 @@ impl<'ctx> Declaration<'ctx> { ) -> Self { Self { r#type, value } } + + pub fn function_value(&self) -> inkwell::values::FunctionValue<'ctx> { + self.value + } } diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/deploy_code.rs b/crates/llvm-context/src/polkavm/context/function/runtime/deploy_code.rs index 9e6a6272..da246598 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/deploy_code.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/deploy_code.rs @@ -8,6 +8,8 @@ use crate::polkavm::context::Context; use crate::polkavm::Dependency; use crate::polkavm::WriteLLVM; +use inkwell::debug_info::AsDIScope; + /// The deploy code function. /// Is a special function that is only used by the front-end generated code. #[derive(Debug)] @@ -60,7 +62,57 @@ where context.set_basic_block(context.current_function().borrow().entry_block()); context.set_code_type(CodeType::Deploy); + if let Some(dinfo) = context.debug_info() { + context.builder().unset_current_debug_location(); + let di_builder = dinfo.builder(); + let func_name: &str = runtime::FUNCTION_DEPLOY_CODE; + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + + let di_flags = inkwell::debug_info::DIFlagsConstants::PUBLIC; + let ret_type = dinfo.create_word_type(Some(di_flags))?.as_type(); + let subroutine_type = + di_builder.create_subroutine_type(di_file, Some(ret_type), &[], di_flags); + let linkage = dinfo.namespace_as_identifier(Some(func_name)); + let di_func_scope = di_builder.create_function( + di_scope, + func_name, + Some(linkage.as_str()), + di_file, + 0, + subroutine_type, + false, + true, + 1, + di_flags, + false, + ); + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + func_value.set_subprogram(di_func_scope); + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let di_loc = di_builder.create_debug_location( + context.llvm(), + 0, + 0, + di_func_scope.as_debug_info_scope(), + None, + ); + context.builder().set_current_debug_location(di_loc) + } + self.inner.into_llvm(context)?; + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + dinfo + .builder() + .create_debug_location(context.llvm(), 0, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } match context .basic_block() .get_last_instruction() @@ -73,8 +125,22 @@ where } context.set_basic_block(context.current_function().borrow().return_block()); + if let Some(dinfo) = context.debug_info() { + context.builder().unset_current_debug_location(); + let di_builder = dinfo.builder(); + let di_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope"); + let di_loc = + di_builder.create_debug_location(context.llvm(), 0, 0, di_parent_scope, None); + context.builder().set_current_debug_location(di_loc) + } context.build_return(None); + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(()) } } diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs index 5879597f..850d7257 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -9,6 +9,8 @@ use crate::polkavm::r#const::*; use crate::polkavm::Dependency; use crate::polkavm::WriteLLVM; +use inkwell::debug_info::AsDIScope; + /// The entry function. /// The function is a wrapper managing the runtime and deploy code calling logic. /// Is a special runtime function that is only used by the front-end generated code. @@ -126,6 +128,15 @@ impl Entry { where D: Dependency + Clone, { + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let di_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope"); + let di_loc = di_builder.create_debug_location(context.llvm(), 0, 0, di_scope, None); + context.builder().set_current_debug_location(di_loc) + } + let is_deploy = context .current_function() .borrow() @@ -206,14 +217,82 @@ where context.set_current_function(runtime::FUNCTION_ENTRY)?; context.set_basic_block(context.current_function().borrow().entry_block()); + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let func_name: &str = runtime::FUNCTION_ENTRY; + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + + let di_flags = inkwell::debug_info::DIFlagsConstants::PUBLIC; + let ret_type = dinfo.create_word_type(Some(di_flags))?.as_type(); + let subroutine_type = + di_builder.create_subroutine_type(di_file, Some(ret_type), &[], di_flags); + let linkage = dinfo.namespace_as_identifier(Some(func_name)); + let di_func_scope = di_builder.create_function( + di_scope, + func_name, + Some(linkage.as_str()), + di_file, + 0, + subroutine_type, + false, + true, + 1, + di_flags, + false, + ); + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + func_value.set_subprogram(di_func_scope); + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let di_loc = di_builder.create_debug_location( + context.llvm(), + 0, + 0, + di_func_scope.as_debug_info_scope(), + None, + ); + context.builder().set_current_debug_location(di_loc) + } + Self::initialize_globals(context)?; + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + dinfo + .builder() + .create_debug_location(context.llvm(), 0, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } Self::load_calldata(context)?; + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + dinfo + .builder() + .create_debug_location(context.llvm(), 0, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } Self::leave_entry(context)?; - + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + dinfo + .builder() + .create_debug_location(context.llvm(), 0, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } context.build_unconditional_branch(context.current_function().borrow().return_block()); context.set_basic_block(context.current_function().borrow().return_block()); context.build_unreachable(); + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(()) } } diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/runtime_code.rs b/crates/llvm-context/src/polkavm/context/function/runtime/runtime_code.rs index 5b9c05d4..04810f2d 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/runtime_code.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/runtime_code.rs @@ -8,6 +8,8 @@ use crate::polkavm::context::Context; use crate::polkavm::Dependency; use crate::polkavm::WriteLLVM; +use inkwell::debug_info::AsDIScope; + /// The runtime code function. /// Is a special function that is only used by the front-end generated code. #[derive(Debug)] @@ -59,7 +61,62 @@ where context.set_basic_block(context.current_function().borrow().entry_block()); context.set_code_type(CodeType::Runtime); + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let func_name: &str = runtime::FUNCTION_RUNTIME_CODE; + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + let di_func_scope = match func_value.get_subprogram() { + Some(scp) => scp, + None => { + let di_flags = inkwell::debug_info::DIFlagsConstants::PUBLIC; + let ret_type = dinfo.create_word_type(Some(di_flags))?.as_type(); + let subroutine_type = + di_builder.create_subroutine_type(di_file, Some(ret_type), &[], di_flags); + let linkage = dinfo.namespace_as_identifier(Some(func_name)); + di_builder.create_function( + di_scope, + func_name, + Some(linkage.as_str()), + di_file, + 0, + subroutine_type, + false, + true, + 1, + di_flags, + false, + ) + } + }; + + func_value.set_subprogram(di_func_scope); + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let di_loc = di_builder.create_debug_location( + context.llvm(), + 0, + 0, + di_func_scope.as_debug_info_scope(), + None, + ); + context.builder().set_current_debug_location(di_loc) + } + self.inner.into_llvm(context)?; + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + dinfo + .builder() + .create_debug_location(context.llvm(), 0, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } match context .basic_block() .get_last_instruction() @@ -74,6 +131,10 @@ where context.set_basic_block(context.current_function().borrow().return_block()); context.build_unreachable(); + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(()) } } diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 119d21e3..a928f361 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -5,7 +5,7 @@ pub mod argument; pub mod attribute; pub mod build; pub mod code_type; -// pub mod debug_info; +pub mod debug_info; pub mod evmla_data; pub mod function; pub mod global; @@ -21,6 +21,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use inkwell::debug_info::AsDIScope; use inkwell::types::BasicType; use inkwell::values::BasicValue; @@ -36,7 +37,7 @@ use self::address_space::AddressSpace; use self::attribute::Attribute; use self::build::Build; use self::code_type::CodeType; -// use self::debug_info::DebugInfo; +use self::debug_info::DebugInfo; use self::evmla_data::EVMLAData; use self::function::declaration::Declaration as FunctionDeclaration; use self::function::intrinsics::Intrinsics; @@ -86,7 +87,7 @@ where /// Whether to append the metadata hash at the end of bytecode. include_metadata_hash: bool, /// The debug info of the current module. - // debug_info: DebugInfo<'ctx>, + debug_info: Option>, /// The debug configuration telling whether to dump the needed IRs. debug_config: Option, @@ -196,6 +197,7 @@ where optimizer: Optimizer, dependency_manager: Option, include_metadata_hash: bool, + debug_info: Option>, debug_config: Option, ) -> Self { Self::link_stdlib_module(llvm, &module); @@ -221,7 +223,8 @@ where dependency_manager, include_metadata_hash, - // debug_info, + + debug_info, debug_config, solidity_data: None, @@ -434,6 +437,41 @@ where let value = self.module().add_function(name, r#type, linkage); + if let Some(dinfo) = self.debug_info() { + let di_builder = dinfo.builder(); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + + let di_flags = inkwell::debug_info::DIFlagsConstants::PUBLIC; + let ret_type = dinfo.create_word_type(Some(di_flags))?.as_type(); + let subroutine_type = + di_builder.create_subroutine_type(di_file, Some(ret_type), &[], di_flags); + let linkage = dinfo.namespace_as_identifier(Some(name)); + let di_func_scope = di_builder.create_function( + di_scope, + name, + Some(linkage.as_str()), + di_file, + 0, + subroutine_type, + false, + true, + 1, + di_flags, + false, + ); + value.set_subprogram(di_func_scope); + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let di_loc = di_builder.create_debug_location( + self.llvm(), + 0, + 0, + di_func_scope.as_debug_info_scope(), + None, + ); + self.builder().set_current_debug_location(di_loc) + } + let entry_block = self.llvm.append_basic_block(value, "entry"); let return_block = self.llvm.append_basic_block(value, "return"); @@ -477,6 +515,10 @@ where let function = Rc::new(RefCell::new(function)); self.functions.insert(name.to_string(), function.clone()); + if let Some(dinfo) = self.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(function) } @@ -574,6 +616,11 @@ where .expect("The dependency manager is unset") } + /// Returns the debug info. + pub fn debug_info(&self) -> Option<&DebugInfo<'ctx>> { + self.debug_info.as_ref() + } + /// Returns the debug config reference. pub fn debug_config(&self) -> Option<&DebugConfig> { self.debug_config.as_ref() diff --git a/crates/llvm-context/src/polkavm/context/tests.rs b/crates/llvm-context/src/polkavm/context/tests.rs index 54988221..1715005c 100644 --- a/crates/llvm-context/src/polkavm/context/tests.rs +++ b/crates/llvm-context/src/polkavm/context/tests.rs @@ -15,7 +15,7 @@ pub fn create_context( let module = llvm.create_module("test"); let optimizer = Optimizer::new(optimizer_settings); - Context::::new(llvm, module, optimizer, None, true, None) + Context::::new(llvm, module, optimizer, None, true, None, None) } #[test] diff --git a/crates/solidity/src/project/contract/mod.rs b/crates/solidity/src/project/contract/mod.rs index c59f98b4..4ec3df3e 100644 --- a/crates/solidity/src/project/contract/mod.rs +++ b/crates/solidity/src/project/contract/mod.rs @@ -14,6 +14,7 @@ use revive_llvm_context::PolkaVMWriteLLVM; use crate::build::contract::Contract as ContractBuild; use crate::project::Project; use crate::solc::version::Version as SolcVersion; +use revive_llvm_context::DebugInfo; use self::ir::IR; use self::metadata::Metadata; @@ -83,6 +84,7 @@ impl Contract { debug_config: Option, ) -> anyhow::Result { let llvm = inkwell::context::Context::create(); + let emit_debug_info = optimizer_settings.emit_debug_info(); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); let version = project.version.clone(); @@ -106,6 +108,7 @@ impl Contract { let module = match self.ir { IR::LLVMIR(ref llvm_ir) => { + // Create the output module let memory_buffer = inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy( llvm_ir.source.as_bytes(), @@ -116,12 +119,22 @@ impl Contract { } _ => llvm.create_module(self.path.as_str()), }; + + let debug_info = if emit_debug_info { + let debug_info = DebugInfo::new(&module); + debug_info.initialize_module(&llvm, &module); + Some(debug_info) + } else { + None + }; + let mut context = revive_llvm_context::PolkaVMContext::new( &llvm, module, optimizer, Some(project), include_metadata_hash, + debug_info, debug_config, ); context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default()); @@ -154,6 +167,19 @@ impl Contract { ) })?; + if let Some(dinfo) = context.debug_info() { + dinfo.finalize_module() + } + + #[cfg(debug_assertions)] + if let Err(err) = context.verify() { + eprintln!("Invalid LLVM module generated:"); + eprintln!("------------------------------"); + context.module().print_to_stderr(); + eprintln!("------------------------------"); + anyhow::bail!(err); + } + let build = context.build(self.path.as_str(), metadata_hash)?; Ok(ContractBuild::new( diff --git a/crates/solidity/src/resolc/arguments.rs b/crates/solidity/src/resolc/arguments.rs index 4bc5dd86..2824ed1c 100644 --- a/crates/solidity/src/resolc/arguments.rs +++ b/crates/solidity/src/resolc/arguments.rs @@ -149,6 +149,11 @@ pub struct Arguments { #[structopt(long = "suppress-warnings")] pub suppress_warnings: Option>, + /// Generate source based debug information in the output code file. This only has an effect + /// with the LLVM-IR code generator and is ignored otherwise. + #[structopt(short = 'g')] + pub emit_source_debug_info: bool, + /// Dump all IRs to files in the specified directory. /// Only for testing and debugging. #[structopt(long = "debug-output-dir")] diff --git a/crates/solidity/src/resolc/main.rs b/crates/solidity/src/resolc/main.rs index 20c808b0..3876cd2e 100644 --- a/crates/solidity/src/resolc/main.rs +++ b/crates/solidity/src/resolc/main.rs @@ -106,6 +106,7 @@ fn main_inner() -> anyhow::Result<()> { } optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each; optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging; + optimizer_settings.emit_debug_info = arguments.emit_source_debug_info; let include_metadata_hash = match arguments.metadata_hash { Some(metadata_hash) => { diff --git a/crates/solidity/src/yul/parser/statement/assignment.rs b/crates/solidity/src/yul/parser/statement/assignment.rs index 007378db..7af277c7 100644 --- a/crates/solidity/src/yul/parser/statement/assignment.rs +++ b/crates/solidity/src/yul/parser/statement/assignment.rs @@ -115,6 +115,19 @@ where mut self, context: &mut revive_llvm_context::PolkaVMContext, ) -> anyhow::Result<()> { + if let Some(dinfo) = context.debug_info() { + let di_parent_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_parent_scope, + None, + ); + context.builder().set_current_debug_location(di_loc) + }; + let value = match self.initializer.into_llvm(context)? { Some(value) => value, None => return Ok(()), @@ -142,6 +155,19 @@ where context.build_store(tuple_pointer, value.to_llvm())?; for (index, binding) in self.bindings.into_iter().enumerate() { + if let Some(dinfo) = context.debug_info() { + let di_parent_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_parent_scope, + None, + ); + context.builder().set_current_debug_location(di_loc) + }; + let field_pointer = context.build_gep( tuple_pointer, &[ diff --git a/crates/solidity/src/yul/parser/statement/block.rs b/crates/solidity/src/yul/parser/statement/block.rs index ffa2c63b..2bc99a9d 100644 --- a/crates/solidity/src/yul/parser/statement/block.rs +++ b/crates/solidity/src/yul/parser/statement/block.rs @@ -5,6 +5,8 @@ use std::collections::HashSet; use serde::Deserialize; use serde::Serialize; +use inkwell::debug_info::AsDIScope; + use crate::yul::error::Error; use crate::yul::lexer::token::lexeme::symbol::Symbol; use crate::yul::lexer::token::lexeme::Lexeme; @@ -154,8 +156,40 @@ where } context.set_current_function(current_function.as_str())?; + + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_block_scope = di_builder + .create_lexical_block( + di_scope, + dinfo.compilation_unit().get_file(), + std::cmp::min(self.location.line, u32::MAX as usize) as u32, + 0, + ) + .as_debug_info_scope(); + dinfo.push_scope(di_block_scope); + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_scope, None); + context.builder().set_current_debug_location(di_loc); + } + context.set_basic_block(current_block); for statement in local_statements.into_iter() { + if let Some(dinfo) = context.debug_info() { + let di_block_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = + std::cmp::min(statement.location().line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_block_scope, + None, + ); + context.builder().set_current_debug_location(di_loc); + } if context.basic_block().get_terminator().is_some() { break; } @@ -194,6 +228,10 @@ where } } + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(()) } } diff --git a/crates/solidity/src/yul/parser/statement/function_definition.rs b/crates/solidity/src/yul/parser/statement/function_definition.rs index ee167b57..d60c98c5 100644 --- a/crates/solidity/src/yul/parser/statement/function_definition.rs +++ b/crates/solidity/src/yul/parser/statement/function_definition.rs @@ -3,7 +3,9 @@ use std::collections::BTreeSet; use std::collections::HashSet; +use inkwell::debug_info::AsDIScope; use inkwell::types::BasicType; + use serde::Deserialize; use serde::Serialize; @@ -265,13 +267,61 @@ where context: &mut revive_llvm_context::PolkaVMContext, ) -> anyhow::Result<()> { context.set_current_function(self.identifier.as_str())?; - let r#return = context.current_function().borrow().r#return(); - context.set_basic_block(context.current_function().borrow().entry_block()); + + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + context.builder().unset_current_debug_location(); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + let func_name: &str = func_value + .get_name() + .to_str() + .unwrap_or(self.identifier.as_str()); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = dinfo.top_scope().expect("expected a debug-info scope"); + + let di_func_scope = match func_value.get_subprogram() { + Some(scp) => scp, + None => { + let di_flags = inkwell::debug_info::DIFlagsConstants::PUBLIC; + let ret_type = dinfo.create_word_type(Some(di_flags))?.as_type(); + let subroutine_type = + di_builder.create_subroutine_type(di_file, Some(ret_type), &[], di_flags); + let linkage = dinfo.namespace_as_identifier(Some(func_name)); + di_builder.create_function( + di_scope, + func_name, + Some(linkage.as_str()), + di_file, + 0, + subroutine_type, + false, + true, + 1, + di_flags, + false, + ) + } + }; + func_value.set_subprogram(di_func_scope); + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_loc_scope, None); + context.builder().set_current_debug_location(di_loc) + } + + let r#return = context.current_function().borrow().r#return(); match r#return { revive_llvm_context::PolkaVMFunctionReturn::None => {} revive_llvm_context::PolkaVMFunctionReturn::Primitive { pointer } => { let identifier = self.result.pop().expect("Always exists"); + let r#type = identifier.r#type.unwrap_or_default(); context.build_store(pointer, r#type.into_llvm(context).const_zero())?; context @@ -334,6 +384,18 @@ where } self.body.into_llvm(context)?; + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_loc_scope, + None, + ); + context.builder().set_current_debug_location(di_loc) + } match context .basic_block() .get_last_instruction() @@ -367,6 +429,10 @@ where } } + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + } + Ok(()) } } diff --git a/crates/solidity/src/yul/parser/statement/object.rs b/crates/solidity/src/yul/parser/statement/object.rs index 42f90251..1d20f147 100644 --- a/crates/solidity/src/yul/parser/statement/object.rs +++ b/crates/solidity/src/yul/parser/statement/object.rs @@ -2,6 +2,8 @@ use std::collections::HashSet; +use inkwell::debug_info::AsDIScope; + use serde::Deserialize; use serde::Serialize; @@ -215,15 +217,55 @@ where } fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> { + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let object_name: &str = self.identifier.as_str(); + let di_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope"); + let object_scope = di_builder.create_namespace(di_parent_scope, object_name, true); + dinfo.push_scope(object_scope.as_debug_info_scope()); + dinfo.push_namespace(object_name.to_string()); + } + if self.identifier.ends_with("_deployed") { revive_llvm_context::PolkaVMRuntimeCodeFunction::new(self.code).into_llvm(context)?; } else { revive_llvm_context::PolkaVMDeployCodeFunction::new(self.code).into_llvm(context)?; } + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_loc_scope, + None, + ); + context.builder().set_current_debug_location(di_loc) + } if let Some(object) = self.inner_object { object.into_llvm(context)?; } + if let Some(dinfo) = context.debug_info() { + let di_loc_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_loc = dinfo.builder().create_debug_location( + context.llvm(), + line_num, + 0, + di_loc_scope, + None, + ); + context.builder().set_current_debug_location(di_loc) + } + + if let Some(dinfo) = context.debug_info() { + dinfo.pop_namespace(); + dinfo.pop_scope(); + } Ok(()) } diff --git a/crates/solidity/src/yul/parser/statement/variable_declaration.rs b/crates/solidity/src/yul/parser/statement/variable_declaration.rs index 3733cca9..854e41dc 100644 --- a/crates/solidity/src/yul/parser/statement/variable_declaration.rs +++ b/crates/solidity/src/yul/parser/statement/variable_declaration.rs @@ -91,6 +91,23 @@ impl VariableDeclaration { } } +fn set_debug_location( + context: &revive_llvm_context::PolkaVMContext<'_, D>, + line_num: u32, +) -> anyhow::Result<()> +where + D: revive_llvm_context::PolkaVMDependency + Clone, +{ + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let di_parent_scope = dinfo.top_scope().expect("expected a debug-info scope"); + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_parent_scope, None); + context.builder().set_current_debug_location(di_loc); + } + Ok(()) +} + impl revive_llvm_context::PolkaVMWriteLLVM for VariableDeclaration where D: revive_llvm_context::PolkaVMDependency + Clone, @@ -101,7 +118,13 @@ where ) -> anyhow::Result<()> { if self.bindings.len() == 1 { let identifier = self.bindings.remove(0); - let r#type = identifier.r#type.unwrap_or_default().into_llvm(context); + if context.debug_info().is_some() { + let line_num: u32 = + std::cmp::min(identifier.location.line, u32::MAX as usize) as u32; + let _ = set_debug_location(context, line_num); + }; + let identifier_type = identifier.r#type.clone().unwrap_or_default(); + let r#type = identifier_type.into_llvm(context); let pointer = context.build_alloca(r#type, identifier.inner.as_str()); context .current_function() @@ -116,7 +139,7 @@ where .current_function() .borrow_mut() .yul_mut() - .insert_constant(identifier.inner, constant); + .insert_constant(identifier.inner.clone(), constant); } value.to_llvm() @@ -131,6 +154,10 @@ where } for (index, binding) in self.bindings.iter().enumerate() { + if context.debug_info().is_some() { + let line_num: u32 = std::cmp::min(binding.location.line, u32::MAX as usize) as u32; + let _ = set_debug_location(context, line_num); + }; let yul_type = binding .r#type .to_owned()