From 3ccd408a266453a1024d55e8b279330804d1ee3d Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Mon, 25 Mar 2024 14:21:47 -0300 Subject: [PATCH 1/2] Improve cycle display for merged definitions --- src/hvmc_net/mutual_recursion.rs | 32 +++++++++++++++++-- tests/golden_tests.rs | 4 ++- .../golden_tests/mutual_recursion/merged.hvm | 7 ++++ .../mutual_recursion__merged.hvm.snap | 31 ++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 tests/golden_tests/mutual_recursion/merged.hvm create mode 100644 tests/snapshots/mutual_recursion__merged.hvm.snap diff --git a/src/hvmc_net/mutual_recursion.rs b/src/hvmc_net/mutual_recursion.rs index 1c4e5d9bf..462545b20 100644 --- a/src/hvmc_net/mutual_recursion.rs +++ b/src/hvmc_net/mutual_recursion.rs @@ -17,17 +17,19 @@ pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Di let cycles = graph.cycles(); if !cycles.is_empty() { - let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(&cycles)); + let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(cycles)); diagnostics.add_book_warning(msg.as_str(), WarningType::MutualRecursionCycle); } diagnostics.fatal(()) } -fn show_cycles(cycles: &[Vec]) -> String { +fn show_cycles(mut cycles: Vec>) -> String { let tail = &format!("\n{:ERR_INDENT_SIZE$}* ...", ""); let tail = if cycles.len() > 5 { tail } else { "" }; + cycles = cycles.into_iter().flat_map(combinations_from_merges).collect::>(); + let mut cycles = cycles .iter() .take(5) @@ -151,3 +153,29 @@ impl Debug for Graph { write!(f, "Graph{:?}", self.0) } } + +fn combinations_from_merges(cycle: Vec) -> Vec> { + let mut combinations: Vec> = vec![vec![]]; + for r#ref in cycle { + if let Some(index) = r#ref.find("_$_") { + let (left, right) = r#ref.split_at(index); + let right = &right[3 ..]; // skip "_$_" + let mut new_combinations = Vec::new(); + for combination in &combinations { + let mut left_comb = combination.clone(); + left_comb.push(left.to_string()); + new_combinations.push(left_comb); + + let mut right_comb = combination.clone(); + right_comb.push(right.to_string()); + new_combinations.push(right_comb); + } + combinations = new_combinations; + } else { + for combination in &mut combinations { + combination.push(r#ref.clone()); + } + } + } + combinations +} diff --git a/tests/golden_tests.rs b/tests/golden_tests.rs index 00c645967..cde91252f 100644 --- a/tests/golden_tests.rs +++ b/tests/golden_tests.rs @@ -349,7 +349,9 @@ fn mutual_recursion() { ..DiagnosticsConfig::new(Severity::Allow, true) }; let mut book = do_parse_book(code, path)?; - let res = compile_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?; + let mut opts = CompileOpts::light(); + opts.merge = true; + let res = compile_book(&mut book, opts, diagnostics_cfg, None)?; Ok(format!("{}{}", res.diagnostics, res.core_book)) }) } diff --git a/tests/golden_tests/mutual_recursion/merged.hvm b/tests/golden_tests/mutual_recursion/merged.hvm new file mode 100644 index 000000000..a2aa34743 --- /dev/null +++ b/tests/golden_tests/mutual_recursion/merged.hvm @@ -0,0 +1,7 @@ +(X x) = (Rec x) +(Y x) = (Rec x) + +(Rec x) = (X x) +(Rec2 x) = (X x) + +(Main) = (Y 0) diff --git a/tests/snapshots/mutual_recursion__merged.hvm.snap b/tests/snapshots/mutual_recursion__merged.hvm.snap new file mode 100644 index 000000000..9ca0d57de --- /dev/null +++ b/tests/snapshots/mutual_recursion__merged.hvm.snap @@ -0,0 +1,31 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/mutual_recursion/merged.hvm +--- +Errors: +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: + * Rec -> X -> Rec + * Rec -> Y -> Rec + * Rec2 -> X -> Rec2 + * Rec2 -> Y -> Rec2 + +Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. +If the float-combinators optimization is not on, we recommend activating it. + +You have 2 options: + +1. Easy Fix: use lazy-mode. + +Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has the advantage of not doing wasteful work, having an improved complexity class, and being compatible with unrestricted recursion. It has a small overhead though, and isn't compatible with GPU. + +2. Hard Fix: refactor the program to use lazy references. + +When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. +If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') + +For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') + +See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md. From 4bdf93e5c148c0aa3c117137e2207a27b6cd9245 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Mon, 25 Mar 2024 15:52:34 -0300 Subject: [PATCH 2/2] Refactor add merge separator constant --- src/hvmc_net/mutual_recursion.rs | 9 ++++++--- src/term/transform/definition_merge.rs | 4 +++- tests/golden_tests.rs | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/hvmc_net/mutual_recursion.rs b/src/hvmc_net/mutual_recursion.rs index 462545b20..11ccd33db 100644 --- a/src/hvmc_net/mutual_recursion.rs +++ b/src/hvmc_net/mutual_recursion.rs @@ -1,4 +1,7 @@ -use crate::diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE}; +use crate::{ + diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE}, + term::transform::definition_merge::MERGE_SEPARATOR, +}; use hvmc::ast::{Book, Tree}; use indexmap::{IndexMap, IndexSet}; use std::fmt::Debug; @@ -157,9 +160,9 @@ impl Debug for Graph { fn combinations_from_merges(cycle: Vec) -> Vec> { let mut combinations: Vec> = vec![vec![]]; for r#ref in cycle { - if let Some(index) = r#ref.find("_$_") { + if let Some(index) = r#ref.find(MERGE_SEPARATOR) { let (left, right) = r#ref.split_at(index); - let right = &right[3 ..]; // skip "_$_" + let right = &right[MERGE_SEPARATOR.len() ..]; // skip merge separator let mut new_combinations = Vec::new(); for combination in &combinations { let mut left_comb = combination.clone(); diff --git a/src/term/transform/definition_merge.rs b/src/term/transform/definition_merge.rs index 319d31202..f91caf56f 100644 --- a/src/term/transform/definition_merge.rs +++ b/src/term/transform/definition_merge.rs @@ -4,6 +4,8 @@ use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use std::collections::BTreeMap; +pub const MERGE_SEPARATOR: &str = "_$_"; + impl Book { /// Merges definitions that have the same structure into one definition. /// Expects variables to be linear. @@ -28,7 +30,7 @@ impl Book { for (term, equal_defs) in equal_terms { // def1_$_def2_$_def3 - let new_name = Name::new(equal_defs.iter().join("_$_")); + let new_name = Name::new(equal_defs.iter().join(MERGE_SEPARATOR)); // Builtin origin takes precedence let builtin = equal_defs.iter().any(|nam| self.defs[nam].builtin); diff --git a/tests/golden_tests.rs b/tests/golden_tests.rs index cde91252f..09609cc7e 100644 --- a/tests/golden_tests.rs +++ b/tests/golden_tests.rs @@ -349,7 +349,7 @@ fn mutual_recursion() { ..DiagnosticsConfig::new(Severity::Allow, true) }; let mut book = do_parse_book(code, path)?; - let mut opts = CompileOpts::light(); + let mut opts = CompileOpts::light(); opts.merge = true; let res = compile_book(&mut book, opts, diagnostics_cfg, None)?; Ok(format!("{}{}", res.diagnostics, res.core_book))