Skip to content

Commit

Permalink
[llvm-context,solidity] Generate debug-info location information.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wpt967 committed Sep 11, 2024
1 parent b6eaa2f commit 8ad6527
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 37 deletions.
75 changes: 43 additions & 32 deletions crates/llvm-context/src/polkavm/context/debug_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::rc::Rc;

use inkwell::debug_info::AsDIScope;
use inkwell::debug_info::DIScope;
use num::Zero;

/// Debug info scope stack
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -79,58 +78,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<inkwell::debug_info::DIType<'ctx>>,
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<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
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<inkwell::debug_info::DIType<'ctx>> {
flags: Option<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
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<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
self.create_primitive_type(revive_common::BIT_LENGTH_WORD, flags)
}

/// Finalizes the builder.
pub fn finalize(&self) {
self.builder.finalize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ impl<'ctx> Declaration<'ctx> {
) -> Self {
Self { r#type, value }
}

pub fn function_value(&self) -> inkwell::values::FunctionValue<'ctx> {
self.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -60,6 +62,39 @@ 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")
.as_ref()
.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()
Expand All @@ -75,6 +110,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(())
}
}
33 changes: 33 additions & 0 deletions crates/llvm-context/src/polkavm/context/function/runtime/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -251,6 +253,33 @@ 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")
.as_ref()
.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)?;
Expand All @@ -259,6 +288,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(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -59,6 +61,39 @@ 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")
.as_ref()
.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()
Expand All @@ -74,6 +109,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(())
}
}
18 changes: 17 additions & 1 deletion crates/llvm-context/src/polkavm/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RefCell<Function<'ctx>>> {
self.current_function
Expand Down Expand Up @@ -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()
Expand Down
39 changes: 39 additions & 0 deletions crates/solidity/src/yul/parser/statement/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ 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")
.as_ref()
.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()
Expand All @@ -134,6 +153,7 @@ where
)
})?;
context.build_store(pointer, value.to_llvm())?;

return Ok(());
}

Expand All @@ -142,6 +162,25 @@ 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")
.as_ref()
.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,
&[
Expand Down
Loading

0 comments on commit 8ad6527

Please sign in to comment.