Skip to content

Commit

Permalink
[sc-481] Add transformation passes to hvm-core.
Browse files Browse the repository at this point in the history
- Add `transform` subcommand to `hvm-core`.
- Add `pre-reduce`-related options.
- Add `Net::init_heap_bytes` method.
  • Loading branch information
FranchuFranchu committed Feb 28, 2024
1 parent 0ba064c commit 18d2ff8
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 58 deletions.
2 changes: 2 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use crate::{ops::Op, run::Lab, util::deref};
use std::{collections::BTreeMap, fmt, str::FromStr};

pub mod transform;

/// The top level AST node, representing a collection of named nets.
///
/// This is a newtype wrapper around a `BTreeMap<String, Net>`, and is
Expand Down
1 change: 1 addition & 0 deletions src/ast/transform/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod pre_reduce;
63 changes: 63 additions & 0 deletions src/ast/transform/pre_reduce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Reduce the compiled networks, solving any annihilations and commutations.

Check warning on line 1 in src/ast/transform/pre_reduce.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (annihilations)

use std::sync::Mutex;

use crate::{ast::Book, host::Host, run, run::Def};

/// A Def that pushes all interactions to its inner Vec.
struct InertDef(Mutex<Vec<(run::Port, run::Port)>>);

impl run::AsDef for InertDef {
unsafe fn call<M: run::Mode>(def: *const run::Def<Self>, _: &mut run::Net<M>, port: run::Port) {
let def = unsafe { &*def };
def.data.0.lock().unwrap().push((run::Port::new_ref(def), port));
}
}

impl Book {
/// Reduces the definitions in the book individually, except for the skipped
/// ones.
///
/// Defs in `dont_interact` are skipped and treated as inert defs.

Check warning on line 21 in src/ast/transform/pre_reduce.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (dont)
pub fn pre_reduce(
&mut self,
skip: &dyn Fn(&str) -> bool,
dont_interact: &mut dyn Iterator<Item = &str>,

Check warning on line 25 in src/ast/transform/pre_reduce.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (dont)
max_memory: usize,
max_rwts: u64,
) -> Result<(), String> {
// Create a host
// with inert definitions in the place
// of core builtins, to prevent them from being reduced

Check warning on line 31 in src/ast/transform/pre_reduce.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (builtins)
let mut host = Host::default();
for builtin in dont_interact {

Check warning on line 33 in src/ast/transform/pre_reduce.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (dont)
let def = InertDef(Default::default());
host.insert_def(builtin, crate::host::DefRef::Owned(Box::new(Def::new(run::LabSet::ALL, def))));
}
host.insert_book(self);
let area = run::Net::<run::Strict>::init_heap_bytes(max_memory);

for (nam, net) in self.nets.iter_mut() {
// Skip unnecessary work
if net.rdex.is_empty() || skip(nam) {
continue;
}

let mut rt = run::Net::<run::Strict>::new(&area);
rt.boot(host.defs.get(nam).expect("No function."));
rt.expand();
rt.reduce(max_rwts as usize);

// Move interactions with inert defs back into the net rdex array
for def in host.defs.values() {
if let Some(def) = def.downcast_ref::<InertDef>() {
let mut stored_redexes = def.data.0.lock().unwrap();
rt.rdex.extend(core::mem::take(&mut *stored_redexes));
}
}
// Place the reduced net back into the def map
*net = host.readback(&mut rt);
}
Ok(())
}
}
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pub mod host;
pub mod ops;
pub mod run;
pub mod stdlib;

pub mod util;

