diff --git a/Cargo.lock b/Cargo.lock index c5e3621ba0..72da692c47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3540,6 +3540,10 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_debug_flags" +version = "0.1.0" + [[package]] name = "roc_docs" version = "0.1.0" @@ -3765,6 +3769,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_mono", @@ -3806,6 +3811,7 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_exhaustive", "roc_module", @@ -4004,6 +4010,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_region", @@ -4017,6 +4024,7 @@ version = "0.1.0" dependencies = [ "bitflags", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_types", diff --git a/Cargo.toml b/Cargo.toml index 1e721c0d62..706c467a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "compiler/arena_pool", "compiler/test_gen", "compiler/roc_target", + "compiler/debug_flags", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", diff --git a/compiler/README.md b/compiler/README.md index d26c7e862e..d4c93aab03 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -165,23 +165,19 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`. -## Debugging intermediate representations +## Debugging the compiler -### Debugging the typechecker +Please see the [debug flags](./debug_flags/src/lib.rs) for information on how to +ask the compiler to emit debug information during various stages of compilation. -Setting the following environment variables: +There are some goals for more sophisticated debugging tools: -- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done, - before and after the unification. -- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification. -- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during - pretty-printing of types. +- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486. + Any interest in helping out here is greatly appreciated. -Note that this is only relevant during debug builds. Eventually we should have -some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486 -for one. +### General Tips -### The mono IR +#### Miscompilations If you observe a miscomplication, you may first want to check the generated mono IR for your code - maybe there was a problem during specialization or layout @@ -189,13 +185,16 @@ generation. One way to do this is to add a test to `test_mono/src/tests.rs` and run the tests with `cargo test -p test_mono`; this will write the mono IR to a file. -You may also want to set some or all of the following environment variables: +#### Typechecking errors -- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function - specialization to stdout -- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of - reset/reuse isntructions to stdout -- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference - counting instructions to stdout -- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the - information it knows about the mono IR whenever it is printed +First, try to minimize your reproduction into a test that fits in +[`solve_expr`](./solve/tests/solve_expr.rs). + +Once you've done this, check out the `ROC_PRINT_UNIFICATIONS` debug flag. It +will show you where type unification went right and wrong. This is usually +enough to figure out a fix for the bug. + +If that doesn't work and you know your error has something to do with ranks, +you may want to instrument `deep_copy_var_help` in [solve](./solve/src/solve.rs). + +If that doesn't work, chatting on Zulip is always a good strategy. diff --git a/compiler/debug_flags/Cargo.toml b/compiler/debug_flags/Cargo.toml new file mode 100644 index 0000000000..8aecfb22ec --- /dev/null +++ b/compiler/debug_flags/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "roc_debug_flags" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/compiler/debug_flags/src/lib.rs b/compiler/debug_flags/src/lib.rs new file mode 100644 index 0000000000..801ab53533 --- /dev/null +++ b/compiler/debug_flags/src/lib.rs @@ -0,0 +1,79 @@ +//! Flags for debugging the Roc compiler. +//! +//! Lists environment variable flags that can be enabled for verbose debugging features in debug +//! builds of the compiler. +//! +//! For example, I might define the following alias to run cargo with all unifications and +//! expanded type aliases printed: +//! +//! ```bash +//! alias cargo="\ +//! ROC_PRINT_UNIFICATIONS=1 \ +//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=1 \ +//! cargo" +//! ``` +//! +//! More generally, I have the following: +//! +//! ```bash +//! alias cargo="\ +//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=0 \ +//! ROC_PRINT_UNIFICATIONS=0 \ +//! ROC_PRINT_MISMATCHES=0 \ +//! ROC_PRINT_IR_AFTER_SPECIALIZATION=0 \ +//! ROC_PRINT_IR_AFTER_RESET_REUSE=0 \ +//! ROC_PRINT_IR_AFTER_REFCOUNT=0 \ +//! ROC_PRETTY_PRINT_IR_SYMBOLS=0 \ +//! cargo" +//! ``` +//! +//! Now you can turn debug flags on and off as you like. + +#[macro_export] +macro_rules! dbg_do { + ($flag:path, $expr:expr) => { + #[cfg(debug_assertions)] + { + if std::env::var($flag).unwrap_or("0".to_string()) != "0" { + $expr + } + } + }; +} + +macro_rules! flags { + ($($(#[doc = $doc:expr])+ $flag:ident)*) => {$( + $(#[doc = $doc])+ + pub static $flag: &str = stringify!($flag); + )*}; +} + +flags! { + // ===Types=== + + /// Expands the contents of aliases during pretty-printing of types. + ROC_PRETTY_PRINT_ALIAS_CONTENTS + + // ===Solve=== + + /// Prints type unifications, before and after they happen. + ROC_PRINT_UNIFICATIONS + + /// Prints all type mismatches hit during type unification. + ROC_PRINT_MISMATCHES + + // ===Mono=== + + /// Writes the mono IR to stderr after function specialization + ROC_PRINT_IR_AFTER_SPECIALIZATION + + /// Writes the mono IR to stderr after insertion of reset/reuse instructions + ROC_PRINT_IR_AFTER_RESET_REUSE + + /// Writes the mono IR to stderr after insertion of refcount instructions + ROC_PRINT_IR_AFTER_REFCOUNT + + /// Instructs the mono IR pretty printer to dump pretty symbols and verbose + /// layout information + ROC_PRETTY_PRINT_IR_SYMBOLS +} diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 28641d1c1b..df3ffe4ffb 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -21,6 +21,7 @@ roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_reporting = { path = "../../reporting" } +roc_debug_flags = { path = "../debug_flags" } morphic_lib = { path = "../../vendor/morphic_lib" } ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.8.0", features = ["collections"] } @@ -33,4 +34,4 @@ tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" -roc_test_utils = { path = "../../test_utils" } \ No newline at end of file +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 7fa6bf8292..27884c80fe 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -15,6 +15,10 @@ use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, }; +use roc_debug_flags::{ + dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, + ROC_PRINT_IR_AFTER_SPECIALIZATION, +}; use roc_error_macros::internal_error; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ @@ -1627,21 +1631,20 @@ fn start_tasks<'a>( Ok(()) } -#[cfg(debug_assertions)] -fn debug_print_ir(state: &State, flag: &str) { - if env::var(flag) != Ok("1".into()) { - return; - } +macro_rules! debug_print_ir { + ($state:expr, $flag:path) => { + dbg_do!($flag, { + let procs_string = $state + .procedures + .values() + .map(|proc| proc.to_pretty(200)) + .collect::>(); - let procs_string = state - .procedures - .values() - .map(|proc| proc.to_pretty(200)) - .collect::>(); + let result = procs_string.join("\n"); - let result = procs_string.join("\n"); - - println!("{}", result); + eprintln!("{}", result); + }) + }; } /// Report modules that are imported, but from which nothing is used @@ -2181,8 +2184,7 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_SPECIALIZATION"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_SPECIALIZATION); Proc::insert_reset_reuse_operations( arena, @@ -2192,13 +2194,11 @@ fn update<'a>( &mut state.procedures, ); - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_RESET_REUSE"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE); Proc::insert_refcount_operations(arena, &mut state.procedures); - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_REFCOUNT"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT); // This is not safe with the new non-recursive RC updates that we do for tag unions // diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index d47714ebdd..79b1ea060c 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -19,6 +19,7 @@ roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } roc_target = { path = "../roc_target" } roc_error_macros = {path="../../error_macros"} +roc_debug_flags = {path="../debug_flags"} ven_pretty = { path = "../../vendor/pretty" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1b0875c14a..790eaef5ce 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -10,6 +10,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_can::abilities::AbilitiesStore; use roc_can::expr::{AnnotatedMark, ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; +use roc_debug_flags::{dbg_do, ROC_PRETTY_PRINT_IR_SYMBOLS}; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -25,11 +26,11 @@ use roc_types::subs::{ use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; +#[inline(always)] pub fn pretty_print_ir_symbols() -> bool { - #[cfg(debug_assertions)] - if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { + dbg_do!(ROC_PRETTY_PRINT_IR_SYMBOLS, { return true; - } + }); false } diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 550cb5f333..e00c368701 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -10,6 +10,7 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_error_macros = {path="../../error_macros"} +roc_debug_flags = {path="../debug_flags"} ven_ena = { path = "../../vendor/ena" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 917e4a33e0..b266edddb8 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -449,15 +449,12 @@ fn write_content<'a>( ); } - // useful for debugging - if cfg!(debug_assertions) - && std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok() - { + roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRETTY_PRINT_ALIAS_CONTENTS, { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); write_content(env, ctx, content, subs, buf, parens); buf.push_str("]]"); - } + }); }), } } diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index d8622ed928..e92e2aa3e3 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -19,3 +19,6 @@ path = "../module" [dependencies.roc_types] path = "../types" + +[dependencies.roc_debug_flags] +path = "../debug_flags" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index f5c224bc09..9a5ca9a28a 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use roc_debug_flags::{dbg_do, ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS}; use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -11,14 +12,14 @@ use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, macro_rules! mismatch { () => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - } + }) Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -26,17 +27,16 @@ macro_rules! mismatch { } }}; ($msg:expr) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg); - println!(""); - } - + eprintln!($msg); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -47,16 +47,16 @@ macro_rules! mismatch { mismatch!($msg) }}; ($msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg, $($arg)*); - println!(""); - } + eprintln!($msg, $($arg)*); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -64,16 +64,16 @@ macro_rules! mismatch { } }}; (%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg, $($arg)*); - println!(""); - } + eprintln!($msg, $($arg)*); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)], @@ -298,7 +298,7 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option static mut UNIFICATION_DEPTH: usize = 0; - if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() { + dbg_do!(ROC_PRINT_UNIFICATIONS, { let prefix = match opt_outcome { None => "❔", Some(outcome) if outcome.mismatches.is_empty() => "✅", @@ -340,7 +340,7 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option ); unsafe { UNIFICATION_DEPTH = new_depth }; - } + }) } fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {