Add manual toggle for optimizations

This commit is contained in:
imaqtkatt 2024-01-19 10:39:01 -03:00
parent 68bd80709d
commit 69380a3471
3 changed files with 86 additions and 37 deletions

View File

@ -22,22 +22,26 @@ pub use term::load_book::load_file_to_book;
pub fn check_book(mut book: Book) -> Result<(), String> {
// TODO: Do the checks without having to do full compilation
compile_book(&mut book, OptimizationLevel::Light)?;
compile_book(&mut book, Opts::light())?;
Ok(())
}
pub fn compile_book(book: &mut Book, opt_level: OptimizationLevel) -> Result<CompileResult, String> {
let (main, warnings) = desugar_book(book, opt_level)?;
pub fn compile_book(book: &mut Book, opts: Opts) -> Result<CompileResult, String> {
let (main, warnings) = desugar_book(book, opts)?;
let (nets, hvmc_names, labels) = book_to_nets(book, main);
let mut core_book = nets_to_hvmc(nets, &hvmc_names)?;
pre_reduce_book(&mut core_book, opt_level >= OptimizationLevel::Heavy)?;
if opt_level >= OptimizationLevel::Heavy {
pre_reduce_book(&mut core_book, opts.pre_reduce)?;
if opts.prune {
prune_defs(&mut core_book);
}
Ok(CompileResult { core_book, hvmc_names, labels, warnings })
}
pub fn desugar_book(book: &mut Book, opt_level: OptimizationLevel) -> Result<(DefId, Vec<Warning>), String> {
pub fn desugar_book(
book: &mut Book,
Opts { eta, ref_to_ref, prune, supercombinators, .. }: Opts,
) -> Result<(DefId, Vec<Warning>), String> {
let mut warnings = Vec::new();
let main = book.check_has_main()?;
book.check_shared_names()?;
@ -50,13 +54,15 @@ pub fn desugar_book(book: &mut Book, opt_level: OptimizationLevel) -> Result<(De
book.check_unbound_vars()?;
book.make_var_names_unique();
book.linearize_vars();
book.eta_reduction(opt_level >= OptimizationLevel::Heavy);
book.detach_supercombinators(main);
if opt_level >= OptimizationLevel::Heavy {
book.eta_reduction(eta);
if supercombinators {
book.detach_supercombinators(main);
}
if ref_to_ref {
book.simplify_ref_to_ref()?;
}
book.simplify_main_ref(main);
if opt_level >= OptimizationLevel::Heavy {
if prune {
book.prune(main);
}
Ok((main, warnings))
@ -82,9 +88,9 @@ pub fn run_book(
debug: bool,
linear: bool,
skip_warnings: bool,
opt_level: OptimizationLevel,
opts: Opts,
) -> Result<(Term, DefNames, RunInfo), String> {
let CompileResult { core_book, hvmc_names, labels, warnings } = compile_book(&mut book, opt_level)?;
let CompileResult { core_book, hvmc_names, labels, warnings } = compile_book(&mut book, opts)?;
if !warnings.is_empty() {
let warnings = warnings.iter().join("\n");
@ -152,17 +158,55 @@ pub fn total_rewrites(rwrts: &Rewrites) -> usize {
rwrts.anni + rwrts.comm + rwrts.eras + rwrts.dref + rwrts.oper
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum OptimizationLevel {
/// The minimum amount of transformations to produce valid hvmc outputs.
Light,
/// More aggressive optimizations.
Heavy,
#[derive(Clone, Copy, Debug, Default)]
pub struct Opts {
/// Enables [term::transform::eta_reduction].
pub eta: bool,
/// Enables [term::transform::simplify_ref_to_ref].
pub ref_to_ref: bool,
/// Enables [term::transform::definition_pruning] and [hvmc_net::prune].
pub prune: bool,
/// Enables [hvmc_net::pre_reduce].
pub pre_reduce: bool,
/// Enables [term::transform::detach_supercombinators].
pub supercombinators: bool,
}
impl From<usize> for OptimizationLevel {
fn from(value: usize) -> Self {
if value == 0 { OptimizationLevel::Light } else { OptimizationLevel::Heavy }
impl Opts {
/// All optimizations enabled.
pub fn heavy() -> Self {
Self { eta: true, ref_to_ref: true, prune: true, pre_reduce: true, supercombinators: true }
}
/// All optimizations disabled, except detach supercombinators.
pub fn light() -> Self {
let mut this = Self::default();
this.supercombinators = true;
this
}
}
impl Opts {
pub fn from_vec(&mut self, values: Vec<String>) -> Result<(), String> {
for value in values {
match value.as_ref() {
"all" => *self = Opts::heavy(),
"eta" => self.eta = true,
"no-eta" => self.eta = false,
"prune" => self.prune = true,
"no-prune" => self.prune = false,
"ref-to-ref" => self.ref_to_ref = true,
"no-ref-to-ref" => self.ref_to_ref = false,
"supercombinators" => self.supercombinators = true,
"no-supercombinators" => self.supercombinators = false,
other => return Err(format!("Unknown option '{other}'.")),
}
}
Ok(())
}
}

View File

@ -1,8 +1,7 @@
use clap::{Parser, ValueEnum};
use hvmc::ast::{show_book, show_net};
use hvml::{
check_book, compile_book, desugar_book, load_file_to_book, run_book, total_rewrites, OptimizationLevel,
RunInfo,
check_book, compile_book, desugar_book, load_file_to_book, run_book, total_rewrites, Opts, RunInfo,
};
use std::path::PathBuf;
@ -32,8 +31,17 @@ struct Args {
#[arg(short = 'l', help = "Linear readback (show explicit dups)")]
pub linear: bool,
#[arg(short = 'O', default_value = "1", help = "Optimization level (0 or 1)", value_parser = opt_level_parser)]
pub opt_level: OptimizationLevel,
#[arg(
short = 'O',
value_delimiter = ' ',
action = clap::ArgAction::Append,
long_help = r#"Enables or disables the given optimizations
all, eta, no-eta, prune, no-prune, ref-to-ref, no-ref-to-ref,
supercombinators (enabled by default), no-supercombinators
"#,
)]
pub opts: Vec<String>,
#[arg(help = "Path to the input file")]
pub path: PathBuf,
@ -63,17 +71,14 @@ fn mem_parser(arg: &str) -> Result<usize, String> {
Ok(base * mult)
}
fn opt_level_parser(arg: &str) -> Result<OptimizationLevel, String> {
let num = arg.parse::<usize>().map_err(|e| e.to_string())?;
Ok(OptimizationLevel::from(num))
}
fn main() {
fn run() -> Result<(), String> {
#[cfg(not(feature = "cli"))]
compile_error!("The 'cli' feature is needed for the hvm-lang cli");
let args = Args::parse();
let mut opts = Opts::light();
Opts::from_vec(&mut opts, args.opts)?;
let mut book = load_file_to_book(&args.path)?;
if args.verbose {
@ -85,7 +90,7 @@ fn main() {
check_book(book)?;
}
Mode::Compile => {
let compiled = compile_book(&mut book, args.opt_level)?;
let compiled = compile_book(&mut book, opts)?;
for warn in &compiled.warnings {
eprintln!("WARNING: {warn}");
@ -94,13 +99,13 @@ fn main() {
print!("{}", show_book(&compiled.core_book));
}
Mode::Desugar => {
desugar_book(&mut book, args.opt_level)?;
desugar_book(&mut book, opts)?;
println!("{book}");
}
Mode::Run => {
let mem_size = args.mem / std::mem::size_of::<(hvmc::run::APtr, hvmc::run::APtr)>();
let (res_term, def_names, info) =
run_book(book, mem_size, !args.single_core, args.debug, args.linear, args.skip_warnings, args.opt_level)?;
run_book(book, mem_size, !args.single_core, args.debug, args.linear, args.skip_warnings,opts)?;
let RunInfo { stats, readback_errors, net: lnet } = info;
let total_rewrites = total_rewrites(&stats.rewrites) as f64;
let rps = total_rewrites / stats.run_time / 1_000_000.0;

View File

@ -8,7 +8,7 @@ use hvml::{
parser::{parse_definition_book, parse_term},
term_to_compat_net, Book, DefId, Term,
},
OptimizationLevel,
Opts,
};
use insta::assert_snapshot;
use itertools::Itertools;
@ -95,7 +95,7 @@ fn compile_term() {
fn compile_file() {
run_golden_test_dir(function_name!(), &|code| {
let mut book = do_parse_book(code)?;
let compiled = compile_book(&mut book, OptimizationLevel::Heavy)?;
let compiled = compile_book(&mut book, Opts::heavy())?;
Ok(format!("{:?}", compiled))
})
}
@ -103,7 +103,7 @@ fn compile_file() {
fn compile_file_o0() {
run_golden_test_dir(function_name!(), &|code| {
let mut book = do_parse_book(code)?;
let compiled = compile_book(&mut book, OptimizationLevel::Light)?;
let compiled = compile_book(&mut book, Opts::light())?;
Ok(format!("{:?}", compiled))
})
}
@ -113,7 +113,7 @@ fn run_file() {
run_golden_test_dir(function_name!(), &|code| {
let book = do_parse_book(code)?;
// 1 million nodes for the test runtime. Smaller doesn't seem to make it any faster
let (res, def_names, info) = run_book(book, 1 << 20, true, false, false, false, OptimizationLevel::Heavy)?;
let (res, def_names, info) = run_book(book, 1 << 20, true, false, false, false, Opts::heavy())?;
let res = if info.readback_errors.is_empty() {
res.display(&def_names).to_string()
} else {