#[doc(hidden)] // not public api
Expand Down
214 changes: 158 additions & 56 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use clap::{Args, Parser, Subcommand};
use hvmc::{
ast::{Net, Tree},
ast::{Book, Net, Tree},
host::Host,
run::{DynNet, Mode, Strict, Trg},
*,
Expand All @@ -24,19 +24,25 @@ fn main() {
if cfg!(feature = "_full_cli") {
let cli = FullCli::parse();
match cli.mode {
CliMode::Compile { file } => {
let host = load_files(&[file.clone()]);
CliMode::Compile { compile_opts, file } => {
let host = create_host(&load_book(&[file.clone()], &compile_opts));
compile_executable(&file, &host.lock().unwrap()).unwrap();
}
CliMode::Run { opts, file, args } => {
let host = load_files(&[file]);
run(&host.lock().unwrap(), opts, args);
CliMode::Run { run_opts, mut compile_opts, file, args } => {
// Don't pre-reduce the main reduction
compile_opts.pre_reduce_skip.push(args.entry_point.clone());
let host = create_host(&load_book(&[file], &compile_opts));
run(&host.lock().unwrap(), run_opts, args);
}
CliMode::Reduce { run_opts, files, exprs } => {
let host = load_files(&files);
CliMode::Reduce { run_opts, compile_opts, files, exprs } => {
let host = create_host(&load_book(&files, &compile_opts));
let exprs: Vec<_> = exprs.iter().map(|x| Net::from_str(x).unwrap()).collect();
reduce_exprs(&host.lock().unwrap(), &exprs, &run_opts);
}
CliMode::Transform { compile_opts, files } => {
let book = load_book(&files, &compile_opts);
println!("{}", book);
}
}
} else {
let cli = BareCli::parse();
Expand Down Expand Up @@ -77,58 +83,26 @@ struct BareCli {
pub args: RunArgs,
}

#[derive(Args, Clone, Debug)]
struct RuntimeOpts {
#[arg(short = 's', long = "stats")]
/// Show performance statistics.
show_stats: bool,
#[arg(short = '1', long = "single")]
/// Single-core mode (no parallelism).
single_core: bool,
#[arg(short = 'l', long = "lazy")]
/// Lazy mode.
///
/// Lazy mode only expands references that are reachable
/// 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", default_value = "1G", value_parser = mem_parser)]
/// How much memory to allocate on startup.
///
/// Supports abbreviations such as '4G' or '400M'.
memory: u64,
}

#[derive(Args, Clone, Debug)]
struct RunArgs {
#[arg(short = 'e', default_value = "main")]
/// Name of the definition that will get reduced.
entry_point: String,
/// List of arguments to pass to the program.
///
/// Arguments are passed using the lambda-calculus interpretation
/// of interaction combinators. So, for example, if the arguments are
/// "#1" "#2" "#3", then the expression that will get reduced is
/// `r & @main ~ (#1 (#2 (#3 r)))`.
args: Vec<String>,
}

#[derive(Subcommand, Clone, Debug)]
#[command(author, version)]
enum CliMode {
/// Compile a hvm-core program into a Rust crate.
Compile {
/// hvm-core file to compile.
file: String,
#[command(flatten)]
compile_opts: CompileArgs,
},
/// Run a program, optionally passing a list of arguments to it.
Run {
#[command(flatten)]
opts: RuntimeOpts,
/// Name of the file to load.
file: String,
#[command(flatten)]
args: RunArgs,
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
compile_opts: CompileArgs,
},
/// Reduce hvm-core expressions to their normal form.
///
Expand All @@ -137,8 +111,6 @@ enum CliMode {
/// which makes it possible to reference definitions from the file
/// in the expression.
Reduce {
#[command(flatten)]
run_opts: RuntimeOpts,
#[arg(required = false)]
/// Files to load before reducing the expressions.
///
Expand All @@ -151,9 +123,119 @@ enum CliMode {
/// printed on a new line. This list must be separated from the file list
/// with a double dash ('--').
exprs: Vec<String>,
#[command(flatten)]
run_opts: RuntimeOpts,
#[command(flatten)]
compile_opts: CompileArgs,
},
/// Transform a hvm-core program using one of the optimiztion passes

Check warning on line 131 in src/main.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (optimiztion)
Transform {
/// Files to load before reducing the expressions.
///
/// Multiple files will act as if they're concatenated together.
#[arg(required = true)]
files: Vec<String>,
#[command(flatten)]
compile_opts: CompileArgs,
},
}

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

#[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-inert", value_delimiter = ' ', action = clap::ArgAction::Append)]
/// Defintions that are made inert (inactive) when pre-reducing.

Check warning on line 157 in src/main.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (Defintions)
///
/// This is usually side-effectful builtin functions, such as `HVM.log`.

Check warning on line 159 in src/main.rs

View workflow job for this annotation

GitHub Actions / cspell

Unknown word (effectful)
/// Interactions with these functions will do no effect, and redexes with them
/// will still be present in the pre-reduced book.
pre_reduce_inactive: Vec<String>,
#[arg(long = "pre-reduce-memory", default_value = "1G", value_parser = mem_parser)]
/// How much memory to allocate when pre-reducing
///
/// Supports abbreviations such as '4G' or '400M'.
pre_reduce_memory: u64,
#[arg(long = "pre-reduce-rewrites", default_value = "100M", value_parser = mem_parser)]
/// Maximum amount of rewrites to do when pre-reducing
///
/// Supports abbreviations such as '4G' or '400M'.
pre_reduce_rewrites: u64,
}

#[derive(Args, Clone, Debug)]
struct RuntimeOpts {
#[arg(short = 's', long = "stats")]
/// Show performance statistics.
show_stats: bool,
#[arg(short = '1', long = "single")]
/// Single-core mode (no parallelism).
single_core: bool,
#[arg(short = 'l', long = "lazy")]
/// Lazy mode.
///
/// Lazy mode only expands references that are reachable
/// 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", default_value = "1G", value_parser = mem_parser)]
/// How much memory to allocate on startup.
///
/// Supports abbreviations such as '4G' or '400M'.
memory: u64,
}
#[derive(Args, Clone, Debug)]
struct RunArgs {
#[arg(short = 'e', default_value = "main")]
/// Name of the definition that will get reduced.
entry_point: String,
/// List of arguments to pass to the program.
///
/// Arguments are passed using the lambda-calculus interpretation
/// of interaction combinators. So, for example, if the arguments are
/// "#1" "#2" "#3", then the expression that will get reduced is
/// `r & @main ~ (#1 (#2 (#3 r)))`.
args: Vec<String>,
}

#[derive(clap::ValueEnum, Clone, Debug)]
pub enum TransformArg {
All,
NoAll,
PreReduce,
NoPreReduce,
}

#[derive(Default)]
pub struct TransformOptions {
pre_reduce: bool,
}

impl TransformArg {
fn opts_from_cli(args: &Vec<Self>) -> TransformOptions {
use TransformArg::*;
let mut opts = TransformOptions::default();
for arg in args {
match arg {
All => opts.pre_reduce = true,
NoAll => opts.pre_reduce = false,
PreReduce => opts.pre_reduce = true,
NoPreReduce => opts.pre_reduce = false,
}
}
opts
}
}

fn run(host: &Host, opts: RuntimeOpts, args: RunArgs) {
let mut net = Net { root: Tree::Ref { nam: args.entry_point }, rdex: vec![] };
for arg in args.args {
Expand Down Expand Up @@ -182,16 +264,38 @@ fn mem_parser(arg: &str) -> Result<u64, String> {
Ok(base * scale)
}

fn load_files(files: &[String]) -> Arc<Mutex<Host>> {
let files: Vec<_> = files
fn load_book(files: &[String], compile_opts: &CompileArgs) -> Book {
let mut book = files
.iter()
.map(|name| {
fs::read_to_string(name).unwrap_or_else(|_| {
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);
})
})
.collect();
.fold(Book::default(), |mut acc, i| {
acc.nets.extend(i.nets);
acc
});
let transform_opts = TransformArg::opts_from_cli(&compile_opts.compile_opts);
if transform_opts.pre_reduce {
book
.pre_reduce(
&|x| compile_opts.pre_reduce_skip.iter().any(|y| x == y),
&mut compile_opts.pre_reduce_inactive.iter().map(|x| x.as_ref()),
compile_opts.pre_reduce_memory as usize,
compile_opts.pre_reduce_rewrites,
)
.unwrap();
}
book
}

fn create_host(book: &Book) -> Arc<Mutex<Host>> {
let host = Arc::new(Mutex::new(host::Host::default()));
host.lock().unwrap().insert_def(
"HVM.log",
Expand All @@ -202,14 +306,12 @@ fn load_files(files: &[String]) -> Arc<Mutex<Host>> {
}
}))),
);
for file_contents in files {
host.lock().unwrap().insert_book(&ast::Book::from_str(&file_contents).unwrap());
}
host.lock().unwrap().insert_book(&book);
host
}

fn reduce_exprs(host: &Host, exprs: &[Net], opts: &RuntimeOpts) {
let heap = run::Net::<Strict>::init_heap(opts.memory as usize);
let heap = run::Net::<Strict>::init_heap_bytes(opts.memory as usize);
for expr in exprs {
let mut net = DynNet::new(&heap, opts.lazy_mode);
dispatch_dyn_net!(&mut net => {
Expand Down
6 changes: 5 additions & 1 deletion src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ impl<'a, M: Mode> Net<'a, M> {
// -------------

impl<'a, M: Mode> Net<'a, M> {
/// Allocate an area for the net's heap with a given size.
/// Allocate an area for the net's heap with a given size in nodes.
pub fn init_heap(size: usize) -> Box<[Node]> {
unsafe {
Box::from_raw(core::ptr::slice_from_raw_parts_mut(
Expand All @@ -950,6 +950,10 @@ impl<'a, M: Mode> Net<'a, M> {
))
}
}
/// Allocate an area for the net's heap with a given size in bytes.
pub fn init_heap_bytes(size: usize) -> Box<[Node]> {
Self::init_heap(size / core::mem::size_of::<Node>())
}

/// Frees one word of a two-word allocation.
#[inline(always)]
Expand Down
Loading

0 comments on commit 18d2ff8

Please sign in to comment.