Merge pull request #303 from HigherOrderCO/feature/sc-678/add-run-c-and-run-cuda-commands-to-bend

[sc-678] Add run-c and run-cuda commands to bend
This commit is contained in:
Nicolas Abril 2024-05-14 23:50:14 +02:00 committed by GitHub
commit a0c1ea4bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 208 additions and 89 deletions

View File

@ -1,6 +1,6 @@
# Bend
*Note: The latest version of Bend targets an unreleased version of HVM. If you want to use Bend right now, use branch [hvm-core](https://github.com/HigherOrderCO/bend/tree/hvm-core)*.
_Note: The latest version of Bend targets an unreleased version of HVM. If you want to use Bend right now, use branch [hvm-core](https://github.com/HigherOrderCO/bend/tree/hvm-core)_.
Bend is a programming language that can run massively parallel programs on the GPU or the CPU using the power of interaction nets and the [HVM](https://github.com/HigherOrderCO/hvm).
With Bend, you can write programs for the GPU as easily as you'd write a normal program in your favorite language.
@ -9,10 +9,10 @@ It is based on the [Interaction-Calculus](https://github.com/VictorTaelin/Intera
Currently Bend only supports strict/eager evaluation. If you need lazy, optimal evaluation, we recommend using [HVM1](https://github.com/HigherOrderCO/HVM1) for now.
## Installation
With the nightly version of rust installed, clone the repository:
```bash
git clone https://github.com/HigherOrderCO/bend.git
@ -20,32 +20,37 @@ cd bend
```
Install using cargo:
```bash
cargo install --path . --locked
```
If you want to run programs directly from Bend, you also need to have [HVM](https://github.com/HigherOrderCO/hvm2) installed.
## Usage
Command | Usage | Description
--------- | --------------------- | ---
Check | `bend check <file>` | Checks if a program is valid
Compile | `bend compile <file>` | Compiles a program to HVM and outputs it to stdout
Run | `bend run <file>` | Compiles and then runs a program in HVM
Normalize | `bend norm <file>` | Compiles and then normalizes a program in HVM, outputting the result to stdout
Desugar | `bend desugar <file>` | Desugars a program to the core syntax and outputs it to stdout
| Command | Usage | Description |
| ------- | --------------------- | ----------------------------------------------------------------- |
| Check | `bend check <file>` | Checks if a program is valid |
| GenHvm | `bend gen-hvm <file>` | Compiles a program to HVM and outputs it to stdout |
| Run | `bend run <file>` | Compiles and then runs a program with the Rust HVM implementation |
| Run-C | `bend run-c <file>` | Compiles and then runs a program with the C HVM implementation |
| Run-Cu | `bend run-cu <file>` | Compiles and then runs a program with the Cuda HVM implementation |
| Gen-C | `bend gen-c <file>` | Compiles the program to standalone C |
| Gen-Cu | `bend gen-cu <file>` | Compiles the program to standalone Cuda |
| Desugar | `bend desugar <file>` | Desugars a program to the core syntax and outputs it to stdout |
If your program uses IO, it should return an IO value and you need to run it with --io. See [using io](docs/using-io.md), for more details.
If your program doesn't return an IO value, then you should not run it with --io
If you want to compile a file to a file, just redirect the output with `>`:
```bash
bend compile <file.bend> > <file.hvm>
```
There are many compiler options that can be passed through the CLI. You can see the list of options [here](docs/compiler-options.md).
## Examples
Bend offers two flavors of syntax, a user-friendly python-like syntax (the default) and the core ML/Haskell-like syntax that's used internally by the compiler.
@ -53,19 +58,20 @@ You can read the full reference for both of them [here](docs/syntax.md), but the
To see some more complex examples programs, check out the [examples](examples/) folder.
We can start with a basic program that adds the numbers 3 and 2.
```py
def main:
return 2 + 3
```
Normalizing this program will show the number 5.
Be careful with `run` and `norm`, since they will not show any warnings by default. Before running a new program it's useful to first `check` it.
Bend programs consist of a series of function definitions, always starting with a function called `main` or `Main`.
Functions can receive arguments both directly and using a lambda abstraction.
```py
// These two are equivalent
def add(x, y):
@ -76,6 +82,7 @@ def add2:
```
You can then call this function like this:
```py
def main:
sum = add(2, 3)
@ -83,6 +90,7 @@ def main:
```
You can bundle multiple values into a single value using a tuple or a struct.
```py
// With a tuple
def Tuple.fst(x):
@ -105,6 +113,7 @@ def Pair.fst_2(x: Pair):
```
For more complicated data structures, we can use `enum` to define a algebraic data types.
```py
enum MyTree:
Node(val, ~left, ~right)
@ -112,6 +121,7 @@ enum MyTree:
```
We can then pattern match on the enum to perform different actions depending on the variant of the value.
```py
def Maybe.or_default(x, default):
match x:
@ -124,6 +134,7 @@ def Maybe.or_default(x, default):
We use `~` to indicate that a field is recursive.
This allows us to easily create and consume these recursive data structures with `bend` and `fold`:
```py
def MyTree.sum(x):
// Sum all the values in the tree.
@ -146,6 +157,7 @@ def main:
```
These are equivalent to inline recursive functions that create a tree and consume it.
```py
def MyTree.sum(x):
match x:
@ -163,10 +175,12 @@ def main_bend(val):
def main:
return main_bend(0)
```
Making your program around trees is a very good way of making it parallelizable, since each core can be dispatched to work on a different branch of the tree.
*Attention*: Note that despite the ADT syntax sugars, Bend is an *untyped* language and the compiler will not stop you from using values incorrectly, which can lead to very unexpected results.
_Attention_: Note that despite the ADT syntax sugars, Bend is an _untyped_ language and the compiler will not stop you from using values incorrectly, which can lead to very unexpected results.
For example, the following program will compile just fine even though `!=` is only defined for native numbers:
```py
def main:
bend val = [0, 1, 2, 3] while val != []:
@ -179,6 +193,7 @@ def main:
x = 0
return x
```
Normalizing this program will show `λ* *` and not the expected `6`.
It's also important to note that Bend is linear (technically affine), meaning that every variable is only used once. When a variable is used more than once, the compiler will automatically insert a duplication.
@ -187,6 +202,7 @@ You can read more about it in [Dups and sups](docs/dups-and-sups.md) and learn h
To use a variable twice without duplicating it, you can use a `use` statement.
It inlines clones of some value in the statements that follow it.
```py
def foo(x):
use result = bar(1, x)
@ -196,10 +212,11 @@ def foo(x):
def foo(x):
return (bar(1, x), bar(1, x))
```
Note that any variable in the `use` will end up being duplicated.
Bend supports recursive functions of unrestricted depth:
```py
def native_num_to_adt(n):
if n == 0:
@ -207,12 +224,13 @@ def native_num_to_adt(n):
else:
return Nat.succ(native_num_to_adt(n - 1))
```
If your recursive function is not based on pattern matching syntax (like `if`, `match`, `fold`, etc) you have to be careful to avoid an infinite loop.
Since Bend is eagerly executed, some situations will cause function applications to always be expanded, which can lead to looping situations.
You can read how to avoid this in [Lazy definitions](docs/lazy-definitions.md).
Bend has native numbers and operations.
```py
def main:
a = 1 // A 24 bit unsigned integer.
@ -224,6 +242,7 @@ def main:
```
`switch` pattern matches on unsigned native numbers:
```py
switch x = 4:
// From '0' to n, ending with the default case '_'.
@ -237,19 +256,22 @@ switch x = 4:
```
Bend has Lists and Strings, which support Unicode characters.
```rs
def main:
return ["You: Hello, 🌎", "🌎: Hello, user"]
```
A string is desugared to a String data type containing two constructors, `String.cons` and `String.nil`.
List also becomes a type with two constructors, `List.cons` and `List.nil`.
```rs
// These two are equivalent
def StrEx:
"Hello"
def ids:
[1, 2, 3]
[1, 2, 3]
// These types are builtin.
enum String:
@ -265,6 +287,7 @@ def ids:
```
Characters are delimited by `'` `'` and support Unicode escape sequences. They are encoded as a U24 with the unicode codepoint as their value.
```
// These two are equivalent
def chars:
@ -274,10 +297,10 @@ def chars2:
[65, 0x4242, 0x1F30E]
```
### More features
Key:
- &#128215;: Basic resources
- &#128217;: Intermediate resources
- &#128213;: Advanced resources

View File

@ -144,13 +144,15 @@ pub fn desugar_book(
if !ctx.info.has_errors() { Ok(ctx.info) } else { Err(ctx.info) }
}
pub fn run_book(
pub fn run_book_with_fn(
mut book: Book,
run_opts: RunOpts,
compile_opts: CompileOpts,
diagnostics_cfg: DiagnosticsConfig,
args: Option<Vec<Term>>,
) -> Result<(Term, String, Diagnostics), Diagnostics> {
cmd: &str,
arg_io: bool,
) -> Result<Option<(Term, String, Diagnostics)>, Diagnostics> {
let CompileResult { core_book, labels, diagnostics } =
compile_book(&mut book, compile_opts.clone(), diagnostics_cfg, args)?;
@ -161,16 +163,27 @@ pub fn run_book(
let out_path = ".out.hvm";
std::fs::write(out_path, core_book.to_string()).map_err(|x| x.to_string())?;
let Output { status, stdout, stderr } = std::process::Command::new("hvm")
.arg("run")
.arg(out_path)
.output()
.map_err(|x| format!("While running hvm: {x}"))?;
let run_fn = |out_path: &str| {
let mut process = std::process::Command::new("hvm");
process.arg(cmd).arg(out_path);
if arg_io {
process.arg("--io");
process.stdout(std::process::Stdio::inherit());
process.spawn()?.wait_with_output()
} else {
process.output()
}
};
let Output { status, stdout, stderr } = run_fn(out_path).map_err(|e| format!("While running hvm: {e}"))?;
let out = String::from_utf8_lossy(&stdout);
let err = String::from_utf8_lossy(&stderr);
let status = if !status.success() { status.to_string() } else { String::new() };
if arg_io {
return Ok(None);
}
let Some((_, result)) = out.split_once("Result: ") else {
return Err(format!("Error reading result from hvm. Output :\n{}{}{}", err, status, out).into());
};
@ -182,7 +195,17 @@ pub fn run_book(
};
let (term, diags) = readback_hvm_net(&net, &book, &labels, run_opts.linear_readback);
Ok((term, stats.to_string(), diags))
Ok(Some((term, stats.to_string(), diags)))
}
pub fn run_book(
book: Book,
run_opts: RunOpts,
compile_opts: CompileOpts,
diagnostics_cfg: DiagnosticsConfig,
args: Option<Vec<Term>>,
) -> Result<(Term, String, Diagnostics), Diagnostics> {
run_book_with_fn(book, run_opts, compile_opts, diagnostics_cfg, args, "run", false).map(Option::unwrap)
}
pub fn readback_hvm_net(net: &Net, book: &Book, labels: &Labels, linear: bool) -> (Term, Diagnostics) {

View File

@ -2,7 +2,7 @@ use bend::{
check_book, compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
fun::{Book, Name},
load_file_to_book, run_book, CompileOpts, OptLevel, RunOpts,
load_file_to_book, run_book_with_fn, CompileOpts, OptLevel, RunOpts,
};
use clap::{Args, CommandFactory, Parser, Subcommand};
use std::path::{Path, PathBuf};
@ -39,49 +39,18 @@ enum Mode {
#[arg(help = "Path to the input file")]
path: PathBuf,
},
/// Compiles the program and runs it with the Rust HVM implementation.
Run(RunArgs),
/// Compiles the program and runs it with the C HVM implementation.
RunC(RunArgs),
/// Compiles the program and runs it with the Cuda HVM implementation.
RunCu(RunArgs),
/// Compiles the program to hvmc and prints to stdout.
Compile {
#[arg(
short = 'O',
value_delimiter = ' ',
action = clap::ArgAction::Append,
long_help = r#"Enables or disables the given optimizations
float_combinators is enabled by default on strict mode."#,
)]
comp_opts: Vec<OptArgs>,
#[command(flatten)]
warn_opts: CliWarnOpts,
#[arg(help = "Path to the input file")]
path: PathBuf,
},
/// Compiles the program and runs it with the hvm.
Run {
#[arg(short = 'p', help = "Debug and normalization pretty printing")]
pretty: bool,
#[command(flatten)]
run_opts: RunArgs,
#[arg(
short = 'O',
value_delimiter = ' ',
action = clap::ArgAction::Append,
long_help = r#"Enables or disables the given optimizations
float_combinators is enabled by default on strict mode."#,
)]
comp_opts: Vec<OptArgs>,
#[command(flatten)]
warn_opts: CliWarnOpts,
#[arg(help = "Path to the input file")]
path: PathBuf,
#[arg(value_parser = |arg: &str| bend::fun::parser::TermParser::new(arg).parse_term())]
arguments: Option<Vec<bend::fun::Term>>,
},
GenHvm(GenArgs),
/// Compiles the program to standalone C and prints to stdout.
GenC(GenArgs),
/// Compiles the program to standalone Cuda and prints to stdout.
GenCu(GenArgs),
/// Runs the lambda-term level desugaring passes.
Desugar {
#[arg(
@ -106,6 +75,57 @@ enum Mode {
#[derive(Args, Clone, Debug)]
struct RunArgs {
#[arg(short = 'p', help = "Debug and normalization pretty printing")]
pretty: bool,
#[arg(long, help = "Run with IO enabled")]
io: bool,
#[command(flatten)]
run_opts: CliRunOpts,
#[arg(
short = 'O',
value_delimiter = ' ',
action = clap::ArgAction::Append,
long_help = r#"Enables or disables the given optimizations
float_combinators is enabled by default on strict mode."#,
)]
comp_opts: Vec<OptArgs>,
#[command(flatten)]
warn_opts: CliWarnOpts,
#[arg(help = "Path to the input file")]
path: PathBuf,
#[arg(value_parser = |arg: &str| bend::fun::parser::TermParser::new(arg).parse_term())]
arguments: Option<Vec<bend::fun::Term>>,
}
#[derive(Args, Clone, Debug)]
struct GenArgs {
#[arg(
short = 'O',
value_delimiter = ' ',
action = clap::ArgAction::Append,
long_help = r#"Enables or disables the given optimizations
float_combinators is enabled by default on strict mode."#,
)]
comp_opts: Vec<OptArgs>,
#[arg(long, help = "Generate with IO enabled")]
io: bool,
#[command(flatten)]
warn_opts: CliWarnOpts,
#[arg(help = "Path to the input file")]
path: PathBuf,
}
#[derive(Args, Clone, Debug)]
struct CliRunOpts {
#[arg(short = 'l', help = "Linear readback (show explicit dups)")]
linear: bool,
@ -228,6 +248,18 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
Ok(book)
};
let (gen_cmd, gen_supports_io) = match &cli.mode {
Mode::GenC(..) => ("gen-c", true),
Mode::GenCu(..) => ("gen-cu", false),
_ => ("gen", false),
};
let (run_cmd, run_supports_io) = match &cli.mode {
Mode::RunC(..) => ("run-c", true),
Mode::RunCu(..) => ("run-cu", false),
_ => ("run", false),
};
match cli.mode {
Mode::Check { comp_opts, warn_opts, path } => {
let diagnostics_cfg = set_warning_cfg_from_cli(DiagnosticsConfig::default(), warn_opts);
@ -238,7 +270,7 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
eprintln!("{}", diagnostics);
}
Mode::Compile { path, comp_opts, warn_opts } => {
Mode::GenHvm(GenArgs { comp_opts, warn_opts, path, .. }) => {
let diagnostics_cfg = set_warning_cfg_from_cli(DiagnosticsConfig::default(), warn_opts);
let opts = compile_opts_from_cli(&comp_opts);
@ -249,6 +281,39 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
println!("{}", compile_res.core_book);
}
Mode::GenC(GenArgs { comp_opts, io, warn_opts, path })
| Mode::GenCu(GenArgs { comp_opts, io, warn_opts, path }) => {
if io && !gen_supports_io {
Err("Selected mode does not support io.".to_string())?;
}
let diagnostics_cfg = set_warning_cfg_from_cli(DiagnosticsConfig::default(), warn_opts);
let opts = compile_opts_from_cli(&comp_opts);
let mut book = load_book(&path)?;
let compile_res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
let out_path = ".out.hvm";
std::fs::write(out_path, compile_res.core_book.to_string()).map_err(|x| x.to_string())?;
let gen_fn = |out_path: &str| {
let mut process = std::process::Command::new("hvm");
process.arg(gen_cmd).arg(out_path);
if io {
process.arg("--io");
}
process.output().map_err(|e| format!("While running hvm: {e}"))
};
let std::process::Output { stdout, stderr, status } = gen_fn(out_path)?;
let out = String::from_utf8_lossy(&stdout);
let err = String::from_utf8_lossy(&stderr);
let status = if !status.success() { status.to_string() } else { String::new() };
eprintln!("{err}");
println!("{out}");
println!("{status}");
}
Mode::Desugar { path, comp_opts, warn_opts, pretty } => {
let diagnostics_cfg = set_warning_cfg_from_cli(DiagnosticsConfig::default(), warn_opts);
@ -265,8 +330,14 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
}
}
Mode::Run { run_opts, pretty, comp_opts, warn_opts, arguments, path } => {
let RunArgs { linear, print_stats } = run_opts;
Mode::Run(RunArgs { pretty, io, run_opts, comp_opts, warn_opts, path, arguments })
| Mode::RunC(RunArgs { pretty, io, run_opts, comp_opts, warn_opts, path, arguments })
| Mode::RunCu(RunArgs { pretty, io, run_opts, comp_opts, warn_opts, path, arguments }) => {
let CliRunOpts { linear, print_stats } = run_opts;
if io && !run_supports_io {
Err("Selected mode does not support io.".to_string())?;
}
let diagnostics_cfg =
set_warning_cfg_from_cli(DiagnosticsConfig::new(Severity::Allow, arg_verbose), warn_opts);
@ -278,16 +349,18 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
let run_opts = RunOpts { linear_readback: linear, pretty };
let book = load_book(&path)?;
let (term, stats, diags) = run_book(book, run_opts, compile_opts, diagnostics_cfg, arguments)?;
eprint!("{diags}");
if pretty {
println!("Result:\n{}", term.display_pretty(0));
} else {
println!("Result: {}", term);
}
if print_stats {
println!("{stats}");
if let Some((term, stats, diags)) =
run_book_with_fn(book, run_opts, compile_opts, diagnostics_cfg, arguments, run_cmd, io)?
{
eprint!("{diags}");
if pretty {
println!("Result:\n{}", term.display_pretty(0));
} else {
println!("Result: {}", term);
}
if print_stats {
println!("{stats}");
}
}
}
};

View File

@ -1,3 +1,3 @@
compile
gen-hvm
tests/golden_tests/cli/compile_all.hvm
-Oall

View File

@ -1,3 +1,3 @@
compile
gen-hvm
tests/golden_tests/cli/compile_inline.hvm
-Oinline

View File

@ -1,4 +1,4 @@
compile
gen-hvm
tests/golden_tests/cli/compile_no_opts.hvm
-Ono-all
-Ono-eta

View File

@ -1,3 +1,3 @@
compile
gen-hvm
tests/golden_tests/cli/compile_pre_reduce.hvm
-Opre-reduce

View File

@ -1,4 +1,4 @@
compile
gen-hvm
tests/golden_tests/cli/compile_strict_loop.hvm
-Ono-all
-Arecursion-cycle
-Arecursion-cycle

View File

@ -1,3 +1,3 @@
compile
gen-hvm
tests/golden_tests/cli/compile_wrong_opt.hvm
-Ofoo

View File

@ -1,2 +1,2 @@
compile
gen-hvm
tests/golden_tests/cli/warn_and_err.hvm