Merge pull request #742 from rtfeldman/llvm-debug-info

add `--debug` flag
This commit is contained in:
Richard Feldman 2020-11-26 21:21:22 -05:00 committed by GitHub
commit e3380a535a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 352 additions and 50 deletions

View File

@ -16,6 +16,8 @@ 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 developtment you should be fine without it.
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).

View File

@ -24,6 +24,7 @@ pub fn build_file(
src_dir: PathBuf,
roc_file_path: PathBuf,
opt_level: OptLevel,
emit_debug_info: bool,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
@ -95,6 +96,7 @@ pub fn build_file(
Triple::host(),
&app_o_file,
opt_level,
emit_debug_info,
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());

View File

@ -14,6 +14,7 @@ use target_lexicon::Triple;
pub mod build;
pub mod repl;
pub static FLAG_DEBUG: &str = "debug";
pub static FLAG_OPTIMIZE: &str = "optimize";
pub static FLAG_ROC_FILE: &str = "ROC_FILE";
pub static DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
@ -34,6 +35,12 @@ pub fn build_app<'a>() -> App<'a> {
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.required(false),
)
)
.subcommand(App::new("run")
.about("Build and run a program")
@ -48,6 +55,12 @@ pub fn build_app<'a>() -> App<'a> {
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.required(false),
)
)
.subcommand(App::new("repl")
.about("Launch the interactive Read Eval Print Loop (REPL)")
@ -70,6 +83,8 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
} else {
OptLevel::Normal
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let path = Path::new(filename).canonicalize().unwrap();
let src_dir = path.parent().unwrap().canonicalize().unwrap();
@ -92,7 +107,14 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
}
});
let binary_path = build::build_file(target, src_dir, path, opt_level, LinkType::Executable)
let binary_path = build::build_file(
target,
src_dir,
path,
opt_level,
emit_debug_info,
LinkType::Executable,
)
.expect("TODO gracefully handle build_file failing");
if run_after_build {

View File

@ -20,6 +20,7 @@ pub fn gen_from_mono_module(
target: Triple,
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
) {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
@ -159,15 +160,78 @@ pub fn gen_from_mono_module(
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
if emit_debug_info {
module.strip_debug_info();
let mut app_ll_file = std::path::PathBuf::from(app_o_file);
app_ll_file.set_extension("ll");
let mut app_ll_dbg_file = std::path::PathBuf::from(app_o_file);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_bc_file = std::path::PathBuf::from(app_o_file);
app_bc_file.set_extension("bc");
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")
.env_clear()
.args(&[app_ll_file.to_str().unwrap()])
.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),
}
}
}
// assemble the .ll into a .bc
let _ = Command::new("llvm-as-10")
.env_clear()
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
let _ = Command::new("llc-10")
.env_clear()
.args(&[
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
} else {
// Emit the .o file
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine = target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
let target_machine =
target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
target_machine
.write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
}
}
pub struct FunctionIterator<'ctx> {

View File

