xlsynth 0.45.0

Accelerated Hardware Synthesis (XLS/XLSynth) via Rust
Documentation
# Ahead-of-time (AOT) compilation

DSLX or XLS IR is compiled into object code at build time, and the resulting
object code is wrapped in generated Rust code to enable easy integration into
user code.

Advantages compared to the DSLX interpreter or the XLS JIT:

- Avoids compilation overhead at runtime.
- Exposed interface uses native Rust types (e.g. `u64`, `[u8]`, structs,
  arrays).
- High-performance interface avoids per-call allocation/deallocation, with
  minimal copying of arguments and outputs.

## AOT Design

The `xlsynth` AOT framework consists of the following pieces:

- The libxls library exposes a C API for compiling XLS IR into object code and
  metadata about how to invoke the code (buffer sizes, alignments, etc.).
- The XLS C API is wrapped in Rust code for convenience and safety:
  [`xlsynth/src/aot_lib.rs`]../src/aot_lib.rs.
- Builder code ([`xlsynth/src/aot_builder.rs`]../src/aot_builder.rs) generates
  Rust code that provides a native, high-performance interface to the underlying
  XLS object code. The generated code is encapsulated in an object that
  allocates the necessary buffers at creation time and exposes a simple entry
  point for calling the function. The generated code includes native Rust types
  for the interface.
- Runner code ([`xlsynth/src/aot_runner.rs`]../src/aot_runner.rs) is a library
  used by the generated code for tasks like allocating buffers and
  packing/unpacking arguments.

## Using AOT compilation

- Invoke the builder API from the `build.rs` file of the project to AOT-compile
  the XLS code and create the generated Rust code at build time.
- Add `mod`s in the project code (e.g. `lib.rs`) that pull in the generated Rust
  code via `include!`.
- Call the exported API to create the AOT object and invoke its `run` method.

For an example, see [`xlsynth/tests/aot-test-crate`](../tests/aot-test-crate/).

## Thread safety

The underlying object code is thread-safe, but the `AotRunner` object is not.
The object allocates internal buffers which are used for each invocation of the
AOT-compiled function. Each thread should create its own runner by calling
`new_runner`.

## Generated code

When AOT-compiled, the generated Rust wrapper module exposes a small typed
interface for calling the compiled code. The generated code is a wrapper around
`AotRunner` defined in `aot_runner.rs` with a method for creating the object and
two methods for running the compiled code:

- `run(...)`: Runs the compiled function and returns the output value. Raises an
  error if an assert failed.
- `run_with_events(...)`: Runs the compiled function and returns any trace or
  assert messages along with the output value. Does not raise an error if an
  assert failed. The caller can instead check for any assert messages.

Types are defined for each function input (`Input0`, `Input1`, etc) and the
function output (`Output`). Bits types of 64 bits or less are defined as aliases
of the smallest unsigned type which can hold the value (`bool`, `u8`, `u16`,
`u32`, or `u64`). Wider types are aliases of arrays of `u8` (e.g., `[u8; 16]`).
Tuples and arrays are defined as `structs` and arrays respectively.

As a simple example, consider a DSLX function that multiplies two 8-bit unsigned
values and returns a 16-bit unsigned product:

```dslx
pub fn mul8x8(a: u8, b: u8) -> u16 {
  (a as u16) * (b as u16)
}
```

The generated Rust code looks like:

```rust
// Type aliases generated from the XLS signature.
pub type Bits8 = u8;
pub type Bits16 = u16;

pub type Input0 = Bits8; // `a: bits[8]`
pub type Input1 = Bits8; // `b: bits[8]`
pub type Output = Bits16; // return: bits[16]

pub struct Runner {
    // wraps `xlsynth::AotRunner` internally
}

impl Runner {
    pub fn new() -> Result<Self, xlsynth::XlsynthError>;

    pub fn run(&mut self, a: &Input0, b: &Input1) -> Result<Output, xlsynth::XlsynthError>;

    pub fn run_with_events(
        &mut self,
        a: &Input0,
        b: &Input1,
    ) -> Result<xlsynth::AotRunResult<Output>, xlsynth::XlsynthError>;
}

pub fn new_runner() -> Result<Runner, xlsynth::XlsynthError>;
```

Typical call pattern:

```rust
let mut runner = mul8x8_aot::new_runner()?;
let out = runner.run(&3, &5)?;
assert_eq!(out, 15);
```