From a16de557d26ff522551871d6e768b27a65834db1 Mon Sep 17 00:00:00 2001 From: wpt967 Date: Thu, 15 Aug 2024 12:08:02 +0100 Subject: [PATCH] [llvm-context,solidity] Generate debug-info location information. When debug-info generation is enabled, construct a DebugInfo instance. Add a namespace stack to the DebugInfo structure to keep track of the names of named scopes, such as namespaces, objects and functions, enclosing a construct. Generate source level debug information for the functions defined when YUL is lowered to LLVM-IR. This includes the deploy_code and runtime functions generated by the compiler. Generate debug-location information for other constructs that may appear in a contract. --- crates/llvm-context/src/lib.rs | 1 + .../src/polkavm/context/debug_info.rs | 175 ++++++++++++++---- .../polkavm/context/function/declaration.rs | 4 + .../context/function/runtime/deploy_code.rs | 65 +++++++ .../polkavm/context/function/runtime/entry.rs | 59 ++++++ .../context/function/runtime/runtime_code.rs | 47 +++++ .../llvm-context/src/polkavm/context/mod.rs | 14 +- .../llvm-context/src/polkavm/context/tests.rs | 2 +- crates/solidity/src/project/contract/mod.rs | 19 ++ .../src/yul/parser/statement/assignment.rs | 32 ++++ .../src/yul/parser/statement/block.rs | 45 +++++ .../parser/statement/function_definition.rs | 77 +++++++- .../src/yul/parser/statement/object.rs | 23 +++ .../parser/statement/variable_declaration.rs | 34 +++- 14 files changed, 550 insertions(+), 47 deletions(-) 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/polkavm/context/debug_info.rs b/crates/llvm-context/src/polkavm/context/debug_info.rs index d90ad01a..1281b231 100644 --- a/crates/llvm-context/src/polkavm/context/debug_info.rs +++ b/crates/llvm-context/src/polkavm/context/debug_info.rs @@ -1,7 +1,42 @@ //! 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().map(|rc| rc.clone()) + } + + /// 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() + } + + pub fn len(&self) -> usize { + self.stack.len() + } +} /// The LLVM debug information. pub struct DebugInfo<'ctx> { @@ -9,6 +44,10 @@ pub struct DebugInfo<'ctx> { compile_unit: inkwell::debug_info::DICompileUnit<'ctx>, /// The debug info builder. builder: inkwell::debug_info::DebugInfoBuilder<'ctx>, + /// The debug info scope stack. Used when lowering to llvm-ir. + scope_stack: RefCell>, + // Stack of names of enclosing objects, functions and other namespaces. + namespace_stack: RefCell>, } impl<'ctx> DebugInfo<'ctx> { @@ -35,63 +74,127 @@ 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![]), } } - /// Creates a function info. + /// Create debug-info for a function. pub fn create_function( &self, + scope: inkwell::debug_info::DIScope<'ctx>, name: &str, + linkage_name: Option<&str>, + return_type: Option>, + parameter_types: &[inkwell::debug_info::DIType<'ctx>], + file: inkwell::debug_info::DIFile<'ctx>, + line_num: u32, + is_definition: bool, + is_local: bool, + is_optimized: bool, + flags: Option, ) -> anyhow::Result> { - let subroutine_type = self.builder.create_subroutine_type( - self.compile_unit.get_file(), - Some(self.create_type(revive_common::BIT_LENGTH_FIELD)?), - &[], - inkwell::debug_info::DIFlags::zero(), - ); + let di_flags = flags.unwrap_or(inkwell::debug_info::DIFlagsConstants::ZERO); + let ret_type = return_type.unwrap_or(self.create_word_type(flags)?.as_type()); + let subroutine_type = + self.builder + .create_subroutine_type(file, Some(ret_type), parameter_types, di_flags); let function = self.builder.create_function( - self.compile_unit.get_file().as_debug_info_scope(), + scope, name, - None, - self.compile_unit.get_file(), - 42, + linkage_name, + file, + line_num, subroutine_type, - true, - false, - 1, - inkwell::debug_info::DIFlags::zero(), - false, - ); - - self.builder.create_lexical_block( - function.as_debug_info_scope(), - self.compile_unit.get_file(), - 1, + is_local, + is_definition, 1, + di_flags, + is_optimized, ); 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 + } + + pub fn push_scope(&self, scope: DIScope<'ctx>) -> () { + self.scope_stack.borrow_mut().push(scope) + } + + pub fn pop_scope(&self) -> Option> { + self.scope_stack.borrow_mut().pop() + } + + pub fn num_scopes(&self) -> usize { + self.scope_stack.borrow().len() + } + + pub fn top_scope(&self) -> Option> { + self.scope_stack.borrow().top() + } + + pub fn push_namespace(&self, name: String) -> () { + self.namespace_stack.borrow_mut().push(name); + } + + pub fn pop_namespace(&self) -> Option { + self.namespace_stack.borrow_mut().pop() + } + + pub fn top_namespace(&self) -> Option { + self.namespace_stack.borrow().last().map(|rc| rc.clone()) + } + + // 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(separator); + }; + sep = true; + ret.push_str(s.as_str()) + } + if let Some(n) = name { + if sep { + ret.push(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..f07c5da9 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,6 +62,47 @@ 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 line_num: u32 = 0; + let column: u32 = 0; + let func_name: &str = runtime::FUNCTION_DEPLOY_CODE; + let linkage_name = dinfo.namespace_as_identifier(Some(func_name).clone()); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = di_file.as_debug_info_scope(); + let di_func_scope = dinfo.create_function( + di_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + di_file, + line_num, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + let _ = dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + let _ = func_value.set_subprogram(di_func_scope); + + let lexical_scope = di_builder + .create_lexical_block( + di_func_scope.as_debug_info_scope(), + dinfo.compilation_unit().get_file(), + line_num, + column, + ) + .as_debug_info_scope(); + let _ = dinfo.push_scope(lexical_scope); + } + self.inner.into_llvm(context)?; match context .basic_block() @@ -73,8 +116,30 @@ 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 line_num: u32 = 0x0beef; + let di_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + 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) + } context.build_return(None); + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + 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..6cbd3e2f 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,18 @@ 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") + .clone(); + let line_num = 0; + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_scope, None); + context.builder().set_current_debug_location(di_loc) + } + let is_deploy = context .current_function() .borrow() @@ -206,6 +220,46 @@ 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 line_num: u32 = 0; + let column: u32 = 0; + let func_name: &str = runtime::FUNCTION_ENTRY; + let linkage_name = dinfo.namespace_as_identifier(Some(func_name).clone()); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = di_file.as_debug_info_scope(); + let di_func_scope = dinfo.create_function( + di_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + di_file, + line_num, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + let _ = dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + let _ = func_value.set_subprogram(di_func_scope); + + let lexical_scope = di_builder + .create_lexical_block( + di_func_scope.as_debug_info_scope(), + dinfo.compilation_unit().get_file(), + line_num, + column, + ) + .as_debug_info_scope(); + let _ = dinfo.push_scope(lexical_scope); + } + Self::initialize_globals(context)?; Self::load_calldata(context)?; Self::leave_entry(context)?; @@ -214,6 +268,11 @@ 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(); + 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..5e4f8edb 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,6 +61,46 @@ 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 line_num: u32 = 0; + let column: u32 = 0; + let func_name: &str = runtime::FUNCTION_RUNTIME_CODE; + let linkage_name = dinfo.namespace_as_identifier(Some(func_name).clone()); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = di_file.as_debug_info_scope(); + let di_func_scope = dinfo.create_function( + di_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + di_file, + line_num, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + let _ = dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let func_value = context + .current_function() + .borrow() + .declaration() + .function_value(); + let _ = func_value.set_subprogram(di_func_scope); + + let lexical_scope = di_builder + .create_lexical_block( + di_func_scope.as_debug_info_scope(), + dinfo.compilation_unit().get_file(), + line_num, + column, + ) + .as_debug_info_scope(); + let _ = dinfo.push_scope(lexical_scope); + } + self.inner.into_llvm(context)?; match context .basic_block() @@ -74,6 +116,11 @@ 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(); + 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..76b67bf9 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; @@ -36,7 +36,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 +86,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 +196,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 +222,7 @@ where dependency_manager, include_metadata_hash, - // debug_info, + debug_info, debug_config, solidity_data: None, @@ -574,6 +575,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..cbf55812 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,28 @@ impl Contract { } _ => llvm.create_module(self.path.as_str()), }; + + let debug_info = if emit_debug_info { + 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, + ); + Some(DebugInfo::new(&module)) + } 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()); diff --git a/crates/solidity/src/yul/parser/statement/assignment.rs b/crates/solidity/src/yul/parser/statement/assignment.rs index 007378db..3369c915 100644 --- a/crates/solidity/src/yul/parser/statement/assignment.rs +++ b/crates/solidity/src/yul/parser/statement/assignment.rs @@ -115,6 +115,22 @@ 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") + .clone(); + 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 +158,22 @@ 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") + .clone(); + 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..42f97bff 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,47 @@ where } context.set_current_function(current_function.as_str())?; + + 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") + .clone(); + 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_parent_scope, + dinfo.compilation_unit().get_file(), + std::cmp::min(u32::MAX as usize, self.location.line) as u32, + 0, + ) + .as_debug_info_scope(); + let _ = dinfo.push_scope(di_block_scope); + + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_block_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") + .clone(); + 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 +235,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..fdfec14c 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; @@ -260,18 +262,80 @@ where Ok(()) } - fn into_llvm( + fn into_llvm<'ctx>( mut self, - context: &mut revive_llvm_context::PolkaVMContext, + context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>, ) -> 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 column: u32 = std::cmp::min(self.location.column, 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 linkage_name = dinfo.namespace_as_identifier(Some(func_name).clone()); + let di_file = dinfo.compilation_unit().get_file(); + let di_scope = di_file.as_debug_info_scope(); + let di_func_scope = dinfo.create_function( + di_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + di_file, + line_num, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + let _ = func_value.set_subprogram(di_func_scope); + + let lexical_scope = di_builder + .create_lexical_block( + di_func_scope.as_debug_info_scope(), + dinfo.compilation_unit().get_file(), + line_num, + column, + ) + .as_debug_info_scope(); + let _ = dinfo.push_scope(lexical_scope); + } + + let r#return = context.current_function().borrow().r#return(); match r#return { revive_llvm_context::PolkaVMFunctionReturn::None => {} revive_llvm_context::PolkaVMFunctionReturn::Primitive { pointer } => { + if let Some(dinfo) = context.debug_info() { + let di_builder = dinfo.builder(); + let line_num: u32 = std::cmp::min(self.location.line, u32::MAX as usize) as u32; + let di_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + 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) + } + 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 @@ -367,6 +431,11 @@ where } } + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_scope(); + 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..7aa27289 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,6 +217,19 @@ 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") + .clone(); + let object_scope = di_builder.create_namespace(di_parent_scope, object_name, true); + let _ = dinfo.push_scope(object_scope.as_debug_info_scope()); + let _ = dinfo.push_namespace(object_name.to_string()); + } + if self.identifier.ends_with("_deployed") { revive_llvm_context::PolkaVMRuntimeCodeFunction::new(self.code).into_llvm(context)?; } else { @@ -225,6 +240,14 @@ where object.into_llvm(context)?; } + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_namespace(); + let _ = dinfo.pop_scope(); + if dinfo.num_scopes() == 1 { + let _ = dinfo.builder().finalize(); + } + } + 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..8d8c9c3b 100644 --- a/crates/solidity/src/yul/parser/statement/variable_declaration.rs +++ b/crates/solidity/src/yul/parser/statement/variable_declaration.rs @@ -91,6 +91,26 @@ impl VariableDeclaration { } } +fn set_debug_location<'ctx, D>( + context: &revive_llvm_context::PolkaVMContext<'ctx, 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") + .clone(); + let di_loc = + di_builder.create_debug_location(context.llvm(), line_num, 0, di_parent_scope, None); + let _ = 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 +121,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 +142,7 @@ where .current_function() .borrow_mut() .yul_mut() - .insert_constant(identifier.inner, constant); + .insert_constant(identifier.inner.clone(), constant); } value.to_llvm() @@ -131,6 +157,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()