@ -1725,10 +1725,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
let c_function_name: String =
format!("roc_{}_exposed", roc_function.get_name().to_str().unwrap());
let result = expose_function_to_host_help(env, roc_function, &c_function_name);
let subprogram = env.new_subprogram(&c_function_name);
result.set_subprogram(subprogram);
expose_function_to_host_help(env, roc_function, &c_function_name);
}
fn expose_function_to_host_help<'a, 'ctx, 'env>(
@ -1806,7 +1803,8 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap());
let size_function_name: String =
format!("roc_{}_size", roc_function.get_name().to_str().unwrap());
let size_function = env.module.add_function(
size_function_name.as_str(),
@ -1814,10 +1812,30 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
Some(Linkage::External),
);
let subprogram = env.new_subprogram(&size_function_name);
size_function.set_subprogram(subprogram);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
let func_scope = size_function.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
env.context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(env.context, loc);
let size: BasicValueEnum = return_type.size_of().unwrap().into();
builder.build_return(Some(&size));
@ -2012,6 +2030,9 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
env.module
.add_function(&wrapper_function_name, wrapper_function_type, None);
let subprogram = env.new_subprogram(wrapper_function_name);
wrapper_function.set_subprogram(subprogram);
// our exposed main function adheres to the C calling convention
wrapper_function.set_call_conventions(FAST_CALL_CONV);
@ -2021,6 +2042,23 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
let basic_block = context.append_basic_block(wrapper_function, "entry");
builder.position_at_end(basic_block);
let func_scope = wrapper_function.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
env.context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(env.context, loc);
let result = invoke_and_catch(
env,
wrapper_function,
@ -2119,8 +2157,9 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
// STEP 1: build function header
// e.g. `roc__main_1_Fx_caller`
let function_name = format!(
"{}_{}_caller",
"roc_{}_{}_caller",
def_name,
alias_symbol.ident_string(&env.interns)
);
@ -2200,7 +2239,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!(
"{}_{}_size",
"roc_{}_{}_size",
def_name,
alias_symbol.ident_string(&env.interns)
);

View File

@ -0,0 +1,7 @@
app Main provides [ rocMain ] imports [ Effect ]
rocMain : Effect.Effect {} as Fx
rocMain =
when List.len (Str.split "hello" "JJJJ there") is
_ -> Effect.putLine "Yay"

23
examples/balance/platform/Cargo.lock generated Normal file
View File

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View File

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View File

@ -0,0 +1,13 @@
platform folkertdev/foo
provides [ mainForHost ]
requires { main : Effect {} }
imports []
effects Effect
{
putChar : Int -> Effect {},
putLine : Str -> Effect {},
getLine : Effect Str
}
mainForHost : Effect {} as Fx
mainForHost = main

View File

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View File

@ -0,0 +1,118 @@
#![allow(non_snake_case)]
use roc_std::alloca;
use roc_std::RocCallResult;
use roc_std::RocStr;
use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "Main_rocMain_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "Main_rocMain_1_size"]
fn roc_main_size() -> i64;
#[link_name = "Main_rocMain_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "Main_rocMain_1_Fx_size"]
fn size_Fx() -> i64;
}
#[no_mangle]
pub fn roc_fx_putChar(foo: i64) -> () {
let character = foo as u8 as char;
print!("{}", character);
()
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
()
}
#[no_mangle]
pub fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len())
}
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
let size = size_Fx() as usize;
alloca::with_stack_bytes(size, |buffer| {
let buffer: *mut std::ffi::c_void = buffer;
let buffer: *mut u8 = buffer as *mut u8;
call_Fx(
function_pointer,
closure_data_ptr as *const u8,
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<i64>);
// match output.into() {
// Ok(v) => v,
// Err(e) => panic!("failed with {}", e),
// }
32
})
}
#[no_mangle]
pub fn rust_main() -> isize {
println!("Running Roc closure");
let start_time = SystemTime::now();
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let answer = unsafe {
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(()) => {
let function_pointer = {
// this is a pointer to the location where the function pointer is stored
// we pass just the function pointer
let temp = buffer.offset(8) as *const i64;
(*temp) as *const u8
};
let closure_data_ptr = buffer.offset(16);
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc execution took {:.4} ms",
duration.as_secs_f64() * 1000.0,
);
// Exit code
0
}

View File

@ -1,31 +1,18 @@
app "effect-example" provides [ main ] imports [ Effect, RBTree ]
toAndFro : Int
toAndFro =
empty : RBTree.Dict Int {}
empty = RBTree.empty
empty
|> (\d -> RBTree.insert 1 {} d)
|> RBTree.toList
|> List.len
app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
main : Effect.Effect {} as Fx
main =
# if RBTree.isEmpty empty then
if toAndFro == 2 then
Effect.putLine "Yay"
|> Effect.after (\{} -> Effect.getLine)
|> Effect.after (\line -> Effect.putLine line)
else
Effect.putLine "Nay"
when if 1 == 1 then True 3 else False 3.14 is
True 3 -> Effect.putLine "Yay"
_ -> Effect.putLine "Yay"
# Effect.always "Write a thing"
# |> Effect.map (\line -> Str.concat line "!")
# |> Effect.after (\line -> Effect.putLine line)
# main : Effect.Effect {} as Fx
# main =
# if RBTree.isEmpty (RBTree.insert 1 2 Empty) then
# Effect.putLine "Yay"
# |> Effect.after (\{} -> Effect.getLine)
# |> Effect.after (\line -> Effect.putLine line)
# else
# Effect.putLine "Nay"
#

View File

@ -1,4 +1,4 @@
interface RBTree exposes [ Dict, empty, size, singleton, isEmpty, insert, remove, update, fromList, toList ] imports []
interface RBTree exposes [ Dict, empty, size, singleton, isEmpty, insert, remove, update, fromList, toList, balance ] imports []
# The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ]

View File

@ -7,16 +7,16 @@ use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "Main_main_1_exposed"]
#[link_name = "roc__main_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "Main_main_1_size"]
#[link_name = "roc__main_1_size"]
fn roc_main_size() -> i64;
#[link_name = "Main_main_1_Fx_caller"]
#[link_name = "roc__main_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "Main_main_1_Fx_size"]
#[link_name = "roc__main_1_Fx_size"]
fn size_Fx() -> i64;
}
@ -60,10 +60,10 @@ unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<i64>);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(v) => v,
Ok(_) => 0,
Err(e) => panic!("failed with {}", e),
}
})
@ -95,7 +95,12 @@ pub fn rust_main() -> isize {
let closure_data_ptr = buffer.offset(16);
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
let result =
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout);
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);