#![cfg_attr(feature = "trace", feature(const_type_name))]
use clap::{Args, Parser, Subcommand};
use hvmc::{
ast::{Book, Net, Tree},
host::Host,
run::{DynNet, Mode, Trg},
stdlib::{create_host, insert_stdlib},
transform::{TransformOpts, TransformPass, TransformPasses},
*,
};
use parking_lot::Mutex;
use std::{
env::consts::{DLL_PREFIX, DLL_SUFFIX},
ffi::OsStr,
fmt::Write,
fs::{self, File},
io::{self, BufRead},
path::{Path, PathBuf},
process::{self, Stdio},
str::FromStr,
sync::Arc,
time::{Duration, Instant},
};
fn main() {
if cfg!(feature = "trace") {
trace::set_hook();
}
if cfg!(feature = "_full_cli") {
let cli = FullCli::parse();
match cli.mode {
CliMode::Compile { file, dylib, transform_args, output } => {
let output = if let Some(output) = output {
output
} else if let Some("hvmc") = file.extension().and_then(OsStr::to_str) {
file.with_extension("")
} else {
eprintln!("file missing `.hvmc` extension; explicitly specify an output path with `--output`.");
process::exit(1);
};
let host = create_host(&load_book(&[file], &transform_args));
create_temp_hvm(host).unwrap();
if dylib {
prepare_temp_hvm_dylib().unwrap();
compile_temp_hvm(&["--lib"]).unwrap();
fs::copy(format!(".hvm/target/release/{DLL_PREFIX}hvmc{DLL_SUFFIX}"), output).unwrap();
} else {
compile_temp_hvm(&[]).unwrap();
fs::copy(".hvm/target/release/hvmc", output).unwrap();
}
}
CliMode::Run { run_opts, mut transform_args, file, args } => {
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: Arc<Mutex<Host>> = Default::default();
load_dylibs(host.clone(), &run_opts.include);
insert_stdlib(host.clone());
host.lock().insert_book(&load_book(&[file], &transform_args));
run(host, run_opts, args);
}
CliMode::Reduce { run_opts, transform_args, files, exprs } => {
let host = load_host(&files, &transform_args, &run_opts.include);
let exprs: Vec<_> = exprs.iter().map(|x| Net::from_str(x).unwrap()).collect();
reduce_exprs(host, &exprs, &run_opts);
}
CliMode::Transform { transform_args, files } => {
let book = load_book(&files, &transform_args);
println!("{}", book);
}
}
} else {
let cli = BareCli::parse();
let host = create_host(&Book::default());
gen::insert_into_host(&mut host.lock());
run(host, cli.opts, cli.args);
}
if cfg!(feature = "trace") {
hvmc::trace::_read_traces(usize::MAX);
}
}
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "A massively parallel Interaction Combinator evaluator",
long_about = r##"
A massively parallel Interaction Combinator evaluator
Examples:
$ hvmc run examples/church_encoding/church.hvm
$ hvmc run examples/addition.hvmc "#16" "#3"
$ hvmc compile examples/addition.hvmc
$ hvmc reduce examples/addition.hvmc -- "a & @mul ~ (#3 (#4 a))"
$ hvmc reduce -- "a & #3 ~ <* #4 a>""##
)]
struct FullCli {
#[command(subcommand)]
pub mode: CliMode,
}
#[derive(Parser, Debug)]
#[command(author, version)]
struct BareCli {
#[command(flatten)]
pub opts: RuntimeOpts,
#[command(flatten)]
pub args: RunArgs,
}
#[derive(Subcommand, Clone, Debug)]
#[command(author, version)]
enum CliMode {
Compile {
file: PathBuf,
#[arg(short, long)]
dylib: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[command(flatten)]
transform_args: TransformArgs,
},
Run {
file: PathBuf,
#[command(flatten)]
args: RunArgs,
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
transform_args: TransformArgs,
},
Reduce {
#[arg(required = false)]
files: Vec<PathBuf>,
#[arg(required = false, last = true)]
exprs: Vec<String>,
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
transform_args: TransformArgs,
},
Transform {
#[arg(required = true)]
files: Vec<PathBuf>,
#[command(flatten)]
transform_args: TransformArgs,
},
}
#[derive(Args, Clone, Debug)]
struct TransformArgs {
#[arg(short = 'O', value_delimiter = ' ', action = clap::ArgAction::Append)]
transform_passes: Vec<TransformPass>,
#[command(flatten)]
transform_opts: TransformOpts,
}
#[derive(Args, Clone, Debug)]
struct RuntimeOpts {
#[arg(short, long = "stats")]
show_stats: bool,
#[arg(short = '1', long = "single")]
single_core: bool,
#[arg(short, long = "lazy")]
lazy_mode: bool,
#[arg(short, long, value_parser = util::parse_abbrev_number::<usize>)]
memory: Option<usize>,
#[arg(short, long, value_delimiter = ' ', action = clap::ArgAction::Append)]
include: Vec<PathBuf>,
}
#[derive(Args, Clone, Debug)]
struct RunArgs {
#[arg(short, default_value = "main")]
entry_point: String,
args: Vec<String>,
}
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 {
let arg: Net = Net::from_str(&arg).unwrap();
net.redexes.extend(arg.redexes);
net.apply_tree(arg.root);
}
reduce_exprs(host, &[net], &opts);
}
fn load_host(
files: &[PathBuf],
transform_args: &TransformArgs,
include: &[PathBuf],
) -> Arc<parking_lot::lock_api::Mutex<parking_lot::RawMutex, Host>> {
let host: Arc<Mutex<Host>> = Default::default();
load_dylibs(host.clone(), include);
insert_stdlib(host.clone());
host.lock().insert_book(&load_book(files, transform_args));
host
}
fn load_book(files: &[PathBuf], transform_args: &TransformArgs) -> Book {
let mut book = files
.iter()
.map(|name| {
let contents = fs::read_to_string(name).unwrap_or_else(|_| {
eprintln!("Input file {:?} not found", name);
process::exit(1);
});
contents.parse::<Book>().unwrap_or_else(|e| {
eprintln!("Parsing error {e}");
process::exit(1);
})
})
.fold(Book::default(), |mut acc, i| {
acc.nets.extend(i.nets);
acc
});
let transform_passes = TransformPasses::from(&transform_args.transform_passes[..]);
book.transform(transform_passes, &transform_args.transform_opts).unwrap();
book
}
fn load_dylibs(host: Arc<Mutex<Host>>, include: &[PathBuf]) {
let current_dir = std::env::current_dir().unwrap();
for file in include {
unsafe {
let lib = if file.is_absolute() {
libloading::Library::new(file)
} else {
libloading::Library::new(current_dir.join(file))
}
.expect("failed to load dylib");
let rust_version =
lib.get::<fn() -> &'static str>(b"hvmc_dylib_v0__rust_version").expect("failed to load rust version");
let rust_version = rust_version();
if rust_version != env!("RUSTC_VERSION") {
eprintln!(
"warning: dylib {file:?} was compiled with rust version {rust_version}, but is being run with rust version {}",
env!("RUSTC_VERSION")
);
}
let hvmc_version =
lib.get::<fn() -> &'static str>(b"hvmc_dylib_v0__hvmc_version").expect("failed to load hvmc version");
let hvmc_version = hvmc_version();
if hvmc_version != env!("CARGO_PKG_VERSION") {
eprintln!(
"warning: dylib {file:?} was compiled with hvmc version {hvmc_version}, but is being run with hvmc version {}",
env!("CARGO_PKG_VERSION")
);
}
let insert_into_host =
lib.get::<fn(&mut Host)>(b"hvmc_dylib_v0__insert_host").expect("failed to load insert_host");
insert_into_host(&mut host.lock());
std::mem::forget(lib);
}
}
}
fn reduce_exprs(host: Arc<Mutex<Host>>, exprs: &[Net], opts: &RuntimeOpts) {
let heap = run::Heap::new(opts.memory).expect("memory allocation failed");
for expr in exprs {
let mut net = DynNet::new(&heap, opts.lazy_mode);
dispatch_dyn_net!(&mut net => {
host.lock().encode_net(net, Trg::port(run::Port::new_var(net.root.addr())), expr);
let start_time = Instant::now();
if opts.single_core {
net.normal();
} else {
net.parallel_normal();
}
let elapsed = start_time.elapsed();
println!("{}", host.lock().readback(net));
if opts.show_stats {
print_stats(net, elapsed);
}
});
}
}
fn print_stats<M: Mode>(net: &run::Net<M>, elapsed: Duration) {
eprintln!("RWTS : {:>15}", pretty_num(net.rwts.total()));
eprintln!("- ANNI : {:>15}", pretty_num(net.rwts.anni));
eprintln!("- COMM : {:>15}", pretty_num(net.rwts.comm));
eprintln!("- ERAS : {:>15}", pretty_num(net.rwts.eras));
eprintln!("- DREF : {:>15}", pretty_num(net.rwts.dref));
eprintln!("- OPER : {:>15}", pretty_num(net.rwts.oper));
eprintln!("TIME : {:.3?}", elapsed);
eprintln!("RPS : {:.3} M", (net.rwts.total() as f64) / (elapsed.as_millis() as f64) / 1000.0);
}
fn pretty_num(n: u64) -> String {
n.to_string()
.as_bytes()
.rchunks(3)
.rev()
.map(|x| std::str::from_utf8(x).unwrap())
.flat_map(|x| ["_", x])
.skip(1)
.collect()
}
fn create_temp_hvm(host: Arc<Mutex<host::Host>>) -> Result<(), io::Error> {
let gen = compile::compile_host(&host.lock());
let outdir = ".hvm";
if Path::new(&outdir).exists() {
fs::remove_dir_all(outdir)?;
}
let cargo_toml = include_str!("../Cargo.toml");
let mut cargo_toml = cargo_toml.split_once("##--COMPILER-CUTOFF--##").unwrap().0.to_owned();
cargo_toml.push_str("[features]\ndefault = ['cli']\ncli = ['std', 'dep:clap']\nstd = []");
macro_rules! include_files {
($([$($prefix:ident)*])? $mod:ident {$($sub:tt)*} $($rest:tt)*) => {
fs::create_dir_all(concat!(".hvm/src/", $($(stringify!($prefix), "/",)*)? stringify!($mod)))?;
include_files!([$($($prefix)* $mod)?] $($sub)*);
include_files!([$($($prefix)*)?] $mod $($rest)*);
};
($([$($prefix:ident)*])? $file:ident $($rest:tt)*) => {
fs::write(
concat!(".hvm/src/", $($(stringify!($prefix), "/",)*)* stringify!($file), ".rs"),
include_str!(concat!($($(stringify!($prefix), "/",)*)* stringify!($file), ".rs")),
)?;
include_files!([$($($prefix)*)?] $($rest)*);
};
($([$($prefix:ident)*])?) => {};
}
fs::create_dir_all(".hvm/src")?;
fs::write(".hvm/Cargo.toml", cargo_toml)?;
fs::write(".hvm/src/gen.rs", gen)?;
include_files! {
ast
compile
fuzz
host {
calc_labels
encode
readback
}
lib
main
ops {
num
word
}
prelude
run {
addr
allocator
def
dyn_net
instruction
interact
linker
net
node
parallel
port
wire
}
stdlib
trace
transform {
coalesce_ctrs
encode_adts
eta_reduce
inline
pre_reduce
prune
}
util {
apply_tree
array_vec
bi_enum
create_var
deref
maybe_grow
parse_abbrev_number
stats
}
}
Ok(())
}
fn prepare_temp_hvm_dylib() -> Result<(), io::Error> {
insert_crate_type_cargo_toml()?;
let mut lib = fs::read_to_string(".hvm/src/lib.rs")?;
writeln!(lib).unwrap();
writeln!(
lib,
r#"
#[no_mangle]
pub fn hvmc_dylib_v0__insert_host(host: &mut host::Host) {{
gen::insert_into_host(host)
}}
#[no_mangle]
pub fn hvmc_dylib_v0__hvmc_version() -> &'static str {{
{hvmc_version:?}
}}
#[no_mangle]
pub fn hvmc_dylib_v0__rust_version() -> &'static str {{
{rust_version:?}
}}
"#,
hvmc_version = env!("CARGO_PKG_VERSION"),
rust_version = env!("RUSTC_VERSION"),
)
.unwrap();
fs::write(".hvm/src/lib.rs", lib)
}
fn insert_crate_type_cargo_toml() -> Result<(), io::Error> {
let mut cargo_toml = String::new();
let file = File::open(".hvm/Cargo.toml")?;
for line in io::BufReader::new(file).lines() {
let line = line?;
writeln!(cargo_toml, "{line}").unwrap();
if line == "[lib]" {
writeln!(cargo_toml, r#"crate_type = ["dylib"]"#).unwrap();
}
}
fs::write(".hvm/Cargo.toml", cargo_toml)
}
fn compile_temp_hvm(args: &[&'static str]) -> Result<(), io::Error> {
let output = process::Command::new("cargo")
.current_dir(".hvm")
.arg("build")
.arg("--release")
.args(args)
.stderr(Stdio::inherit())
.output()?;
if !output.status.success() {
process::exit(1);
}
Ok(())
}