Merge pull request #6184 from roc-lang/debug-info

Fix debug info
This commit is contained in:
Anton-4 2023-12-05 14:03:52 +01:00 committed by GitHub
commit a57f29e780
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 95 additions and 173 deletions

View file

@ -76,7 +76,7 @@ To run the test suite (via `cargo test`), you additionally need to install:
- [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal development you should be fine without it.
For emitting LLVM IR for debugging purposes, the `--emit-llvm-ir` flag can be used.
### libxcb libraries

View file

@ -50,7 +50,8 @@ pub const CMD_GLUE: &str = "glue";
pub const CMD_GEN_STUB_LIB: &str = "gen-stub-lib";
pub const CMD_PREPROCESS_HOST: &str = "preprocess-host";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_EMIT_LLVM_IR: &str = "emit-llvm-ir";
pub const FLAG_PROFILING: &str = "profiling";
pub const FLAG_BUNDLE: &str = "bundle";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
@ -102,9 +103,15 @@ pub fn build_app() -> Command {
.action(ArgAction::SetTrue)
.required(false);
let flag_debug = Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
let flag_emit_llvm_ir = Arg::new(FLAG_EMIT_LLVM_IR)
.long(FLAG_EMIT_LLVM_IR)
.help("Emit a `.ll` file containing the LLVM IR of the program")
.action(ArgAction::SetTrue)
.required(false);
let flag_profiling = Arg::new(FLAG_PROFILING)
.long(FLAG_PROFILING)
.help("Keep debug info in the final generated program even in optmized builds")
.action(ArgAction::SetTrue)
.required(false);
@ -163,7 +170,8 @@ pub fn build_app() -> Command {
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_emit_llvm_ir.clone())
.arg(flag_profiling.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
@ -212,7 +220,8 @@ pub fn build_app() -> Command {
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_emit_llvm_ir.clone())
.arg(flag_profiling.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
@ -234,7 +243,8 @@ pub fn build_app() -> Command {
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_emit_llvm_ir.clone())
.arg(flag_profiling.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
@ -247,7 +257,8 @@ pub fn build_app() -> Command {
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_emit_llvm_ir.clone())
.arg(flag_profiling.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
@ -376,7 +387,8 @@ pub fn build_app() -> Command {
.arg(flag_max_threads)
.arg(flag_opt_size)
.arg(flag_dev)
.arg(flag_debug)
.arg(flag_emit_llvm_ir)
.arg(flag_profiling)
.arg(flag_time)
.arg(flag_linker)
.arg(flag_prebuilt)
@ -696,7 +708,13 @@ pub fn build(
CodeGenBackend::Llvm(backend_mode)
};
let emit_debug_info = matches.get_flag(FLAG_DEBUG);
let emit_llvm_ir = matches.get_flag(FLAG_EMIT_LLVM_IR);
if emit_llvm_ir && !matches!(code_gen_backend, CodeGenBackend::Llvm(_)) {
user_error!("Cannot emit llvm ir while using a dev backend.");
}
let emit_debug_info = matches.get_flag(FLAG_PROFILING)
|| matches!(opt_level, OptLevel::Development | OptLevel::Normal);
let emit_timings = matches.get_flag(FLAG_TIME);
let threading = match matches.get_one::<usize>(FLAG_MAX_THREADS) {
@ -745,6 +763,7 @@ pub fn build(
backend: code_gen_backend,
opt_level,
emit_debug_info,
emit_llvm_ir,
};
let load_config = standard_load_config(&triple, build_ordering, threading);

View file

@ -85,6 +85,7 @@ pub struct CodeGenOptions {
pub backend: CodeGenBackend,
pub opt_level: OptLevel,
pub emit_debug_info: bool,
pub emit_llvm_ir: bool,
}
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
@ -101,6 +102,7 @@ pub fn gen_from_mono_module<'a>(
) -> GenFromMono<'a> {
let path = roc_file_path;
let debug = code_gen_options.emit_debug_info;
let emit_llvm_ir = code_gen_options.emit_llvm_ir;
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
@ -120,15 +122,23 @@ pub fn gen_from_mono_module<'a>(
wasm_dev_stack_bytes,
backend_mode,
),
CodeGenBackend::Llvm(backend_mode) => {
gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
CodeGenBackend::Llvm(backend_mode) => gen_from_mono_module_llvm(
arena,
loaded,
path,
target,
opt,
backend_mode,
debug,
emit_llvm_ir,
),
}
}
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[allow(clippy::too_many_arguments)]
fn gen_from_mono_module_llvm<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
@ -137,6 +147,7 @@ fn gen_from_mono_module_llvm<'a>(
opt_level: OptLevel,
backend_mode: LlvmBackendMode,
emit_debug_info: bool,
emit_llvm_ir: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@ -151,9 +162,6 @@ fn gen_from_mono_module_llvm<'a>(
let context = Context::create();
let module = arena.alloc(module_from_builtins(target, &context, "app"));
// strip Zig debug stuff
// module.strip_debug_info();
// mark our zig-defined builtins as internal
let app_ll_file = {
let mut temp = PathBuf::from(roc_file_path);
@ -245,8 +253,9 @@ fn gen_from_mono_module_llvm<'a>(
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
if !emit_debug_info {
module.strip_debug_info();
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -265,6 +274,11 @@ fn gen_from_mono_module_llvm<'a>(
);
}
if emit_llvm_ir {
eprintln!("Emitting LLVM IR to {}", &app_ll_file.display());
module.print_to_file(&app_ll_file).unwrap();
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -359,65 +373,6 @@ fn gen_from_mono_module_llvm<'a>(
assert!(bc_to_object.status.success(), "{bc_to_object:#?}");
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
} else if emit_debug_info {
module.strip_debug_info();
let mut app_ll_dbg_file = PathBuf::from(roc_file_path);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_o_file = PathBuf::from(roc_file_path);
app_o_file.set_extension("o");
use std::process::Command;
// write the ll code to a file, so we can modify it
module.print_to_file(&app_ll_file).unwrap();
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.args(["-instnamer", app_ll_file.to_str().unwrap()])
.output()
{
Ok(_) => {}
Err(error) => {
use std::io::ErrorKind;
match error.kind() {
ErrorKind::NotFound => internal_error!(
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
),
_ => internal_error!("{:?}", error),
}
}
}
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64
| Architecture::X86_32(_)
| Architecture::Aarch64(_)
| Architecture::Wasm32 => {
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let ll_to_object = Command::new("llc")
.args([
"-relocation-model=pic",
"-filetype=obj",
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(ll_to_object.stderr.is_empty(), "{ll_to_object:#?}");
}
_ => unreachable!(),
}
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
} else {
// Emit the .o file
@ -1326,6 +1281,7 @@ pub fn build_str_test<'a>(
backend: CodeGenBackend::Llvm(LlvmBackendMode::Binary),
opt_level: OptLevel::Normal,
emit_debug_info: false,
emit_llvm_ir: false,
};
let emit_timings = false;

View file

@ -7,7 +7,7 @@ const CrossTarget = std.zig.CrossTarget;
const Arch = std.Target.Cpu.Arch;
pub fn build(b: *Build) void {
// const mode = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
// const mode = b.standardOptimizeOption(.{ .preferred_optimize_mode = .Debug });
const mode = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
// Options
@ -58,6 +58,7 @@ fn generateLlvmIrFile(
) void {
const obj = b.addObject(.{ .name = object_name, .root_source_file = main_path, .optimize = mode, .target = target, .use_llvm = true });
obj.strip = true;
obj.disable_stack_probing = true;
// Generating the bin seems required to get zig to generate the llvm ir.
_ = obj.getEmittedBin();
@ -89,6 +90,7 @@ fn generateObjectFile(
obj.strip = true;
obj.link_function_sections = true;
obj.force_pic = true;
obj.disable_stack_probing = true;
const obj_file = obj.getEmittedBin();

View file

@ -1230,6 +1230,8 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx>(
let subprogram = env.new_subprogram(main_fn_name);
c_function.set_subprogram(subprogram);
debug_info_init!(env, c_function);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
@ -4367,6 +4369,8 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
let subprogram = env.new_subprogram(c_function_name);
c_function.set_subprogram(subprogram);
debug_info_init!(env, c_function);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
@ -4375,8 +4379,6 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
builder.position_at_end(entry);
debug_info_init!(env, c_function);
// drop the first argument, which is the pointer we write the result into
let args_vector = c_function.get_params();
let mut args = args_vector.as_slice();
@ -4422,6 +4424,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
let call_result = if env.mode.returns_roc_result() {
debug_assert_eq!(args.len(), roc_function.get_params().len());
let dbg_loc = builder.get_current_debug_location().unwrap();
let roc_wrapper_function =
make_exception_catcher(env, layout_interner, roc_function, return_layout);
debug_assert_eq!(
@ -4430,6 +4433,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
);
builder.position_at_end(entry);
builder.set_current_debug_location(dbg_loc);
let wrapped_layout = roc_call_result_layout(env.arena, return_layout);
call_direct_roc_function(
@ -4515,6 +4519,8 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
let subprogram = env.new_subprogram(c_function_name);
c_function.set_subprogram(subprogram);
debug_info_init!(env, c_function);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
@ -4523,8 +4529,6 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
builder.position_at_end(entry);
debug_info_init!(env, c_function);
// drop the final argument, which is the pointer we write the result into
let args_vector = c_function.get_params();
let mut args = args_vector.as_slice();
@ -4571,10 +4575,12 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
let (call_result, call_result_layout) = {
let last_block = builder.get_insert_block().unwrap();
let dbg_loc = builder.get_current_debug_location().unwrap();
let roc_wrapper_function =
make_exception_catcher(env, layout_interner, roc_function, return_layout);
builder.position_at_end(last_block);
builder.set_current_debug_location(dbg_loc);
let wrapper_result = roc_call_result_layout(env.arena, return_layout);
@ -4626,12 +4632,12 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
let subprogram = env.new_subprogram(&size_function_name);
size_function.set_subprogram(subprogram);
debug_info_init!(env, size_function);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
debug_info_init!(env, size_function);
let size: BasicValueEnum = return_type.size_of().unwrap().into();
builder.new_build_return(Some(&size));
@ -4717,6 +4723,8 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
let subprogram = env.new_subprogram(c_function_name);
c_function.set_subprogram(subprogram);
debug_info_init!(env, c_function);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
@ -4945,12 +4953,12 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx>(
let subprogram = env.new_subprogram(&size_function_name);
size_function.set_subprogram(subprogram);
debug_info_init!(env, size_function);
let entry = env.context.append_basic_block(size_function, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, size_function);
let return_type = match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => {
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
@ -5347,6 +5355,8 @@ fn make_exception_catching_wrapper<'a, 'ctx>(
let subprogram = env.new_subprogram(wrapper_function_name);
wrapper_function.set_subprogram(subprogram);
debug_info_init!(env, wrapper_function);
// The exposed main function must adhere to the C calling convention, but the wrapper can still be fastcc.
wrapper_function.set_call_conventions(FAST_CALL_CONV);
@ -5823,6 +5833,8 @@ fn build_proc_header<'a, 'ctx>(
let subprogram = env.new_subprogram(&fn_name);
fn_val.set_subprogram(subprogram);
debug_info_init!(env, fn_val);
if env.exposed_to_host.contains(&symbol) {
let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena);
expose_function_to_host(

View file

@ -469,6 +469,8 @@ fn build_clone_tag<'a, 'ctx>(
let subprogram = env.new_subprogram(&fn_name);
function_value.set_subprogram(subprogram);
debug_info_init!(env, function_value);
env.dibuilder.finalize();
build_clone_tag_help(

View file

@ -1,3 +1,4 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{add_func, get_panic_msg_ptr, get_panic_tag_ptr, BuilderExt, C_CALL_CONV};
use crate::llvm::build::{CCReturn, Env, FunctionSpec};
@ -254,6 +255,8 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let subprogram = env.new_subprogram("roc_panic");
fn_val.set_subprogram(subprogram);
debug_info_init!(env, fn_val);
env.dibuilder.finalize();
// Add a basic block for the entry point

View file

@ -156,6 +156,8 @@ impl<'ctx> PointerToRefcount<'ctx> {
let subprogram = env.new_subprogram(fn_name);
function_value.set_subprogram(subprogram);
debug_info_init!(env, function_value);
Self::build_decrement_function_body(env, function_value, alignment);
function_value
@ -1049,6 +1051,8 @@ pub fn build_header_help<'ctx>(
let subprogram = env.new_subprogram(fn_name);
fn_val.set_subprogram(subprogram);
debug_info_init!(env, fn_val);
env.dibuilder.finalize();
fn_val

View file

@ -57,7 +57,7 @@ fn roc_function<'a, 'b>(
let config = helpers::llvm::HelperConfig {
mode: LlvmBackendMode::GenTest,
ignore_problems: false,
add_debug_info: true,
emit_debug_info: true,
opt_level: OptLevel::Optimize,
};

View file

@ -86,7 +86,7 @@ fn roc_function<'a>(
let config = helpers::llvm::HelperConfig {
mode: LlvmBackendMode::GenTest,
ignore_problems: false,
add_debug_info: true,
emit_debug_info: true,
opt_level: OptLevel::Optimize,
};

View file

@ -233,9 +233,6 @@ fn create_llvm_module<'a>(
exposed_to_host: MutSet::default(),
};
// strip Zig debug stuff
module.strip_debug_info();
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&env);
@ -279,9 +276,6 @@ fn create_llvm_module<'a>(
env.dibuilder.finalize();
// strip all debug info: we don't use it at the moment and causes weird validation issues
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -325,7 +319,7 @@ fn create_llvm_module<'a>(
pub struct HelperConfig {
pub mode: LlvmBackendMode,
pub ignore_problems: bool,
pub add_debug_info: bool,
pub emit_debug_info: bool,
pub opt_level: OptLevel,
}
@ -343,56 +337,16 @@ pub fn helper<'a>(
let (main_fn_name, delayed_errors, module) =
create_llvm_module(arena, src, config, context, &target, function_kind);
let res_lib = if config.add_debug_info {
let module = annotate_with_debug_info(module, context);
llvm_module_to_dylib(&module, &target, config.opt_level)
} else {
llvm_module_to_dylib(module, &target, config.opt_level)
};
if !config.emit_debug_info {
module.strip_debug_info();
}
let res_lib = llvm_module_to_dylib(module, &target, config.opt_level);
let lib = res_lib.expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib)
}
fn annotate_with_debug_info<'ctx>(
module: &Module<'ctx>,
context: &'ctx inkwell::context::Context,
) -> Module<'ctx> {
use std::process::Command;
let app_ll_file = "/tmp/roc-debugir.ll";
let app_dbg_ll_file = "/tmp/roc-debugir.dbg.ll";
let app_bc_file = "/tmp/roc-debugir.bc";
// write the ll code to a file, so we can modify it
module.print_to_file(app_ll_file).unwrap();
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.args(["-instnamer", app_ll_file])
.output()
{
Ok(_) => {}
Err(error) => {
use std::io::ErrorKind;
match error.kind() {
ErrorKind::NotFound => panic!(
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
),
_ => panic!("{error:?}"),
}
}
}
Command::new("llvm-as")
.args([app_dbg_ll_file, "-o", app_bc_file])
.output()
.unwrap();
inkwell::module::Module::parse_bitcode_from_path(app_bc_file, context).unwrap()
}
#[allow(dead_code)]
fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat};
@ -533,7 +487,7 @@ where
let config = HelperConfig {
mode: LlvmBackendMode::WasmGenTest,
add_debug_info: false,
emit_debug_info: false,
ignore_problems,
opt_level: OPT_LEVEL,
};
@ -611,7 +565,7 @@ pub(crate) fn llvm_evals_to<T, U, F>(
let config = crate::helpers::llvm::HelperConfig {
mode: LlvmBackendMode::GenTest,
add_debug_info: false,
emit_debug_info: false,
ignore_problems,
opt_level: crate::helpers::llvm::OPT_LEVEL,
};

View file

@ -55,6 +55,7 @@ pub fn generate(
backend,
opt_level: OptLevel::Development,
emit_debug_info: false,
emit_llvm_ir: false,
};
let load_config = standard_load_config(

View file

@ -255,9 +255,6 @@ fn mono_module_to_dylib_llvm<'a>(
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();

View file

@ -713,9 +713,6 @@ pub fn expect_mono_module_to_dylib<'a>(
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();

View file

@ -64,29 +64,6 @@
curl # for wasm-bindgen-cli libcurl (see ./ci/www-repl.sh)
]);
# For debugging LLVM IR
debugir = pkgs.stdenv.mkDerivation {
name = "debugir";
src = pkgs.fetchFromGitHub {
owner = "vaivaswatha";
repo = "debugir";
rev = "b981e0b74872d9896ba447dd6391dfeb63332b80";
sha256 = "Gzey0SF0NZkpiObk5e29nbc41dn4Olv1dx+6YixaZH0=";
};
buildInputs = with pkgs; [ cmake libxml2 llvmPkgs.llvm.dev ];
buildPhase = ''
mkdir build
cd build
cmake -DLLVM_DIR=${llvmPkgs.llvm.dev} -DCMAKE_BUILD_TYPE=Release ../
cmake --build ../
cp ../debugir .
'';
installPhase = ''
mkdir -p $out/bin
cp debugir $out/bin
'';
};
sharedInputs = (with pkgs; [
# build libraries
cmake
@ -109,8 +86,6 @@
python3
libiconv # for examples/gui
libxkbcommon # for examples/gui
# debugir needs to be updated to llvm 15
# debugir # used in crates/compiler/build/src/program.rs
cargo-criterion # for benchmarks
simple-http-server # to view roc website when trying out edits
wasm-pack # for repl_wasm