diff --git a/crates/llvm-context/src/polkavm/context/debug_info.rs b/crates/llvm-context/src/polkavm/context/debug_info.rs index 65442f2d..ca56ce4c 100644 --- a/crates/llvm-context/src/polkavm/context/debug_info.rs +++ b/crates/llvm-context/src/polkavm/context/debug_info.rs @@ -4,7 +4,6 @@ use std::cell::RefCell; use inkwell::debug_info::AsDIScope; use inkwell::debug_info::DIScope; -use num::Zero; /// Debug info scope stack #[derive(Clone, Debug, PartialEq, Eq)] @@ -76,58 +75,70 @@ impl<'ctx> DebugInfo<'ctx> { } } - /// 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, + column: 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_WORD)?), - &[], - 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, + is_local, + is_definition, 1, - inkwell::debug_info::DIFlags::zero(), - false, + di_flags, + is_optimized, ); - self.builder.create_lexical_block( - function.as_debug_info_scope(), - self.compile_unit.get_file(), - 1, - 1, - ); + self.builder + .create_lexical_block(function.as_debug_info_scope(), file, line_num, column); 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)) } + /// 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) + } + /// Finalizes the builder. pub fn finalize(&self) { self.builder.finalize(); 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..8253764f 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,38 @@ where context.set_basic_block(context.current_function().borrow().entry_block()); context.set_code_type(CodeType::Deploy); + if let Some(dinfo) = context.debug_info() { + 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_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + let di_func_scope = dinfo.create_function( + di_parent_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + dinfo.compilation_unit().get_file(), + line_num, + column, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + 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); + } + self.inner.into_llvm(context)?; match context .basic_block() @@ -75,6 +109,10 @@ where context.set_basic_block(context.current_function().borrow().return_block()); 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 d2b494a7..b18c753a 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -10,6 +10,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. @@ -251,6 +253,32 @@ 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 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_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + let di_func_scope = dinfo.create_function( + di_parent_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + dinfo.compilation_unit().get_file(), + line_num, + column, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + dinfo.push_scope(di_func_scope.as_debug_info_scope()); + } + Self::initialize_globals(context)?; Self::load_calldata(context)?; Self::leave_entry(context)?; @@ -259,6 +287,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/function/runtime/runtime_code.rs b/crates/llvm-context/src/polkavm/context/function/runtime/runtime_code.rs index 5b9c05d4..8fc6a5fe 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,38 @@ 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 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_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + let di_func_scope = dinfo.create_function( + di_parent_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + dinfo.compilation_unit().get_file(), + line_num, + column, + true, + false, + false, + Some(inkwell::debug_info::DIFlagsConstants::PUBLIC), + )?; + 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); + } + self.inner.into_llvm(context)?; match context .basic_block() @@ -74,6 +108,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 bd6814d5..f5f1931f 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -486,6 +486,11 @@ where self.functions.get(name).cloned() } + /// Whether there is a current function. + pub fn has_current_function(&self) -> bool { + self.current_function.is_some() + } + /// Returns a shared reference to the current active function. pub fn current_function(&self) -> Rc>> { self.current_function @@ -575,11 +580,22 @@ where .expect("The dependency manager is unset") } + /// Whether there is debug info. + pub fn has_debug_info(&self) -> bool { + self.debug_info.is_some() + } + /// Returns the debug info. - pub fn debug_info(&self) -> Option<&DebugInfo> { + pub fn debug_info(&self) -> Option<&DebugInfo<'ctx>> { self.debug_info.as_ref() } + /// Set the debug info. + pub fn set_debug_info(&mut self, dinfo: DebugInfo<'ctx>) -> &Self { + self.debug_info = Some(dinfo); + self + } + /// Returns the debug config reference. pub fn debug_config(&self) -> Option<&DebugConfig> { self.debug_config.as_ref() diff --git a/crates/solidity/src/yul/parser/statement/assignment.rs b/crates/solidity/src/yul/parser/statement/assignment.rs index 007378db..de82a1ad 100644 --- a/crates/solidity/src/yul/parser/statement/assignment.rs +++ b/crates/solidity/src/yul/parser/statement/assignment.rs @@ -121,6 +121,24 @@ where }; if self.bindings.len() == 1 { + 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_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.bindings.remove(0); let pointer = context .current_function() @@ -134,6 +152,7 @@ where ) })?; context.build_store(pointer, value.to_llvm())?; + return Ok(()); } @@ -142,6 +161,24 @@ 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_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_loc = di_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..86531614 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; @@ -136,6 +138,28 @@ where let current_function = context.current_function().borrow().name().to_owned(); let current_block = context.basic_block(); + 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); + } + let mut functions = Vec::with_capacity(self.statements.len()); let mut local_statements = Vec::with_capacity(self.statements.len()); @@ -155,6 +179,7 @@ where context.set_current_function(current_function.as_str())?; context.set_basic_block(current_block); + for statement in local_statements.into_iter() { if context.basic_block().get_terminator().is_some() { break; @@ -194,6 +219,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..7ec0c82b 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,9 +262,9 @@ 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(); @@ -272,6 +274,7 @@ where 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 @@ -302,6 +305,41 @@ where } }; + if let Some(dinfo) = context.debug_info() { + 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_parent_scope = dinfo + .top_scope() + .expect("expected an existing debug-info scope") + .clone(); + let di_func_scope = dinfo.create_function( + di_parent_scope, + func_name, + Some(linkage_name.as_str()), + None, + &[], + dinfo.compilation_unit().get_file(), + line_num, + column, + 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 argument_types: Vec<_> = self .arguments .iter() @@ -367,6 +405,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..37dd73fe 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,25 @@ 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 _ = context + .debug_info() + .unwrap() + .push_scope(object_scope.as_debug_info_scope()); + context + .debug_info() + .unwrap() + .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 +246,12 @@ where object.into_llvm(context)?; } + if let Some(dinfo) = context.debug_info() { + let _ = dinfo.pop_namespace(); + let _ = dinfo.pop_scope(); + 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() diff --git a/crates/solidity/src/yul/parser/type.rs b/crates/solidity/src/yul/parser/type.rs index ab013968..cfd7d8f1 100644 --- a/crates/solidity/src/yul/parser/type.rs +++ b/crates/solidity/src/yul/parser/type.rs @@ -76,4 +76,32 @@ impl Type { Self::Custom(_) => context.word_type(), } } + + /// Get the length in bits of the type. + pub fn bit_length(&self) -> usize { + match self { + Self::Bool => revive_common::BIT_LENGTH_BOOLEAN, + Self::Int(bitlength) => bitlength.clone(), + Self::UInt(bitlength) => bitlength.clone(), + Self::Custom(_) => revive_common::BIT_LENGTH_WORD, + } + } + + /// Converts the type into its LLVM debug-info representation. + pub fn into_llvm_debug_info<'ctx, D>( + self, + context: &revive_llvm_context::PolkaVMContext<'ctx, D>, + ) -> anyhow::Result> + where + D: revive_llvm_context::PolkaVMDependency + Clone, + { + if let Some(dinfo) = context.debug_info() { + match self { + Self::Custom(_) => dinfo.create_word_type(None), + _ => dinfo.create_primitive_type(self.bit_length(), None), + } + } else { + anyhow::bail!("debug information is not enabled") + } + } }