Skip to content

Commit

Permalink
[llvm-context, solidity] Add CLI option '-g' to generate debug inform…
Browse files Browse the repository at this point in the history
…ation.

Add command line option '-g' to generate source level debug information
in the output code. This only works with the LLVM-IR code generator.

Add flag 'emit_debug_info' to the llvm-context optimizer/code-gen
settings structure, to record the setting of the '-g' CLI option.

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 Oct 15, 2024
1 parent f0f344a commit 38e8e3b
Show file tree
Hide file tree
Showing 18 changed files with 444 additions and 27 deletions.
1 change: 1 addition & 0 deletions crates/llvm-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 15 additions & 1 deletion crates/llvm-context/src/optimizer/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -26,6 +26,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 {
Expand All @@ -44,6 +47,8 @@ impl Settings {

is_verify_each_enabled: false,
is_debug_logging_enabled: false,

emit_debug_info: false,
}
}

Expand All @@ -55,6 +60,8 @@ impl Settings {

is_verify_each_enabled: bool,
is_debug_logging_enabled: bool,

emit_debug_info: bool,
) -> Self {
Self {
level_middle_end,
Expand All @@ -65,6 +72,8 @@ impl Settings {

is_verify_each_enabled,
is_debug_logging_enabled,

emit_debug_info,
}
}

Expand Down Expand Up @@ -206,6 +215,11 @@ impl Settings {
pub fn is_fallback_to_size_enabled(&self) -> bool {
self.is_fallback_to_size_enabled
}

/// Whether source-level debug information should be emitted.
pub fn emit_debug_info(&self) -> bool {
self.emit_debug_info
}
}

impl PartialEq for Settings {
Expand Down
166 changes: 149 additions & 17 deletions crates/llvm-context/src/polkavm/context/debug_info.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
//! 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<DIScope<'ctx>>,
}

// 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<DIScope<'ctx>> {
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<DIScope<'ctx>> {
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> {
/// The compile unit.
compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
/// The debug info builder.
builder: inkwell::debug_info::DebugInfoBuilder<'ctx>,
/// Enclosing debug info scopes.
scope_stack: RefCell<ScopeStack<'ctx>>,
// Names of enclosing objects, functions and other namespaces.
namespace_stack: RefCell<Vec<String>>,
}

impl<'ctx> DebugInfo<'ctx> {
Expand All @@ -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<inkwell::debug_info::DISubprogram<'ctx>> {
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(
Expand All @@ -60,7 +125,7 @@ impl<'ctx> DebugInfo<'ctx> {
true,
false,
1,
inkwell::debug_info::DIFlags::zero(),
flags,
false,
);

Expand All @@ -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<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))
}

/// 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<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
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<DIScope<'ctx>> {
self.scope_stack.borrow_mut().pop()
}

/// Return the top of the debug-info scope stack.
pub fn top_scope(&self) -> Option<DIScope<'ctx>> {
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<String> {
self.namespace_stack.borrow_mut().pop()
}

/// Return the top of the namespace stack.
pub fn top_namespace(&self) -> Option<String> {
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
}
}
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 @@ -59,7 +61,18 @@ where
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy);

if context.debug_info().is_some() {
context.builder().unset_current_debug_location();
let func_scope = context
.set_current_function_debug_info(runtime::FUNCTION_DEPLOY_CODE, 0)?
.as_debug_info_scope();
context.debug_info().unwrap().push_scope(func_scope);
context.set_debug_location(0, 0, Some(func_scope))?;
}

self.inner.into_llvm(context)?;
context.set_debug_location(0, 0, None)?;

match context
.basic_block()
.get_last_instruction()
Expand All @@ -72,8 +85,13 @@ where
}

context.set_basic_block(context.current_function().borrow().return_block());
context.set_debug_location(0, 0, None)?;
context.build_return(None);

if let Some(dinfo) = context.debug_info() {
let _ = dinfo.pop_scope();
}

Ok(())
}
}
22 changes: 22 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 @@ -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.
Expand Down Expand Up @@ -141,6 +143,8 @@ impl Entry {
where
D: Dependency + Clone,
{
context.set_debug_location(0, 0, None)?;

let is_deploy = context
.current_function()
.borrow()
Expand Down Expand Up @@ -221,14 +225,32 @@ where
context.set_current_function(runtime::FUNCTION_ENTRY)?;
context.set_basic_block(context.current_function().borrow().entry_block());

if context.debug_info().is_some() {
context.builder().unset_current_debug_location();
let func_scope = context
.set_current_function_debug_info(runtime::FUNCTION_ENTRY, 0)?
.as_debug_info_scope();
context.debug_info().unwrap().push_scope(func_scope);
context.set_debug_location(0, 0, Some(func_scope))?;
}

Self::initialize_globals(context)?;
context.set_debug_location(0, 0, None)?;

Self::load_calldata(context)?;
context.set_debug_location(0, 0, None)?;

Self::leave_entry(context)?;
context.set_debug_location(0, 0, None)?;

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(())
}
}
Loading

0 comments on commit 38e8e3b

Please sign in to comment.