Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Book::transform #107

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license = "MIT"
[[bin]]
name = "hvmc"
path = "src/main.rs"
required-features = ["cli"]
bench = false

[lib]
Expand All @@ -25,15 +26,16 @@ debug = "full"
[dependencies]
TSPL = "0.0.9"
arrayvec = "0.7.4"
clap = { version = "4.5.1", features = ["derive"] }
clap = { version = "4.5.1", features = ["derive"], optional = true }
nohash-hasher = { version = "0.2.0" }
stacker = "0.1.15"
thiserror = "1.0.58"

##--COMPILER-CUTOFF--##

[features]
default = ["_full_cli"]
default = ["cli", "_full_cli"]
cli = ["dep:clap"]
trace = []
_full_cli = []
_fuzz = []
Expand Down
205 changes: 27 additions & 178 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use hvmc::{
host::Host,
run::{DynNet, Mode, Trg},
stdlib::create_host,
transform::{TransformOpts, TransformPass, TransformPasses},
*,
};

Expand All @@ -25,28 +26,28 @@ fn main() {
if cfg!(feature = "_full_cli") {
let cli = FullCli::parse();
match cli.mode {
CliMode::Compile { file, transform_opts, output } => {
CliMode::Compile { file, transform_args, output } => {
let output = output.as_deref().or_else(|| file.strip_suffix(".hvmc")).unwrap_or_else(|| {
eprintln!("file missing `.hvmc` extension; explicitly specify an output path with `--output`.");
process::exit(1);
});
let host = create_host(&load_book(&[file.clone()], &transform_opts));
let host = create_host(&load_book(&[file.clone()], &transform_args));
compile_executable(output, host).unwrap();
}
CliMode::Run { run_opts, mut transform_opts, file, args } => {
CliMode::Run { run_opts, mut transform_args, file, args } => {
// Don't pre-reduce or prune the entry point
transform_opts.pre_reduce_skip.push(args.entry_point.clone());
transform_opts.prune_entrypoints.push(args.entry_point.clone());
let host = create_host(&load_book(&[file], &transform_opts));
transform_args.transform_opts.pre_reduce_skip.push(args.entry_point.clone());
transform_args.transform_opts.prune_entrypoints.push(args.entry_point.clone());
let host = create_host(&load_book(&[file], &transform_args));
run(host, run_opts, args);
}
CliMode::Reduce { run_opts, transform_opts, files, exprs } => {
let host = create_host(&load_book(&files, &transform_opts));
CliMode::Reduce { run_opts, transform_args, files, exprs } => {
let host = create_host(&load_book(&files, &transform_args));
let exprs: Vec<_> = exprs.iter().map(|x| Net::from_str(x).unwrap()).collect();
reduce_exprs(host, &exprs, &run_opts);
}
CliMode::Transform { transform_opts, files } => {
let book = load_book(&files, &transform_opts);
CliMode::Transform { transform_args, files } => {
let book = load_book(&files, &transform_args);
println!("{}", book);
}
}
Expand Down Expand Up @@ -100,7 +101,7 @@ enum CliMode {
/// Output path; defaults to the input file with `.hvmc` stripped.
output: Option<String>,
#[command(flatten)]
transform_opts: TransformOpts,
transform_args: TransformArgs,
},
/// Run a program, optionally passing a list of arguments to it.
Run {
Expand All @@ -111,7 +112,7 @@ enum CliMode {
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
transform_opts: TransformOpts,
transform_args: TransformArgs,
},
/// Reduce hvm-core expressions to their normal form.
///
Expand All @@ -135,7 +136,7 @@ enum CliMode {
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
transform_opts: TransformOpts,
transform_args: TransformArgs,
},
/// Transform a hvm-core program using one of the optimization passes.
Transform {
Expand All @@ -145,36 +146,18 @@ enum CliMode {
#[arg(required = true)]
files: Vec<String>,
#[command(flatten)]
transform_opts: TransformOpts,
transform_args: TransformArgs,
},
}

#[derive(Args, Clone, Debug)]
struct TransformOpts {
#[arg(short = 'O', value_delimiter = ' ', action = clap::ArgAction::Append)]
struct TransformArgs {
/// Enables or disables transformation passes.
#[arg(short = 'O', value_delimiter = ' ', action = clap::ArgAction::Append)]
transform_passes: Vec<TransformPass>,

#[arg(long = "pre-reduce-skip", value_delimiter = ' ', action = clap::ArgAction::Append)]
/// Names of the definitions that should not get pre-reduced.
///
/// For programs that don't take arguments
/// and don't have side effects this is usually the entry point of the
/// program (otherwise, the whole program will get reduced to normal form).
pre_reduce_skip: Vec<String>,
#[arg(long = "pre-reduce-memory", value_parser = parse_abbrev_number::<usize>)]
/// How much memory to allocate when pre-reducing.
///
/// Supports abbreviations such as '4G' or '400M'.
pre_reduce_memory: Option<usize>,
#[arg(long = "pre-reduce-rewrites", default_value = "100M", value_parser = parse_abbrev_number::<u64>)]
/// Maximum amount of rewrites to do when pre-reducing.
///
/// Supports abbreviations such as '4G' or '400M'.
pre_reduce_rewrites: u64,
/// Names of the definitions that should not get pruned.
#[arg(long = "prune-entrypoints", default_value = "main")]
prune_entrypoints: Vec<String>,
#[command(flatten)]
transform_opts: TransformOpts,
}

#[derive(Args, Clone, Debug)]
Expand All @@ -192,7 +175,7 @@ struct RuntimeOpts {
/// by a walk from the root of the net. This leads to a dramatic slowdown,
/// but allows running programs that would expand indefinitely otherwise.
lazy_mode: bool,
#[arg(short = 'm', long = "memory", value_parser = parse_abbrev_number::<usize>)]
#[arg(short = 'm', long = "memory", value_parser = util::parse_abbrev_number::<usize>)]
/// How much memory to allocate on startup.
///
/// Supports abbreviations such as '4G' or '400M'.
Expand All @@ -213,73 +196,6 @@ struct RunArgs {
args: Vec<String>,
}

macro_rules! transform_passes {
($($pass:ident: $name:literal $(| $alias:literal)*),* $(,)?) => {
#[derive(Clone, Debug)]
#[allow(non_camel_case_types)]
pub enum TransformPass {
all(bool),
$($pass(bool),)*
}

#[derive(Default)]
struct TransformPasses {
$($pass: bool),*
}

impl TransformPasses {
fn set_all(&mut self) {
$(self.$pass = true;)*
}
}

impl clap::ValueEnum for TransformPass {
fn value_variants<'a>() -> &'a [Self] {
&[
Self::all(true), Self::all(false),
$(Self::$pass(true), Self::$pass(false),)*
]
}

fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
use TransformPass::*;
Some(match self {
all(true) => clap::builder::PossibleValue::new("all"),
all(false) => clap::builder::PossibleValue::new("no-all"),
$(
$pass(true) => clap::builder::PossibleValue::new($name)$(.alias($alias))*,
$pass(false) => clap::builder::PossibleValue::new(concat!("no-", $name))$(.alias(concat!("no-", $alias)))*,
)*
})
}
}

impl TransformPass {
fn passes_from_cli(args: &Vec<Self>) -> TransformPasses {
use TransformPass::*;
let mut opts = TransformPasses::default();
for arg in args {
match arg {
all(true) => opts.set_all(),
all(false) => opts = TransformPasses::default(),
$(&$pass(b) => opts.$pass = b,)*
}
}
opts
}
}
};
}

transform_passes! {
pre_reduce: "pre-reduce" | "pre",
coalesce_ctrs: "coalesce-ctrs" | "coalesce",
encode_adts: "encode-adts" | "adts",
eta_reduce: "eta-reduce" | "eta",
inline: "inline",
prune: "prune",
}

fn run(host: Arc<Mutex<Host>>, opts: RuntimeOpts, args: RunArgs) {
let mut net = Net { root: Tree::Ref { nam: args.entry_point }, redexes: vec![] };
for arg in args.args {
Expand All @@ -290,29 +206,8 @@ fn run(host: Arc<Mutex<Host>>, opts: RuntimeOpts, args: RunArgs) {

reduce_exprs(host, &[net], &opts);
}
/// Turn a string representation of a number, such as '1G' or '400K', into a
/// number.
///
/// This return a [`u64`] instead of [`usize`] to ensure that parsing CLI args
/// doesn't fail on 32-bit systems. We want it to fail later on, when attempting
/// to run the program.
fn parse_abbrev_number<T: TryFrom<u64>>(arg: &str) -> Result<T, String>
where
<T as TryFrom<u64>>::Error: core::fmt::Debug,
{
let (base, scale) = match arg.to_lowercase().chars().last() {
None => return Err("Mem size argument is empty".to_string()),
Some('k') => (&arg[0 .. arg.len() - 1], 1u64 << 10),
Some('m') => (&arg[0 .. arg.len() - 1], 1u64 << 20),
Some('g') => (&arg[0 .. arg.len() - 1], 1u64 << 30),
Some('t') => (&arg[0 .. arg.len() - 1], 1u64 << 40),
Some(_) => (arg, 1),
};
let base = base.parse::<u64>().map_err(|e| e.to_string())?;
Ok((base * scale).try_into().map_err(|e| format!("{:?}", e))?)
}

fn load_book(files: &[String], transform_opts: &TransformOpts) -> Book {
fn load_book(files: &[String], transform_args: &TransformArgs) -> Book {
let mut book = files
.iter()
.map(|name| {
Expand All @@ -329,58 +224,10 @@ fn load_book(files: &[String], transform_opts: &TransformOpts) -> Book {
acc.nets.extend(i.nets);
acc
});
let transform_passes = TransformPass::passes_from_cli(&transform_opts.transform_passes);

if transform_passes.pre_reduce {
if transform_passes.eta_reduce {
for (_, def) in &mut book.nets {
def.eta_reduce();
}
}
book.pre_reduce(
&|x| transform_opts.pre_reduce_skip.iter().any(|y| x == y),
transform_opts.pre_reduce_memory,
transform_opts.pre_reduce_rewrites,
);
}
for (_, def) in &mut book.nets {
if transform_passes.eta_reduce {
def.eta_reduce();
}
for tree in def.trees_mut() {
if transform_passes.coalesce_ctrs {
tree.coalesce_constructors();
}
if transform_passes.encode_adts {
tree.encode_scott_adts();
}
}
}
if transform_passes.inline {
loop {
let inline_changed = book.inline().unwrap();
if inline_changed.is_empty() {
break;
}
if !(transform_passes.eta_reduce || transform_passes.encode_adts) {
break;
}
for name in inline_changed {
let def = book.get_mut(&name).unwrap();
if transform_passes.eta_reduce {
def.eta_reduce();
}
if transform_passes.encode_adts {
for tree in def.trees_mut() {
tree.encode_scott_adts();
}
}
}
}
}
if transform_passes.prune {
book.prune(&transform_opts.prune_entrypoints);
}
let transform_passes = TransformPasses::from(&transform_args.transform_passes[..]);
book.transform(transform_passes, &transform_args.transform_opts).unwrap();

book
}

Expand Down Expand Up @@ -434,7 +281,8 @@ fn compile_executable(target: &str, host: Arc<Mutex<host::Host>>) -> Result<(),
fs::remove_dir_all(outdir)?;
}
let cargo_toml = include_str!("../Cargo.toml");
let cargo_toml = cargo_toml.split("##--COMPILER-CUTOFF--##").next().unwrap();
let mut cargo_toml = cargo_toml.split_once("##--COMPILER-CUTOFF--##").unwrap().0.to_owned();
cargo_toml.push_str("[features]\ndefault = ['cli']\ncli = ['dep:clap']");

macro_rules! include_files {
($([$($prefix:ident)*])? $mod:ident {$($sub:tt)*} $($rest:tt)*) => {
Expand Down Expand Up @@ -499,6 +347,7 @@ fn compile_executable(target: &str, host: Arc<Mutex<host::Host>>) -> Result<(),
create_var
deref
maybe_grow
parse_abbrev_number
stats
}
}
Expand Down
Loading
Loading