Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Vx (pronounced “vee-ex”) is a systems programming language for machines that are no longer a single processor. A modern node is a CPU, one or more GPUs, sometimes a neural accelerator, and a memory hierarchy with half a dozen distinct spaces in it — each with its own capacity, its own bandwidth, and its own rules about who is allowed to read it.

Most languages treat that hardware as infrastructure. You write the computation, and a large runtime library decides where it lands. The result is that a whole class of mistake — reading device memory from the host, exceeding the capacity of a scratchpad, using a buffer whose copy has not landed yet — is discovered at runtime, if you are lucky, and silently tolerated if you are not.

Vx takes the opposite position:

Heterogeneity belongs in the type system, not in the runtime.

A pointer into accelerator memory has a different type from a pointer into host DRAM. Crossing between them requires an explicit transfer(). A host thread dereferencing a device pointer is a compile error with a source span, not a segfault in production.

What that buys you

The compiler front-loads into type checking a set of bugs that normally surface much later:

CheckWhat it rules out
Address-space typingDereferencing a device pointer from the host
Capacity admissionA placement whose working set cannot fit the space it targets
Seam contractsReading a buffer whose asynchronous transfer has not been made visible
Linear typesUse-after-move of a consumed buffer
Borrow checkingAliasing and lifetime errors, with region tracking
Topology reachabilityA transfer between spaces with no declared path between them

Capacity admission is worth dwelling on, because it is the one with no equivalent elsewhere. Vx reads a machine file describing a real part — its memory hierarchy, capacities, bandwidths and interconnect — and checks your placements against it before a binary exists. An allocation that cannot fit in the scratchpad you assigned it to is a compile error, not an out-of-memory at training step 1200.

Who this is for

Vx is aimed at the layer underneath the machine-learning stack: runtimes, kernels, schedulers, inference engines, and the systems code that has to be correct across several kinds of silicon at once.

It is deliberately not aimed at exploratory work. PyTorch users mutate a model mid-loop, print a tensor’s shape, branch on it and carry on. In Vx — ahead-of-time compiled, statically regioned — that same dynamism takes real effort.

Vx is the right language for the thing that must be correct and fast across ten kinds of silicon. It is not the right language for the thing you are still figuring out.

How to read this book

If you want to run something in the next ten minutes, go to Install Vx and then Your first program.

If you want to know whether the language is worth your time before installing anything, read A tour of Vx, which covers the whole language with no accelerator involved, and then Topologies and memory, which is the part that is actually different.

If you are evaluating Vx for a real system, Machine files is the chapter that will tell you fastest whether the model matches your hardware.

Project status

Vx is young and under active development. The language, the type checker and the MLIR optimization pipeline are all still moving. Expect sharp edges, expect syntax to change, and please report what you hit — early bug reports are the most useful thing you can contribute right now.

Install Vx

Quick install

curl -fsSL https://vxlang.org/install.sh | sh

This downloads a prebuilt toolchain, verifies it against its published SHA-256, and unpacks it into ~/.vx. Nothing is written outside that directory and nothing needs root.

Then put it on your PATH:

export PATH="$HOME/.vx/bin:$PATH"

Add that line to ~/.zshrc or ~/.bashrc to make it permanent.

Supported platforms

PlatformStatus
macOS on Apple Silicon (M1–M4)Supported. Apple accelerator dispatch available.
Linux x86_64 (glibc 2.35+)Supported. CUDA dispatch when a toolkit is present.
Intel macOSNot supported — the runtime assumes Apple Silicon.
Linux arm64, WindowsNo prebuilt toolchain. Build from source.

Prerequisites

The installer checks for these and stops with the exact command to run if one is missing. It does not install them for you: a script piped into a shell should not quietly run your package manager.

LLVM 22 (macOS, and Linux before v0.0.3)

Vx lowers through MLIR, and shells out to mlir-translate, opt, llc and clang from LLVM 22 when it compiles. From v0.0.3, the Linux toolchains include these, so there is nothing to install on Linux. On macOS you install LLVM 22 yourself. The version matters — the MLIR C API changes between major releases, so LLVM 21 or 23 will not work.

# macOS
brew install llvm@22

# Ubuntu / Debian, only for a release before v0.0.3
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 22
sudo apt-get install -y libmlir-22-dev mlir-22-tools

libmlir-22-dev is the package people miss. It ships the MLIR C API and is not pulled in by llvm-22-dev.

On macOS, Homebrew’s LLVM is keg-only, so it is deliberately not on your PATH. You do not need to change that — the installer records where it found LLVM, and the vxc wrapper points the compiler at it directly.

z3 (optional)

Seam verification — proving that an asynchronous transfer has been made visible before the buffer is read — is discharged by shelling out to z3. It is opt-in behind --verify-seams, so a toolchain without z3 compiles and runs everything else normally; you only need it when you turn that flag on.

The binary is executed, so installing libz3-dev alone is not enough.

brew install z3          # macOS
sudo apt-get install z3  # Ubuntu / Debian

Verify the install

The installer compiles and runs a small program as its last step, so if it finished without complaint you are already working. To check by hand:

vxc --version

This prints the version, and for a release build also the commit it was built from, such as vxc 0.0.3 (f1fb0333). To get the commit in a build of your own, set VX_VERSION_WITH_COMMIT=1 when you run cargo build. Keep that line with anything you report: two builds of the same version can generate different code.

Then compile something. Note that --run propagates the program’s own exit code, so a non-zero exit status here is your program’s return value rather than a failure:

cat > hello.vx <<'EOF'
fn main() -> i32 {
    let x : i32 = 21;
    return x * 2;
}
EOF

vxc --run hello.vx

The last line should read:

[JIT] Program exited with code: 42

Installing a specific version

curl -fsSL https://vxlang.org/install.sh | VX_VERSION=v0.0.3 sh

VX_VERSION goes after the |, so that it is set for the sh that runs the installer.

Toolchains are unpacked side by side under ~/.vx/toolchains/, and ~/.vx/current is a symlink to the active one, so switching versions is a matter of repointing that link.

Uninstalling

rm -rf ~/.vx

Then remove the PATH line from your shell profile. The installer puts nothing anywhere else.

Troubleshooting

vxc: command not found ~/.vx/bin is not on your PATH. See the export line above.

Failed to run mlir-translate: No such file or directory The compiler cannot find the LLVM tools. This normally means LLVM was installed after Vx, so the installer never recorded its location. Re-run the installer — it will find LLVM and rewrite ~/.vx/current/etc/llvm-env.sh.

the Vx runtime library is missing The toolchain directory is incomplete, usually from an interrupted download. Re-run the installer.

Undefined MLIR symbols, or a crash on startup The LLVM on your system is not version 22. Check with llvm-config --version, and note that on macOS a bare llvm-config usually resolves to Xcode’s copy rather than Homebrew’s.

macOS refuses to run the binary Gatekeeper quarantines downloads from the internet. Clear the attribute:

xattr -dr com.apple.quarantine ~/.vx

Next

Building from source

You need this if you are on a platform with no prebuilt toolchain — Linux on arm64, or an Intel Mac — or if you intend to work on the compiler itself.

Everything below has been run on the platform it documents.

What the build needs

RequirementWhy
LLVM/MLIR 22Pinned by mlir-sys in Cargo.toml. A different major version fails to link with undefined MLIR C-API symbols.
Rust (stable)The compiler is written in Rust.
z3 — the binarySeam verification executes it. The library alone is not enough.
libffiThe dispatch runtime calls outlined kernels through their MLIR C interface.

Plus cmake, ninja, pkg-config, python3 and a C++ compiler.

macOS (Apple Silicon)

xcode-select --install

brew install llvm z3 cmake ninja pkg-config python3 rustup
rustup-init -y --default-toolchain stable

Check that Homebrew’s LLVM is the pinned major version:

$(brew --prefix llvm)/bin/llvm-config --version    # expect 22.x

If Homebrew has moved on, install the pinned one with brew install llvm@22 and run LLVM_VERSION=22 ./setup.sh.

Linux x86_64 (Ubuntu 24.04)

scripts/provision/setup_linux.sh installs everything:

./scripts/provision/setup_linux.sh

It installs build tooling (build-essential, ninja-build, cmake, pkg-config), libraries (libffi-dev, libz3-dev, z3, zlib1g-dev, libzstd-dev, libedit-dev, libxml2-dev), and LLVM 22 from apt.llvm.org — Ubuntu’s own repositories lag the pinned version.

libmlir-22-dev is the package people miss. It ships the MLIR C API that the Rust bindings link against, and it is not pulled in by llvm-22-dev.

Rust is installed separately by the script, via rustup.

Sizing, if you are provisioning a box for this: 40 GB of disk (a release build leaves target/ at about 2 GB, and cargo test adds a second profile), and as many cores as you can get. A reference release build takes about 1m48s on 36 vCPU with a warm crate cache.

Configure and build

Identical on both platforms:

./setup.sh           # writes config.local -- LLVM path, cargo/rustup homes, PATH
source config.local  # required in every new shell, before any cargo command
cargo build --release

setup.sh puts LLVM first on PATH, so an unsuffixed llvm-config, mlir-translate or clang++ resolves to the pinned version rather than to Xcode’s or to another LLVM on the box. It warns if what it found does not match the pin.

If you get llvm-config: command not found or cargo: command not found, you opened a new shell and did not source config.local. It is once per shell, every shell.

Verify

cat > /tmp/hello.vx <<'EOF'
fn main() -> i32 {
    let x: i32 = 21;
    return x * 2;
}
EOF

source config.local
./target/release/vxc --run /tmp/hello.vx

Expect the last line to read [JIT] Program exited with code: 42. Then run the suite:

cargo test

Optional components

The build succeeds without any of these; each unlocks one path.

Autodiff (Enzyme) — needed for the autodiff tests and for grad/jvp/vjp lowering:

# The argument is your LLVM 22's bin directory; on macOS, Homebrew's.
./scripts/provision/build_enzyme.sh /opt/homebrew/opt/llvm@22/bin .cargo/enzyme
export ENZYME_LIB="$(pwd)/.cargo/enzyme/LLVMEnzyme-22.dylib"   # .so on Linux

Apple neural engine primitives (macOS only) — build.rs compiles CoreML primitive models, and invokes bare python3, so coremltools has to be importable from that interpreter:

python3 -m pip install coremltools

Without it the build prints warning: Failed to compile matmul_4x4 with coremlc and continues. Kernels fall back to CPU execution; only accelerator dispatch is unavailable.

CUDA (Linux) — detected automatically when /usr/local/cuda is present. Set VX_DISABLE_CUDA=1 to force it off. The toolkit alone is enough to build; a GPU is only needed to run.

Packaging a toolchain

To produce a redistributable tarball from a source build:

./scripts/release/package.sh v0.0.3

That stages the compiler, its runtime library, the standard library and the machine files under dist/, rewrites the linked library paths so the binaries find their own dependencies, generates the wrapper scripts, and writes a .tar.gz alongside its SHA-256.

Your first program

Hello, 42

Every Vx program starts at main, which returns an i32.

fn main() -> i32 {
    let x : i32 = 21;
    return x * 2;
}

Save that as hello.vx and run it:

vxc --run hello.vx
[JIT] Translating to LLVM IR...
[JIT] Optimizing LLVM IR (-O0)...
[JIT] Compiling to native object (-O0)...
[JIT] Linking native executable...
[JIT] Executing native binary...
[JIT] Program exited with code: 42

--run compiles the program and executes it immediately, then propagates the program’s own exit code. A non-zero exit status from vxc --run is your program’s return value, not a compiler failure — this program genuinely exits 42.

Printing

print takes a value; print! takes a literal. Neither needs an import.

fn main() -> i32 {
    print!("the answer is ");
    print(42);
    print!("\n");
    return 0;
}

Compiling ahead of time

The JIT is convenient for iterating. For anything you intend to keep, compile to a native executable:

vxc -c hello.vx -o hello.o

vxc --help lists the other actions — --emit-mlir and --emit-llvm are the two you will reach for most when you want to see what the compiler did with your code.

Something with a shape to it

Types annotate a binding with :, let mut makes it mutable, and for ranges with ..:

fn sum_to(n : i32) -> i32 {
    let mut total : i32 = 0;
    for i in 0..n {
        total += i;
    }
    return total;
}

fn classify(x : i32) -> i32 {
    if x > 10 {
        return 1;
    } else if x == 10 {
        return 2;
    } else {
        return 3;
    }
}

fn main() -> i32 {
    print(sum_to(10));
    print!("\n");
    print(classify(15));
    print!("\n");
    return 0;
}

Diagnostics carry a code and a file:line:column position. Forget a return and the compiler says so directly:

Error[E3028] at hello.vx:2:3: 'add' returns i32 but its body can finish without returning a value

Every code the compiler can emit is listed in the diagnostic index, grouped by the stage that raises it.

Arrays and tensors

An array literal is a tensor, and indexing reads an element back:

fn main() -> i32 {
    let a : Tensor<f32, [4]> = [ 1.0, 2.0, 3.0, 4.0 ];
    print(a[0]);
    print!(" ");
    print(a[3]);
    return 0;
}

Tensor<f32, [4]> is a tensor of four f32 with its shape known at compile time. A ? stands in for a dimension that is not — Tensor<f32, [?, ?]> is a matrix whose extents are runtime values, which you read with .extent(0) and .extent(1).

Structs and methods

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn magnitude_squared(self: &Point) -> i32 {
        return self.x * self.x + self.y * self.y;
    }
}

fn main() -> i32 {
    let p = Point { x: 3, y: 4 };
    return p.magnitude_squared();
}

The receiver is written out in full: self: &Point borrows it, self: &mut Point borrows it mutably. There is no implicit self.

Where to go next

You now have enough to write ordinary programs. Two directions from here:

  • A tour of Vx covers the rest of the language — generics, enums, pattern matching, ownership — none of which involves an accelerator.
  • Topologies and memory is the part that makes Vx different from every other systems language: placing data in a named memory space and having the compiler check it.

A tour of Vx

This chapter covers the ordinary parts of the language — everything you would need to write a command-line program, with no accelerator in sight. If you have written Rust, most of this will look familiar; the differences are called out where they matter.

Values and types

#![allow(unused)]
fn main() {
let x : i32 = 21;      // annotated
let y = 21;            // inferred from context
let mut count = 0;     // mutable
}

Bindings are immutable unless you write mut.

The primitive types are the ones you would expect: i8 i16 i32 i64, u8 u16 u32 u64, f16 f32 f64, and bool. For machine learning there are also bf16, the 4-bit integers i4 and u4, and the fp8 and fp4 types in Low-precision numbers.

There are no implicit numeric conversions. An i32 does not become an i64 because the context wants one; you write the conversion. Integer literals infer to the type the context requires, so let n : i64 = 5; is fine, but mixing two differently-typed values in one expression is an error. This is deliberate — silent widening is a common source of both bugs and unintended performance cliffs.

A const at the top level names a value known while compiling. Its type is required, and each use is replaced by the value:

#![allow(unused)]
fn main() {
const SIDES : i32 = 6;
const EDGES : i32 = SIDES * 2;
}

The value is a number or a bool: a literal, or arithmetic on literals and other consts.

A const can also be a table of numbers. It is stored once, read-only, and read one number at a time:

#![allow(unused)]
fn main() {
const POWERS : Tensor<i64, [4]> = [1, 10, 100, 1000];

fn power_of_ten(i : i64) -> i64 {
    return POWERS[i];
}
}

Functions

#![allow(unused)]
fn main() {
fn add(a : i32, b : i32) -> i32 {
    return a + b;
}
}

Parameter types and the return type are both mandatory — there is no inference for either, and a function with no useful result is written -> void. A function may end with a bare expression instead of return, as in Rust.

Control flow

#![allow(unused)]
fn main() {
if x > 10 {
    // ...
} else if x == 10 {
    // ...
} else {
    // ...
}

for i in 0..n {
    // ...
}

while i < n {
    // until the condition is false
    i = i + 1;
}

loop {
    // forever, until you break
    if done { break; }
}
}

The three loops are for over a range, while with a condition checked before every turn, and bare loop with an explicit break.

0..n is a half-open range: it includes 0 and excludes n.

Structs

struct Point {
    x: i32,
    y: i32,
}

fn main() -> i32 {
    let p = Point { x: 3, y: 4 };
    let a = p.x;
    return a;
}

Methods go in an impl block, and the receiver is written out in full — there is no implicit self:

#![allow(unused)]
fn main() {
impl Point {
    fn magnitude_squared(self: &Point) -> i32 {
        return self.x * self.x + self.y * self.y;
    }

    fn translate(self: &mut Point, dx: i32, dy: i32) -> void {
        self.x += dx;
        self.y += dy;
    }
}
}

A return type is never optional. A function that produces no useful result returns void, and exits early with a bare return;.

&Point borrows immutably, &mut Point mutably. A method with no self parameter is an associated function, called as Point::make(...).

Tuples

A tuple groups a few values without naming a struct for them. Its elements are read by position, or taken apart with let:

fn divmod(a: i64, b: i64) -> (i64, i64) {
    return (a / b, a % b);
}

fn main() -> i32 {
    let (q, r) = divmod(17, 5);
    let pair = (q, r);
    return (pair.0 + pair.1) as i32;
}

A tuple has two to six elements. Patterns nest, let ((a, _), c) = t;, and _ skips an element.

match takes a tuple apart too. A literal in a pattern must equal that element, and a name binds it. The last arm must match every value, such as _ or (x, y):

#![allow(unused)]
fn main() {
fn sign_pair(a: i64, b: i64) -> i64 {
    match (a, b) {
        (0, 0) => { return 0; },
        (0, y) => { return y; },
        (x, _) => { return x; }
    }
}
}

An element of a pattern can also be an enum variant, which binds its payload:

#![allow(unused)]
fn main() {
fn add(a: Option<i64>, b: Option<i64>) -> i64 {
    match (a, b) {
        (Option<i64>::Some(x), Option<i64>::Some(y)) => { return x + y; },
        (Option<i64>::Some(x), _) => { return x; },
        _ => { return 0; }
    }
}
}

Enums and pattern matching

Enums carry data:

#![allow(unused)]
fn main() {
enum Result {
    Ok(i32),
    Err(i32),
}

enum Color {
    Red, Green, Blue,
}
}

Construct a variant with ::, and take it apart with match:

#![allow(unused)]
fn main() {
fn unwrap_or(r: Result, default: i32) -> i32 {
    let mut out = default;
    match r {
        Result::Ok(val) => { out = val; },
        Result::Err(code) => { out = -code; },
    }
    return out;
}
}

A data-carrying enum is laid out as a tag plus a payload.

A match can also be used as a value directly, with each arm evaluating to a result:

#![allow(unused)]
fn main() {
fn pick(x : i32) -> i32 {
    match x { 0 => { 7 }, _ => { 9 } }
}
}

Stopping a program

panic(msg) prints panic: and the message, then stops the program; abort() stops it with no message. A function declared -> ! never returns, like core::panic’s unreachable(), todo() and unimplemented(). A function can end with a call to one, and the call can stand where any value is expected:

#![allow(unused)]
fn main() {
import core::panic;

fn get(ok: bool, v: i64) -> i64 {
    let x: i64 = if ok { v } else { todo() };
    return x;
}
}

Option::expect(msg) and Result::expect(msg) stop the program with the caller’s message.

Arrays and tensors

An array literal is a tensor:

#![allow(unused)]
fn main() {
let a : Tensor<f32, [4]> = [ 1.0, 2.0, 3.0, 4.0 ];
let first = a[0];
}

The shape is part of the type. Tensor<f32, [4]> has four elements, known at compile time. A ? marks a dimension that is only known at runtime:

#![allow(unused)]
fn main() {
fn matmul(a : Tensor<f32, [?, ?]>, b : Tensor<f32, [?, ?]>) -> Tensor<f32, [?, ?]> {
    let mut result : Tensor<f32, [?, ?]> =
        unsafe { Tensor<f32, [?, ?]>::uninit([a.extent(0), b.extent(1)]) };

    for i in 0..a.extent(0) {
        for j in 0..b.extent(1) {
            result[i][j] = 0.0;
            for k in 0..a.extent(1) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    return result;
}
}

.extent(n) reads the size of dimension n. Shapes that are known statically get checked statically — a matmul whose inner dimensions disagree is a compile error rather than a runtime one.

m[i] is row i, and a row has the size of the last dimension: for a [2, 3] tensor, m[0][j] needs j < 3 and does not reach into the next row. An index the compiler can see is too large, such as m[0][5] or m[0][j] inside for j in 0..6, is a compile error. To walk all the elements in one loop, .reshape the tensor to one dimension.

.reshape(shape) gives the same elements in a new shape, without copying them. .pad(shape) makes a larger tensor with zeros after each axis, which is how a matrix is rounded up to a tile size:

#![allow(unused)]
fn main() {
let m : Tensor<f32, [30, 30]> = Tensor<f32, [30, 30]>::fill(1.0);
let tiled : Tensor<f32, [32, 32]> = m.pad([32, 32]);   // m[i][j] is tiled[i][j]
let row : Tensor<f32, [900]> = m.reshape([900]);
}

The language reference lists the other forms, which keep or drop elements in row order.

Collections

The standard library ships the usual containers. Vec<T> is a growable array:

import std::vec;

fn main() -> i32 {
    let mut v = Vec<i32>::new();
    v.push(10);
    v.push(32);
    return v.get(0) + v.get(1);
}

Also available: HashMap, HashSet, Option, Result, String, Box, and iterator adaptors. See the standard library for the full list.

Modules

One file is one module. import pulls another in:

#![allow(unused)]
fn main() {
import std::vec;
import std::io;
import graph::traversal;
}

std:: resolves against the standard library shipped with your toolchain. Anything else resolves against the library search path and then the current directory.

An import brings in everything the module declares, and everything it imports. A module’s own declaration wins over an imported one with the same name, so declaring struct Range does not disturb the library code that uses core::iter’s. When two imports declare the same name, using it is an error, since nothing says which one is meant.

Unsafe

Raw pointers exist, and the operations that can go wrong with them require unsafe:

extern "C" {
    fn malloc(size : i64) -> *mut i8;
    fn free(p : *mut i8) -> void;
}

fn main() -> i32 {
    unsafe {
        let p = malloc(4);
        p[0] = 42;
        print(p[0]);
        free(p);
    }
    return 0;
}

Dereferencing a raw pointer, indexing through one, reading a field through one, and calling an unsafe fn all require an unsafe block. A function that takes a caller’s raw pointer and dereferences it should be declared unsafe fn, so the obligation is visible in its signature rather than buried in its body. Marking a function unsafe does not allow raw pointer operations in its body. Each one still needs an unsafe block, as in Unsafe and FFI.

extern "C" blocks declare foreign functions. Vx’s C ABI interop is zero-overhead: there is no marshalling layer.

Comptime

if comptime selects a branch at compile time. The branches not taken are pruned before semantic analysis, so they may refer to things that do not exist on the target being compiled for:

#![allow(unused)]
fn main() {
let mut val = 0;

if comptime Topology::Current == Topology::CPU {
    val = 1;
} else if comptime Topology::Current == Topology::CPU_AVX512 {
    val = 2;
} else {
    val = 3;
}
}

Topology::Current is the topology the current region compiles for. This is how one source file carries code for several targets without the dead paths having to typecheck against all of them.

if is also an expression:

#![allow(unused)]
fn main() {
return if val == 1 { 0 } else { 1 };
}

Const generics let a value appear in a type — Tensor<f32, [N]> for a const N : i32 — and are resolved by monomorphization.

What is next

Vx for Rust programmers

Vx looks like Rust, so Rust that almost compiles is the first Vx most people write. This chapter lists what to change. Each item shows the Rust form and the Vx form.

Modules and items

  • import, not use. use core::option; is import core::option;. An import brings in everything the module declares.
  • pub exports a symbol. Every top-level item is visible to a module that imports it, with or without pub. A pub fn is also exported from the object file, so C can call it.
  • No attributes. There is no #[derive(..)], #[test] or #[inline].

Functions and methods

  • The return type is always written. fn log(x : i32) is fn log(x : i32) -> void, and main returns i32: fn main() -> i32 { .. return 0; }.
  • self has a type. fn get(&self) is fn get(self : &Self), &mut self is self : &mut Self, and self is self : Self. The type’s own name works too: self : &Point.
  • Closure parameters have types. |x| x + 1 is | x : i64 | x + 1.
  • No impl Trait parameters. fn show(g : &impl Area) is fn show<T : Area>(g : &T).

Types and values

  • No turbofish. Vec::<i32>::new() is Vec<i32>::new().
  • An enum variant is written with its type. Some(3) is Option<i64>::Some(3), and None is Option<i64>::None(). Option comes from import core::option;.
  • An integer variable with no written type is i32. Rust reads let mut total = 0; as an i64 when i64s are added to it later. Vx does not, so write let mut total : i64 = 0;.
  • Arrays are tensors. [i32; 4] is Tensor<i32, [4]>. The shape is part of the type, and a ? marks a size known only at run time: Tensor<f32, [?, ?]>.
  • Uninitialized storage is unsafe. MaybeUninit has no equivalent. Write unsafe { Tensor<f32, [4]>::uninit() } and set every element before reading it, or use Tensor<f32, [4]>::new(), which is zeroed.

Control flow

  • A match arm’s body is a block. 0 => 7, is 0 => { 7 },.
  • No if let or while let. Use match.
  • No ? operator. Use match and return early.

Printing

  • print(x) prints one value with no newline.
  • print!("text") prints text. println!("{} and {}", a, b) puts each value in place of a {} and ends the line. eprint! and eprintln! do the same on standard error.
  • There is no function called println: println(x) is an error. Write println!("{}", x).

What Rust does not have

Rust has one kind of memory. Vx describes several, and checks where data lives:

  • A tensor’s third type argument is the memory it lives in: Tensor<f32, [4, 4], Memory::NPU_HBM>.
  • transfer(t, Memory::NPU_HBM) moves data to another memory. It is required even when the move costs nothing on the hardware.
  • spawn on(Topology::NPU[0]) { .. } runs a block on a device. Everything the block uses must already be in memory that device can reach.
  • unsafe allows raw pointers and calls to unsafe fn, as in Rust. It does not allow reading device memory from the host: that stays a compile error.

Topologies and memory explains these.

A complete program

Most of the differences above, in one program:

import core::iter;
import core::option;

trait Area {
    fn area(self : &Self) -> i64;
}

struct Rect {
    w : i64,
    h : i64,
}

impl Rect {
    fn grow(self : &mut Rect, by : i64) -> void {
        self.w = self.w + by;
        self.h = self.h + by;
    }
}

impl Area for Rect {
    fn area(self : &Rect) -> i64 {
        return self.w * self.h;
    }
}

fn total<T : Area>(a : &T, b : &T) -> i64 {
    return a.area() + b.area();
}

fn half(n : i64) -> Option<i64> {
    if n % 2 == 0 {
        return Option<i64>::Some(n / 2);
    }
    return Option<i64>::None();
}

// Rust would write `half(half(n)?)`.
fn quarter(n : i64) -> Option<i64> {
    match half(n) {
        Option<i64>::Some(h) => { return half(h); },
        Option<i64>::None() => { return Option<i64>::None(); }
    }
}

fn main() -> i32 {
    let mut r = Rect { w : 2, h : 3 };
    r.grow(1);
    let s = Rect { w : 1, h : 1 };
    println!("area {}", total(&r, &s));

    let squares = range(1i64, 4i64).map(| x : i64 | x * x).sum();
    println!("squares {}", squares);

    match quarter(12) {
        Option<i64>::Some(q) => { println!("quarter {}", q); },
        Option<i64>::None() => { println!("no quarter"); }
    }

    let mut t : Tensor<f32, [3]> = unsafe { Tensor<f32, [3]>::uninit() };
    for i in 0..3 {
        t[i] = 0.5 * (i as f32);
    }
    println!("last {}", t[2]);
    return 0;
}

It prints:

area 13
squares 14
quarter 3
last 1

Control flow

Vx has five ways to branch or repeat: if, loop, while, for, and match.

if and else

fn classify(x : i32) -> i32 {
    if x < 0 {
        return 0;
    } else if x == 0 {
        return 1;
    } else {
        return 2;
    }
}

fn main() -> i32 {
    return classify(5) - 2;
}

Braces are always required. There is no single-statement form.

An if whose branches all return is a statement, not a value. An if used in value position has to produce a value on every path:

fn main() -> i32 {
    let x : i32 = 3;
    let label = if x > 2 { 1 } else { 0 };
    return label - 1;
}

loop

loop repeats until something breaks out of it.

fn main() -> i32 {
    let mut i : i32 = 0;
    loop {
        if i >= 3 {
            break;
        }
        i = i + 1;
    }
    return i - 3;
}

continue skips to the next turn of the loop:

fn main() -> i32 {
    let mut seen : i32 = 0;
    let mut i : i32 = 0;
    loop {
        i = i + 1;
        if i < 3 {
            continue;
        }
        seen = seen + 1;
        if i >= 5 {
            break;
        }
    }
    return seen - 3;
}

Loop invariants

A loop can carry an invariant: a condition that must hold on every turn. The prover checks it.

fn main() -> i32 {
    let limit : i32 = 3;
    let mut i : i32 = 0;
    loop invariant(limit > 0) {
        if i >= limit {
            break;
        }
        i = i + 1;
    }
    return 0;
}

The parentheses around the condition are required here, unlike requires and ensures on a function, which take theirs optionally. That inconsistency is not deliberate — it is Vx#501.

while

while checks its condition before every turn, including the first, and stops as soon as the condition is false. If the condition is false to begin with, the body never runs.

fn main() -> i32 {
    let mut i : i32 = 0;
    while i < 4 {
        i = i + 1;
    }
    return i - 4;
}

break and continue work as they do in loop. continue goes back to the condition check, so the condition is never skipped:

fn main() -> i32 {
    let mut i : i32 = 0;
    let mut odd : i32 = 0;
    while i < 6 {
        i = i + 1;
        if i % 2 == 0 {
            continue;
        }
        odd = odd + 1;
    }
    return odd - 3;
}

A while is shorthand for loop { if cond { body } else { break } }, and the compiler treats it as exactly that. So a condition that is not a bool is reported as an error in an if.

A while can carry invariant clauses after its condition, as loop and for can:

fn count_to(n : i32) -> i32
    requires n > 0
{
    let mut i : i32 = 0;
    while i < n invariant(n > 0) {
        i = i + 1;
    }
    return i;
}

fn main() -> i32 {
    return count_to(3) - 3;
}

There is no while let yet, because there is no if let. Write while let Some(x) = next() as a loop with a match that breaks on None.

for

for walks a range or anything that implements Iterator.

fn main() -> i32 {
    let mut total : i32 = 0;
    for i in 0..4 {
        total = total + i;
    }
    return total - 6;
}

0..4 counts from 0 up to but not including 4, so that loop adds 0 + 1 + 2 + 3.

A for loop can carry an invariant in the same way a loop can.

match

match compares a value against patterns, in order, and runs the first arm that fits.

fn main() -> i32 {
    let x : i32 = 1;
    match x {
        0 => { return 1; }
        _ => { return 0; }
    }
}

_ matches anything. It is usually the last arm.

match is most useful with an enum, where each arm handles one variant:

enum Colour {
    Red,
    Green,
}

fn main() -> i32 {
    let c = Colour::Red;
    match c {
        Colour::Red => { return 0; }
        Colour::Green => { return 1; }
    }
}

Where to next

Ownership and borrowing

Vx has no garbage collector and no mandatory reference counting. Memory is managed by ownership, checked at compile time, in the same family as Rust’s model.

Owning and borrowing

A binding owns its value. Passing it to a function by value moves it, and the original binding is no longer usable:

#![allow(unused)]
fn main() {
let v = make_buffer();
consume(v);      // v is moved
// reading v here is an error
}

Borrow instead of moving to keep the original alive:

#![allow(unused)]
fn main() {
fn total(v : &Vec<i32>) -> i32 { /* ... */ }
fn append(v : &mut Vec<i32>, x : i32) { /* ... */ }
}

&T is a shared borrow, &mut T is an exclusive one. The usual rule applies: any number of shared borrows, or exactly one exclusive borrow, never both at once. The checker tracks variance and regions, so a borrow cannot outlive what it points into.

The rules the checker enforces

Each rule below is a compile error, with the code the error index lists it under.

A moved value cannot be used (E4001). Assigning a value, or passing it by value, moves it unless its type is Copy; numbers are. Giving the variable a new value makes it usable again. A method that takes self by value moves the value it is called on: v.into_iter() moves v.

#![allow(unused)]
fn main() {
let a = S { v : 1 };
let b = a;          // a is moved
print(a.v);         // E4001
}

Only one branch of an if or arm of a match runs, so each may move the same value. After the if, the value counts as moved if any branch that reaches the next line moved it.

Inside a loop, a value moved in the body is used again on the next pass, so it is refused at the loop unless the body gives it a new value first. A continue goes round again as well. A move just before break is fine; the value then counts as moved after the loop.

#![allow(unused)]
fn main() {
let a = S { v : 1 };
while more() {      // E4001: a is moved on every pass
    take(a);
}
}

A value cannot move while it is borrowed (E4007). If a borrow of a is used after a moves, the borrow would point at a value that has gone.

#![allow(unused)]
fn main() {
let r = &a;
let b = a;          // E4007: r is used below
print(r.v);
}

A reference cannot give away what it points at (E4008). let b = *r; would move the value out of r, which only borrows it. Copy or clone it instead; for a number, *r is a copy and is fine.

Reading a field that has something to drop moves it out of its struct. After let x = s.a;, s.a has no value and s cannot be used whole, but its other fields can, and s drops only the fields it still holds. Assigning s.a gives it a value again. A field cannot be moved out through a reference (E4008), out of a struct whose type implements Drop, since its drop needs every field, or out of a value with no name, such as make().a, which drops all its fields at the end of the statement (E4013). Borrow it (&s.a) or clone it (s.a.clone()) instead. A field with nothing to drop is copied.

A variable without mut is read-only (E4010). As in Rust, let x = .. and a parameter x : T cannot be assigned, nor can a field or element of x; x cannot be borrowed &mut; and a method that takes &mut self cannot be called on it. Write let mut x = .., or mut x : T for a parameter (mut self : T for a method that takes self by value). A &mut held in a variable without mut can still be written through, since that changes what it points at, not the variable.

#![allow(unused)]
fn main() {
let x = 1;
x = 2;              // E4010
let mut y = 1;
y = 2;              // fine
let r = &mut y;
*r = 3;             // fine: writes y, not r
}

Shared or exclusive, never both (E4002, E4003, E4004). While a &mut borrow of x is still going to be used, x cannot be read, written or borrowed again. While a & borrow is still going to be used, x cannot be borrowed &mut. Two borrows passed to one call count as alive together.

#![allow(unused)]
fn main() {
let m = &mut x;
print(x);           // E4002: m is used below
*m = 2;
}

A borrow ends at its last use. It does not last to the end of the block, so this is fine:

#![allow(unused)]
fn main() {
let m = &mut x;
*m = 2;
print(x);           // m is not used again
}

Different fields are borrowed separately. &mut p.a and &mut p.b can be alive together; &mut p.a twice, or &p.a while &mut p is alive, cannot.

A reborrow borrows the reference (E4002, E4004). let n = &mut *m; borrows m for as long as n is used. Until then m cannot be read, written or reborrowed &mut again. After n’s last use, m works as before.

#![allow(unused)]
fn main() {
let m = &mut x;
let n = &mut *m;
*m = 4;             // E4002: n is used below
*n = 5;
}

A borrowed variable cannot be assigned (E4009). While a & borrow of x, or of a field or element of it, is still going to be used, that part of x cannot be given a new value. A borrow passed only to a call, as in v.cmp(&best), ends with the call.

#![allow(unused)]
fn main() {
let r = &x;
x = 2;              // E4009: r is used below
print(*r);
}

Only a &mut can be written through (E4006). *r = v, r.f = v and r[i] = v need r to be a &mut. So does a &mut field reached through a &. A & cannot be turned into a &mut either: &mut *r, or calling a method that takes &mut self through r, needs r to be a &mut.

A reference cannot outlive what it points at (E4005). A reference to a local, or a value or closure holding one, cannot be returned, stored through a reference, or passed to a call that could store it. Inside a function, a variable declared outside a block cannot be given a reference to a variable declared inside it.

Views of a tensor

A row q[i], a field h.t that holds a tensor, and a view of either are views: they share their owner’s memory instead of copying it. Writing r[0] through a row r = q[1] changes q[1][0], and the other way round. So a view is a borrow of its owner, exactly as &q is, and the rules above apply to it.

#![allow(unused)]
fn main() {
let mut q = Tensor<f32, [2, 4]>::new();
let mut r = q[1];   // borrows q mutably, since r is `mut`
r[0] = 5.0;         // writes q[1][0]
print(q[1][0]);     // fine: r is not used again
}
  • A view is made by indexing to a row (q[1]), reading a tensor field (h.t), reshaping (q.reshape([8])), or taking a view of a view. A view chosen by an if or a match borrows every tensor it may come from, and a view taken through a reference (rq[1] with rq = &q) borrows the tensor behind the reference. A closure that uses a view keeps the view’s tensor borrowed for as long as the closure is used.
  • A view declared mut is a &mut borrow, and its owner must be mut (E4010). Any other view is a & borrow, so two of them can be used together.
  • The owner cannot be moved (E4007), assigned (E4009) or, under a mut view, read (E4002) while the view is still going to be used. As with &, the borrow ends at the view’s last use.
  • A view of a tensor the function owns, a local or a parameter taken by value, cannot be returned (E4005). A view of a tensor taken by reference (q : &Tensor<..>) can be: it points into the caller’s tensor.
  • A view cannot be stored where a tensor of its own is held: a variable that already exists (keep = q[1]) or a struct field (h.t = q[1], Holder { t : q[1] }) (E4011). It would replace that tensor with a window onto another. Bind it to a new variable with let instead. Writing a row into another row, p[0] = q[1], copies the elements and is fine.
  • A view cannot be passed to a function that takes a tensor by value (E4011): the function would own memory that is the owner’s. Pass a copy, f(q[1].clone()), or make the function take the tensor by reference, f(&q[1]). t.clone() makes a new tensor of t’s type with a copy of its elements.

Why views are checked this strictly. Vx is moving to freeing memory automatically (docs/implementation_plans/drop_semantics.md). A tensor that only holds memory will be freed after the last use of the tensor and of every view of it, and a type with a Drop of its own at the end of its block. Freeing at the last use is only safe if the checker sees every view, so a free that comes too early is a borrow checker bug, not a rule for the programmer to remember. The checker tells a view from an owner by where the value came from, since both have the type Tensor; a separate view type (#400) may replace this later.

Not checked yet. These views are still invisible to the checker, or refused when they need not be:

CaseIssue
Two mut rows of different indices, refused because indices are not told apart#1060

Dropping values

When its owner goes away, a value is dropped: what it owns is released. A value that was moved is not dropped, because its new owner drops it. Giving a variable a new value drops the old one.

  • Memory only: a tensor, or a Vec of numbers, is freed after its last use.
  • A Drop implementation: a value whose type implements Drop waits for the end of its block, and values in one block are dropped in reverse order of declaration. Its drop runs first, then its fields are dropped in the order they are declared. Dropping an enum drops the payload of the variant it holds.
import core::ops;

struct Noisy {
    id : i32,
}

impl Drop for Noisy {
    fn drop(self : &mut Noisy) -> void {
        print(self.id);
    }
}

fn main() -> i32 {
    let a = Noisy { id : 1 };
    let b = Noisy { id : 2 };
    return 0;   // prints 2, then 1
}

Vec, Box, String, File and the socket types implement Drop, so none of them is freed by hand. drop cannot be called directly (E4012). To end a value’s life early, core::mem has drop(x); forget(x) ends it without dropping anything, and needs_drop<T>() says whether dropping a T does anything.

Linear values

Some values are linear: they must be consumed exactly once, and the checker enforces it. Device buffers are the motivating case. A buffer that has been handed off to an accelerator has left your control, and reading it again is a use-after-move — reported as a compile error rather than as corrupted data.

This is stronger than an ordinary move check. A linear value cannot be quietly dropped either, because dropping a device allocation without releasing it is a leak the runtime cannot detect for you.

Boxing

Recursive types must be boxed. Box<T> is a heap allocation with a single owner:

#![allow(unused)]
fn main() {
import std::box;

struct Node {
    value: i32,
    next: Box<Node>,
}
}

The requirement is not an oversight — it is what lets the compiler give every nominal type a size without solving a fixpoint across module boundaries, which in turn is what lets the frontend compile modules in parallel with no shared state.

Raw pointers

*const T and *mut T are raw pointers, and they opt out of all of the above. Because of that, the operations that can go wrong with them require unsafe:

  • dereferencing one
  • indexing through one
  • reading a field through one
  • calling an unsafe fn

A function that takes a caller’s raw pointer and dereferences it should be declared unsafe fn, so the obligation is visible in the signature rather than buried in the body. Marking a function unsafe does not allow raw pointer operations in its body. Each one still needs an unsafe block, as in Unsafe and FFI.

unsafe fn read_first(p : *const i32) -> i32 {
    return unsafe { *p };
}

fn main() -> i32 {
    let x : i32 = 42;
    unsafe {
        return read_first(&x) - 42;
    }
}

Keep unsafe blocks small. The point of the annotation is that the region a human has to verify by hand is written down and searchable.

Generics and traits

Generics in Vx are monomorphized: each instantiation becomes its own concrete function or type at compile time. There is no boxing, no vtable and no dynamic dispatch unless you ask for it.

Generic types and functions

#![allow(unused)]
fn main() {
struct Pair<T> {
    first: T,
    second: T,
}

enum Maybe<T> {
    Just(T),
    Nothing,
}
}

Instantiate by naming the argument:

enum Maybe<T> {
    Just(T),
    Nothing,
}

fn main() -> i32 {
    let m = Maybe<i32>::Just(42);

    let mut result = 0;
    match m {
        Maybe<i32>::Just(val) => { result = val; },
        Maybe<i32>::Nothing => { result = 0; },
    }
    return result;
}

Generic impls

An impl block can be generic, or specific to one instantiation:

#![allow(unused)]
fn main() {
impl<T> Pair<T> {
    fn first(self: &Pair<T>) -> T {
        return self.first;
    }
}

impl Pair<i32> {
    fn sum(self: &Pair<i32>) -> i32 {
        return self.first + self.second;
    }
}
}

When several impls could apply, the most specific one wins.

Bounds

Constrain a parameter with :, and write several with +:

#![allow(unused)]
fn main() {
impl<T : Float> Tensor<T, [?, ?]> {
    // ...
}
}
#![allow(unused)]
fn main() {
fn describe<T : Doubles + Tags>(x : T) -> i32 {
    return x.twice() + x.tag();
}
}

A bound can give the trait type arguments, or say what its associated types must be. A bound may also name another parameter’s associated type:

#![allow(unused)]
fn main() {
fn first<I : Iterator<Item = i64>>(it : I) -> i64 { /* ... */ }
fn total<I : Iterator, S : Sum<I::Item>>(it : I) -> S { /* ... */ }
}

Every bound has to hold at the call site, and each one that does not is reported. Note that a bound constrains callers, not the body: a generic body is checked after monomorphization against the concrete type, so it can call any method that type has, whether or not a bound named it.

Traits

A trait method may carry a body. An impl that does not write that method inherits it:

#![allow(unused)]
fn main() {
trait Counts {
    fn value(self : Self) -> i32;
    fn doubled(self : Self) -> i32 {
        return self.value() * 2;
    }
}
}

self.value() inside the default dispatches to whichever impl inherited it, so one body serves every implementor. An impl that writes doubled itself keeps its own.

Traits describe shared behaviour, and are implemented with impl ... for:

#![allow(unused)]
fn main() {
impl<T> Iterator<VecIter<T>, T> for VecIter<T> {
    // ...
}
}

That is how Vec participates in for loops and in the iterator adaptors — map and friends are ordinary generic functions over the Iterator trait rather than compiler magic.

Const generics

A value can be a type parameter, not only a type. This is what makes statically-shaped tensors work:

#![allow(unused)]
fn main() {
fn dot<const N : i32>(a : Tensor<f32, [N]>, b : Tensor<f32, [N]>) -> f32 {
    let mut acc = 0.0;
    for i in 0..N {
        acc += a[i] * b[i];
    }
    return acc;
}
}

Because N is part of the type, passing two tensors of different lengths to dot is a compile error rather than a runtime check you forgot to write. Each distinct N monomorphizes to its own function, so the loop bound is a constant the optimizer can see.

How this stays fast to compile

Every symbol, nominal type and monomorphized instantiation is identified by a flat 256-bit identifier rather than by a pointer into a shared tree. Combined with a nominal type system and mandatory boxing for recursive types, that decouples modules from one another: the frontend resolves and checks them in parallel across cores, with no query engine, no locks and no shared mutable state.

The observable consequence is that the same source produces byte-identical MLIR whether it is compiled serially or in parallel — which is asserted in the test suite rather than assumed.

Compile-time evaluation

comptime marks work that happens while the program is being compiled, not while it runs.

comptime blocks

A comptime block runs during compilation.

fn main() -> i32 {
    comptime {
        let size : i32 = 4 * 4;
    }
    return 0;
}

The block’s value, and every check in it, has to be knowable at compile time. A comptime block cannot call into C or touch a device. It may read a value that is known only when the program runs, but only where nothing the block produces depends on it. A comptime block must run has the rules.

Arithmetic gives the same answers it would at run time. Two integers divide as integers, so 7 / 2 is 3. An integer result outside its type, such as an i32 above 2147483647, has no compile-time value at all, rather than a wrapped one. A block whose value or whose assert depends on such a result is an error.

comptime conditions

if comptime chooses a branch at compile time. The branch not taken is not compiled.

fn main() -> i32 {
    if comptime 1 < 2 {
        return 0;
    }
    return 1;
}

This is different from an ordinary if with a constant condition. An ordinary if is compiled in full and then possibly folded by the optimiser; if comptime decides before code generation, so the untaken branch does not have to compile at all. That matters when a branch is only valid for some types or some hardware.

Fixed-size arrays

Compile-time code can build a fixed-size array of scalars, read its elements, and write them back. The length is fixed when the array is built:

fn main() -> i32 {
    comptime {
        let mut a : Tensor<i64, [3]> = [ 3i64, 1i64, 2i64 ];
        a[0] = 9i64;
        assert(a[0] == 9, "the write happened during compilation");
    }
    return 0;
}

Because both the array and the index are known while compiling, an index past the end is a compile error rather than a bad read at run time:

#![allow(unused)]
fn main() {
let a : Tensor<i64, [3]> = [ 3i64, 1i64, 2i64 ];
let x = a[5];   // error[E8003]: index 5 is out of range: this array has 3 elements
}

An index the compiler cannot work out, such as the result of a call, leaves it unable to follow the write. When that happens the array stops having a compile-time value entirely, rather than keeping the value it had before the write. A later assert on that array cannot be decided, so it is an error (E3033).

Only scalars can go in such an array today. Structs, arrays of arrays, and anything that grows are not compile-time values yet.

Const generic parameters

A generic parameter can be a value rather than a type, written with const:

fn buffer_size<const N : i32>() -> i32 {
    return N;
}

fn main() -> i32 {
    return buffer_size<4>() - 4;
}

The value is fixed when the function is instantiated, so it can be used where a compile-time constant is required — most usefully in a tensor’s shape.

Where this is used

Compile-time evaluation is what lets a shape be part of a type. Tensor<f32, [4, 4]> needs 4 to be known while type checking, not while running, and const generic parameters are how a function can be generic over a shape without giving up that knowledge:

fn main() -> i32 {
    let t : Tensor<f32, [2, 2]> = Tensor<f32, [2, 2]>::new();
    return 0;
}

Because the shape is in the type, a matrix multiply whose dimensions do not line up is a compile error rather than a run-time crash — the same argument as contracts, applied to dimensions.

A comptime block must run

A comptime block is not a hint. It runs while compiling and leaves nothing behind, and the compiler holds you to both halves:

  • If the evaluator cannot finish the block, that is an error, not a quiet fall back to running the code at run time.
  • A block may not write to anything declared outside it. The block disappears, so the write would have to disappear with it.
  • A block may not sit inside another one. The outer block already runs while compiling.
  • The loops in a block, and in the functions it calls, may run 1,000,000 times in all. More is an error (E8005).

The program makes some checks while it runs. An assert stops it when its condition is false. An integer / or % stops it when it divides by zero, or when it divides the minimum value by -1. The block disappears, so a check in it would disappear too. The compiler makes each check while compiling instead.

A false assert is an error (E8002):

fn main() -> i32 {
    comptime {
        let mut v : i64 = 1i64;
        v = v + 1i64;
        assert(v == 1i64, "v is 2");   // error[E8002]: Comptime assert failed: v is 2
    }
    return 0;
}

A check that depends on a value known only when the program runs, such as argc, is an error (E3033). The compiler cannot decide it:

fn main(argc : i32) -> i32 {
    comptime {
        assert(argc > 0, "at least one argument");   // error[E3033]
    }
    return 0;
}

A check on a path that runs only for some run-time values, such as one branch of an if on argc, is an error (E3033) when it fails on that path. The path may never run, but the compiler cannot know that:

fn main(argc : i32) -> i32 {
    let x = comptime {
        if argc > 1 {
            let _q = 1i64 / 0i64;   // error[E3033]: this division stops the program
        };
        3
    };
    return x;
}

An integer overflow does not stop the program, which wraps the result. So a block whose value does not depend on an overflow still runs.

This is the same choice Zig makes with its comptime, and it is why C++ grew consteval alongside constexpr: a constexpr function only may be evaluated while compiling, and when it cannot be, it silently becomes an ordinary call. You find out by reading the disassembly. Here you find out by the compiler refusing.

Lambdas that run while compiling

A closure whose body is a comptime block is a function that runs while compiling:

fn main() -> i32 {
    let base = 10;
    let twice = || comptime {
        let k = 2;
        base * k
    };
    return twice();
}

The body is not worked out where the lambda is written — its parameters have no values yet. It is worked out when it is called, so twice() is 20, and the multiplication never reaches the generated code.

A lambda that takes its arguments and captures nothing is dropped entirely once its calls have folded, leaving a single constant. One that captures a variable, as twice captures base above, still has its call emitted today; only its body has folded. Removing that call as well is not done yet.

The consequence is worth stating plainly. A lambda like this cannot be called with a value that is only known at run time — that call is an error rather than a run-time call. If you want a closure that runs at run time, do not give it a comptime body.

Functions that run while compiling

A function whose whole body is return comptime { .. }; is the named form of such a lambda. Each call runs while compiling and is replaced by its value, and the function itself is not compiled into the program:

fn twice(x : i64) -> i64 {
    return comptime { x * 2i64 };
}

fn main() -> i32 {
    let t = twice(21i64);   // the program holds 42
    return 0;
}

As with the lambda, a call with an argument that is only known at run time is an error. A function without parameters, which names an expression, is also worked out where it is written, so a mistake in it is reported even when nothing calls it.

Inside a comptime block a string literal is a byte buffer the block can write through a pointer, and a pointer result becomes a string constant in the program. core::num uses this for decimal_c_str, which writes a number’s digits while compiling:

import core::num;

fn main() -> i32 {
    let n : i64 = -42i64;
    print!(n.decimal_c_str());   // the program holds "-42"; nothing is worked out at run time
    return 0;
}

A comptime block may put at most 1 MiB in such buffers. Compile-time integers are i64, so a u64 from 2^63 up cannot be worked out yet.

Where to next

Contracts and verification

Most languages let you write down what a function expects only in a comment. Vx lets you write it in the signature, where the compiler can act on it.

There are four pieces: requires, ensures, invariant, and assert.

requires and ensures

requires states what must be true when the function is called. ensures states what will be true when it returns.

fn halve(x : i32) -> i32
    requires x > 0
    ensures return > 0
{
    return x;
}

fn main() -> i32 {
    return halve(4) - 4;
}

Inside ensures, the word return means the value the function is about to return.

A function may carry more than one of each. They are read as a list, all of which must hold:

fn clamp_positive(x : i32, hi : i32) -> i32
    requires x > 0
    requires hi > x
    ensures return > 0
{
    return x;
}

fn main() -> i32 {
    return clamp_positive(1, 2) - 1;
}

The condition may be written bare or in parentheses — requires x > 0 and requires (x > 0) are the same.

invariant

An invariant on a loop states something that is true on every turn.

fn main() -> i32 {
    let limit : i32 = 3;
    let mut i : i32 = 0;
    loop invariant(limit > 0) {
        if i >= limit {
            break;
        }
        i = i + 1;
    }
    return 0;
}

Like requires and ensures, an invariant may be written bare or in parentheses: invariant limit > 0 and invariant(limit > 0) are equivalent. An invariant about i itself, such as i >= 0, cannot be proven yet: see “Not yet” below.

What checks these

Conditions are discharged by an SMT solver — z3 — at compile time. If the prover cannot show a condition holds, compilation fails with a diagnostic in the E8xxx range rather than the program being allowed through.

This means two things worth understanding:

  • z3 must be installed for contracts to be checked. The build instructions in Building from source cover it.
  • The prover can fail to prove something true. A condition it cannot discharge is reported, not assumed. If you hit that, the usual fix is to state an intermediate fact the prover needs, rather than to remove the contract.

What the prover knows

The prover checks each function on its own, from the facts it has at each point in the body.

Where facts come from:

  • the function’s own requires;
  • let x = e;, which gives x == e (a let mut gives nothing, since x may change);
  • assert(c, ..), which gives c after the assert;
  • the condition of an if: the then branch knows it is true, the else branch knows it is false;
  • a loop’s invariant, inside the loop, and a <= i && i < b inside for i in a..b;
  • what a called function promises in its ensures (see below).

Facts follow the paths. What a branch learns ends with the branch. When a branch ends early, by return, break, continue, panic, abort or a call to a function that never returns (-> !), the code after the if is reached only through the other branch, so it keeps what that branch knew:

fn positive(x : i32) -> i32
    ensures return > 0
{
    if x <= 0 {
        return 1;
    }
    return x;
}

fn main() -> i32 {
    return positive(3) - 3;
}

At return x, the prover knows x > 0, because the only way there is past the if.

ensures is checked at each return, from what is known there, with return standing for that value. An error (E8001) points at the return that fails.

Calls. At a call, each requires of the called function must follow from what is known there, with the call’s arguments in place of its parameters. If it does not, the call is an error (E8006). The call’s result is then known through the called function’s ensures:

fn halve(x : i32) -> i32
    requires x > 0
    ensures return > 0
{
    return x;
}

fn twice_halved(x : i32) -> i32
    requires x > 0
    ensures return > 0
{
    return halve(halve(x));
}

fn main() -> i32 {
    return twice_halved(4) - 4;
}

The inner halve(x) meets x > 0 from twice_halved’s own requires. The outer call meets it from the inner call’s ensures. A function with no ensures promises nothing, so the prover knows nothing about what it returns.

What it can reason about: whole numbers with +, - and *, comparisons, &&, || and !, fields and elements of those, and calls. A condition that uses anything else, such as division, is ignored, and the compiler warns that it was: “the prover ignores a condition here”.

Not yet:

  • A match arm does not know which pattern matched (Vx#1352).
  • After a loop, nothing is known about what happened in it: not its invariant, and not the condition that ended it. And since a let mut gives no fact, an invariant about a loop counter cannot be proven when the loop starts (Vx#1353).

assert

assert is the run-time counterpart. It takes a condition and an optional message:

fn main() -> i32 {
    let x : i32 = 1;
    assert(x == 1, "x should be one");
    return 0;
}

If the condition is false at run time, the program stops and reports the message.

Use assert for what you cannot state statically. Use requires and ensures where you can, so the error arrives at compile time instead.

Verified

Verified<T> is a type that carries the fact that a value has been checked. A plain T and a Verified<T> are different types, so a function that demands a checked value cannot be handed an unchecked one by mistake.

fn main() -> i32 {
    let x : i32 = 1;
    let v = Verified(x);
    return 0;
}

This is the same idea as requires, moved into the type: rather than every function re-stating the condition, one function establishes it and the type carries it onwards.

Diagnostics

Contract failures report under their own codes. The full list is in the diagnostic index; the E8xxx group is the prover.

Where to next

Unsafe and FFI

Vx checks a lot at compile time: ownership, borrowing, memory placement, contracts. Some things cannot be checked — a pointer that came from C, a hardware register, a cast the compiler has no way to justify. unsafe is where you take responsibility for those.

unsafe does not switch the checks off. It permits a small, specific set of operations that are otherwise refused.

unsafe blocks

An unsafe block is an expression. It can produce a value.

fn main() -> i32 {
    let x : i32 = 7;
    let p : *const i32 = &x;
    let v = unsafe { *p };
    return v - 7;
}

Reading through a raw pointer — *p — is the operation that needs the block. So does indexing through one, or reading a field through one.

Raw pointers

Two kinds, matching the two kinds of reference:

TypeMeaning
*const TA raw pointer you may read through.
*mut TA raw pointer you may read and write through.

Unlike &T and &mut T, raw pointers are not tracked by the borrow checker. Nothing stops two *mut T pointing at the same value. That is exactly why dereferencing one needs unsafe.

unsafe functions

A function whose body is not the dangerous part — but whose contract is — should be marked unsafe itself. A caller then has to opt in.

unsafe fn read_at(p : *const i32) -> i32 {
    return unsafe { *p };
}

fn main() -> i32 {
    return 0;
}

Unsafe-ness is part of the function’s type. An unsafe fn cannot be passed where a safe function is expected, so the obligation cannot be lost by storing the function in a variable.

The rule for when to mark a function unsafe: if a caller can make it misbehave by passing something the compiler cannot check — a dangling pointer, a wrong length — it is unsafe. If the function checks everything itself, it is not, even if its body uses unsafe internally.

Calling C

An extern block declares functions that exist outside Vx.

extern "C" {
    safe fn abs(x : i32) -> i32;
}

fn main() -> i32 {
    return abs(0);
}

By default an extern function is unsafe to call — the compiler knows nothing about what is on the other side. safe marks one that genuinely is safe to call with any arguments its types allow, so callers do not need an unsafe block.

abs qualifies: every i32 is a valid input and it cannot misuse memory. A function taking a pointer and a length would not qualify, because passing a mismatched pair breaks it.

Use safe sparingly. It is a promise the compiler cannot verify, and it is the one place in an extern block where a mistake is silent.

Calling Vx from C

An object file exports only the functions marked pub. Every other function the program defines is hidden, so C cannot call it.

#![allow(unused)]
fn main() {
pub fn add_one(x : i32) -> i32 {
    return x + 1;
}
}
vxc add_one.vx --action emit-obj -o add_one.o
cc -shared -o libadd_one.so add_one.o

C, or Python’s ctypes, can now load libadd_one.so and call add_one. Give a pub function parameters and a result that C can pass: numbers, bool and raw pointers. Only a top-level function that is not generic can be pub for now.

What unsafe does not unlock

unsafe covers operations the type system cannot justify. It does not override placement.

A pointer into device memory still cannot be dereferenced from the host inside an unsafe block — that is not an unchecked operation, it is a wrong one, and it stays a compile error. See Topologies and memory.

Where to next

Automatic differentiation

Vx can differentiate a function you wrote, at compile time. The derivative is generated from the function’s own code: the program does not record its operations as it runs, builds no graph, and needs no separate framework.

Three forms, matching the three things people usually want.

grad

grad gives the derivative of a function with respect to its input.

fn square(x : f32) -> f32 {
    return x * x;
}

fn main() -> i32 {
    let d = grad(square, 3.0);
    return 0;
}

grad(square, 3.0) is the derivative of square evaluated at 3.0. For x * x that is 2 * x, so 6.0.

vjp — reverse mode

A vector-Jacobian product. This is what backpropagation computes, and it is the efficient choice when a function has many inputs and few outputs — the usual shape of a loss function.

fn square(x : f32) -> f32 {
    return x * x;
}

fn main() -> i32 {
    let g = vjp(square, 2.0, 1.0);
    return 0;
}

The arguments are the function, the point to evaluate at, and the seed — the vector to multiply the Jacobian by, which for a scalar loss is 1.0.

jvp — forward mode

A Jacobian-vector product. The efficient choice in the opposite case: few inputs, many outputs.

fn square(x : f32) -> f32 {
    return x * x;
}

fn main() -> i32 {
    let d = jvp(square, 2.0, 1.0);
    return 0;
}

Same arguments, but the seed is a direction in the input space, and the result is how the outputs move in that direction.

Choosing between them

You haveUse
Many inputs, one output (a loss)vjp
One input, many outputsjvp
A scalar function of a scalargrad

One call to vjp or jvp costs a small multiple of one call to the function. In general, to get the whole Jacobian — every partial derivative — of a function with n inputs and m outputs, you need one vjp for each output, or one jvp for each input. That is the whole reason both exist.

So far Vx differentiates only a function whose first parameter and result are each a single number; see Limits worth knowing.

Larger examples

These use f64, with exp, ln_1p and cos from core::num.

Through calls to other functions

import core::num;

fn log_one_plus(t : f64) -> f64 {
    return t.ln_1p();
}

// softplus(x) = ln(1 + e^x), a smooth version of max(0, x).
fn softplus(x : f64) -> f64 {
    return log_one_plus(x.exp());
}

fn sigmoid(x : f64) -> f64 {
    return 1.0 / (1.0 + (-x).exp());
}

fn main() -> i32 {
    let x : f64 = 0.5;
    print(grad(softplus, x));
    print!(" ");
    print(sigmoid(x));
    return 0;
}

The derivative of softplus is the sigmoid, and the program prints both: 0.6224593312018545 0.6224593312018546. The derivative goes through exp and then through log_one_plus by the chain rule, and neither function had to be marked. The last digit differs because the two numbers are computed in different ways.

Branches and loops

The derivative follows whichever branches and loop iterations the function takes at that point.

// The Huber loss: quadratic near zero, linear further out.
fn huber(x : f64) -> f64 {
    if x < -1.0 {
        return -x - 0.5;
    }
    if x > 1.0 {
        return x - 0.5;
    }
    return 0.5 * x * x;
}

fn main() -> i32 {
    let near : f64 = 0.25;
    let far : f64 = 3.0;
    print(grad(huber, near));
    print!(" ");
    print(grad(huber, far));
    print!(" ");
    print(grad(huber, -far));
    return 0;
}

This prints 0.25 1 -1, the slope of the piece each point falls in.

// x raised to the n, by multiplying n times.
fn power(x : f64, n : i32) -> f64 {
    let mut result : f64 = 1.0;
    for i in 0..n {
        result = result * x;
    }
    return result;
}

fn main() -> i32 {
    // The derivative of x^5 is 5 x^4, which is 80 at x = 2.
    let x : f64 = 2.0;
    print(grad(power, x, 5));
    return 0;
}

This prints 80. The count n comes after x, so it is passed through unchanged and the loop runs five times.

Partial derivatives

grad moves the first parameter and holds the others fixed. To differentiate in another parameter, write a function that takes it first.

fn f(x : f64, y : f64) -> f64 {
    return x * x * y;
}

// f with y as its first parameter, so grad moves y.
fn f_in_y(y : f64, x : f64) -> f64 {
    return f(x, y);
}

fn main() -> i32 {
    let x : f64 = 3.0;
    let y : f64 = 2.0;
    print(grad(f, x, y));
    print!(" ");
    print(grad(f_in_y, y, x));
    return 0;
}

This prints 12 9: the derivative in x is 2xy, and in y it is x * x.

Using the derivative in a loop

A derivative is an ordinary function call, so a program can call it as often as it needs. Newton’s method uses it to find where a function is zero:

import core::num;

fn gap(x : f64) -> f64 {
    return x.cos() - x;
}

fn main() -> i32 {
    // Newton's method for the x where cos x = x.
    let mut x : f64 = 1.0;
    for step in 0..6 {
        x = x - gap(x) / grad(gap, x);
    }
    print(x);
    return 0;
}

This prints 0.7390851332151607, the number whose cosine is itself.

Gradient descent uses it to fit a model. Here the model is a line through the origin, y = w x, and the loss measures how far it is from three points:

// How far the line y = w x is from three measured points, squared and added up.
fn loss(w : f64) -> f64 {
    let e1 = w * 1.0 - 2.0;
    let e2 = w * 2.0 - 4.1;
    let e3 = w * 3.0 - 5.9;
    return e1 * e1 + e2 * e2 + e3 * e3;
}

fn main() -> i32 {
    let mut w : f64 = 0.0;
    for step in 0..50 {
        w = w - 0.02 * grad(loss, w);
    }
    print(w);
    return 0;
}

This prints 1.9928571428571429, the best slope for those points (27.9 / 14).

How it works

Differentiation is done by Enzyme, which differentiates LLVM IR. Because it works on the IR rather than on source, it differentiates through the optimiser’s view of your code, including calls into other functions.

The toolchains the installer sets up include Enzyme. A compiler built from source uses the Enzyme plugin that ENZYME_LIB names; Building from source covers building one.

Limits worth knowing

Only floating-point values can be differentiated. At each grad, vjp or jvp call, the compiler checks the types of two things:

  • the result, and
  • the first parameter, which is the value the derivative is taken with respect to.

Both must be floating-point types. An i32 or a bool takes separated values: between any two of them there is no limit to take, so there is no derivative.

Error: Function 'discrete_func' cannot be differentiated because it returns the discrete type i32

The check is on types only. It does not look for corners or jumps inside the function: a function built from pieces, like huber above, is differentiated piece by piece.

Only single numbers, so far. The first parameter and the result must each be one number, such as an f64, not a tensor. For a function like that the three forms agree: grad gives the slope, and vjp and jvp give the slope times the seed.

A later parameter may be discrete, for grad and vjp. A function of an f64 that also takes an index or a loop count is an ordinary thing to differentiate, and only the first argument varies. jvp takes only a function with one parameter.

Where to next

Low-precision numbers

Machine-learning hardware stores numbers in fewer bits than f32, so that more of them fit in memory and more of them move per second. Vx has these types as part of the language. A machine file lists the ones each device supports (dtypes:), and the compiler checks a program against that list.

The types

TypeBitsValuesInfinityNaNStatus
f1616up to 65504, about 3 decimal digitsyesyesworks
bf1616the range of f32, about 2 decimal digitsyesyesworks
f8e4m38up to 448noyesdeclaration only (#1874)
f8e5m28up to 57344yesyesdeclaration only (#1874)
f4e2m140, 0.5, 1, 1.5, 2, 3, 4, 6, and their negativesnonodeclaration only (#1874)
f8e8m08powers of two, 2⁻¹²⁷ to 2¹²⁷noyesplanned (#1874), block scales only
i44-8 to 7works
u440 to 15works

In a float type’s name, e is the number of exponent bits and m the number of mantissa bits. More exponent bits give a wider range. More mantissa bits give more precision.

“Declaration only” means the type checker and the memory capacity checks accept the type, but the compiler cannot generate code for it yet. You can use it in a machine file’s dtypes: and in the type of a tensor that the capacity checks count. A program that computes with it does not compile.

Conversions

Nothing converts by itself. You write as:

#![allow(unused)]
fn main() {
let x : f32 = 2.7;
let h = x as bf16;
let back = h as f32;
}

A conversion to a narrower float rounds to the nearest value it can hold. On a tie it picks the value whose last bit is 0.

A value too large for the type gives infinity in f16 and bf16. f8e4m3 and f4e2m1 have no infinity, so a value too large for them will give the largest value they hold: 448 or 6. f8e5m2 will do the same, and give 57344. NVIDIA’s conversion instructions also work this way when saturation is on.

Integer arithmetic wraps around. In i4, 3 * 2 + 3 is 9, which does not fit, so the answer is -7. In u4, 15 + 1 is 0.

Arithmetic on fp8 and fp4

The compiler does not support this part yet (#1875, #1876).

f16, bf16, i4 and u4 compute like the other number types. An operation on two bf16 values gives a bf16.

An operation on f8e4m3, f8e5m2 or f4e2m1 values needs a result type that you write:

#![allow(unused)]
fn main() {
let c : f32 = a * b + d;     // a, b and d are f4e2m1
let e : f8e4m3 = a * b;
let f = a * b;               // error: the result type is not written
}

The compiler computes the whole expression in f32, and rounds once, to the type you wrote. This is what the hardware does. The tensor cores of an NVIDIA Blackwell GPU multiply fp4 values, add the products in f32, and convert the sum in a separate step.

This rule does not break the rule that nothing converts by itself. The result type is in the program text, so the reader sees where the precision changes.

A matmul works the same way:

#![allow(unused)]
fn main() {
let c : Tensor<f32, [64, 64]> = a @ b;      // a and b are f4e2m1 tensors
}

The result type can be f32, f16, bf16, f8e4m3, f8e5m2 or f4e2m1. The operands can be of two different narrow types, for example an f4e2m1 tensor times an f8e4m3 tensor.

Block-scaled fp4: NVFP4 and MXFP4

The compiler does not support this part yet (#1877).

An f4e2m1 value is a number from -6 to 6. To hold real weights, NVFP4 and MXFP4 put the values in blocks, and give each block a scale. A value is its f4e2m1 number times the scale of its block.

FormatValues per blockScale per blockScale per tensor
NVFP416f8e4m3one f32
MXFP432f8e8m0, a power of twonone

The scale is part of the tensor type:

#![allow(unused)]
fn main() {
let w : Tensor<f4e2m1, [4096, 4096], Scale<f8e4m3, block: 16>>;   // NVFP4
let m : Tensor<f4e2m1, [4096, 4096], Scale<f8e8m0, block: 32>>;   // MXFP4
}

quantize makes such a tensor from an f32 tensor, and chooses each block’s scale. dequantize turns it back into f32. @ applies the scales and adds the products in f32.

The memory capacity checks count the scales too. A 4096 by 4096 NVFP4 tensor is 8 MiB of values and 1 MiB of scales, so it needs 9 MiB.

Topologies and memory

This is the chapter that makes Vx different from other systems languages. Everything up to here you could have done in Rust or C++; none of it needed a new language.

The two vocabularies

Vx describes a machine with two kinds of declaration, and they answer different questions:

  • Memory — where data lives. Memory::CPU_DRAM, Memory::NPU_HBM, a scratchpad, a cache level. Each has a capacity, a bandwidth, and a scope.
  • Topology — where code runs. Topology::CPU, Topology::NPU[0], Topology::GPU. A topology is bound to the memory it can address.

A tensor’s type records which memory space it is in. A region of code records which topology it runs on. The checker’s job is to make sure those two agree everywhere.

Placement and transfer

Moving data between memory spaces is explicit:

#![allow(unused)]
fn main() {
let a = transfer(a_host, Memory::NPU_HBM);
let b = transfer(b_host, Memory::NPU_HBM);
}

transfer yields a value whose type says it lives in NPU_HBM. The original is still typed as living where it was.

The explicitness is the point, and it is required even when the hardware boundary costs nothing. On Apple’s unified memory the CPU and the GPU address the same physical DRAM, so the copy compiles away to nearly nothing — and you still write it. The reason is that data locality should be provable by reading the source, not by profiling the binary. A transfer you cannot find in the text is a transfer you cannot reason about.

Two things the compiler checks here:

  • Reachability — there must be a declared path between the two spaces. A transfer between memories with no route between them is an error, not a runtime hang.
  • Admission — the destination must have room. This is checked against the machine file, before any binary exists.

Running code somewhere else

spawn on runs a block on a named topology:

fn main() -> i32 {
    let mut host : Tensor<f32, [4, 4]> = unsafe { Tensor<f32, [4, 4]>::uninit() };
    for i in 0..4 {
        for j in 0..4 {
            host[i][j] = 1.0;
        }
    }

    let mut device = transfer(host, Memory::NPU_HBM);

    spawn on(Topology::NPU[0]) {
        for i in 0..4 {
            for j in 0..4 {
                device[i][j] += 1.0;
            }
        }
    }

    return 0;
}

Tensor<f32, [4, 4]>::uninit() takes no arguments: the shape is already part of the type. Only the dynamic form needs extents passed — Tensor<f32, [?, ?]>::uninit([rows, cols]).

The block is outlined into a kernel and handed to the dispatcher for that topology. On Apple Silicon that means CoreML and the neural engine; on an NVIDIA box it means PTX. The source does not change between the two — the machine file does.

Every value the block touches must already live in a memory the target topology can address. That is why the transfer comes first. Skip it, and the error names the value and the space it is in rather than crashing inside a vendor runtime.

The block also calls no helper function. A function declared without a topology belongs to the host, and calling it from inside a device region is a compile error:

Error[E6001]: Function 'f' requires topology 'CPU', but is called from 'ANE'

That is the address-space rule doing its job. Code meant for a device is written in the region, or in a function declared for that topology.

Not implemented yet. spawn on is a statement. The design intends it to become an expression yielding a Future, so a host thread could fan work across several accelerators and join later. There is no future type and no await in the language today.

What gets rejected

The point of putting placement in the type system is the errors you get for free.

Dereferencing a device pointer from the host. A Pinned<T, NPU_SRAM> that escapes into a host expression is a type error with a source span. This is the error that motivates the whole design: in C++ with CUDA it is a segfault, and in Python it is a silent wrong answer.

Working set overflow. A tile you place in a scratchpad that cannot hold it is rejected at compile time, with the required and available figures in the diagnostic. See machine files.

Use-after-move. Buffers are linear values. Consuming one and then reading it again is an error.

Unvisible transfers. An asynchronous transfer whose completion has not been made visible before the buffer is read is a seam violation. Turn the check on with --verify-seams; it discharges the obligation with an SMT solver, and needs z3 on your PATH.

Verified values

Verified<T> marks a value whose computation carried its proof obligations all the way through. A function returning Verified<Tensor> is asserting that the placement, capacity and visibility conditions on the path that produced it were all discharged, not merely unchecked.

#![allow(unused)]
fn main() {
fn custom_matmul(a : Pinned<Tensor<f32, [4, 4]>, Topology::NPU[0]>,
                 b : Pinned<Tensor<f32, [4, 4]>, Topology::NPU[0]>)
    -> Verified<Tensor<f32, [4, 4], Memory::NPU_HBM>> {

    let mut result = unsafe { Tensor<f32, [4, 4], Memory::NPU_HBM>::uninit() };

    spawn on(Topology::NPU[0]) {
        for i in 0..4 {
            for j in 0..4 {
                result[i][j] = 0.0;
                for k in 0..4 {
                    result[i][j] += a[i][k] * b[k][j];
                }
            }
        }
    }

    return Verified(result);
}
}

Pinned<T, Topology> says the value is resident on a particular device. Note the third type argument on Tensor — the memory space it lives in — and that Verified(...) wraps the result after the region, not inside it.

Choosing a machine at compile time

The same program compiles against different hardware by swapping the machine file:

vxc --machine fleet/h100-sxm.vx program.vx -o program
vxc --machine fleet/m4-uma.vx  program.vx -o program

You can ask what the compiler concluded, as JSON, rather than reading it out of diagnostics:

vxc --machine fleet/h100-sxm.vx program.vx --diagnostics-json out.json

That record carries every diagnostic with its structured fields — a capacity rejection includes the space, the amount required, the amount available and the margin — plus the staging routes and per-edge costs that an admitted program resolved to.

Next

Machine files covers how a real part gets described, and what the compiler derives from that description.

Machine files

Most compilers hard-code a cost model. Vx reads one.

A machine file describes the memory hierarchy and interconnect of a real part. The compiler admits or rejects your placements against it, and derives transfer costs from it, before a binary exists.

An example

#![allow(unused)]
fn main() {
Memory HBM  { capacity: 80 GiB, bandwidth: 3.35 TB/s, managed: explicit, scope: device }
Memory L2   { within: Memory::HBM, capacity: 50 MiB, bandwidth: 12 TB/s, managed: cached }
Memory SMEM { within: Memory::L2, capacity: 228 KiB, bandwidth: 128 B/cyc,
              clock: 1.98 GHz, replicas: 132, granule: 1 KiB, scope: sm }

Topology Device {
    arch: nvptx64,
    memory: Memory::HBM,
    transfer Memory::CPU_DRAM -> Memory::HBM : 63 GB/s,
}
}

That is an H100, in eleven lines.

The fields

capacity — how much the space holds. Checked against the working set of anything you place there.

bandwidth — either a rate (3.35 TB/s) or a per-cycle figure with a clock (128 B/cyc at 1.98 GHz). The second form is how scratchpads are usually specified in vendor documentation.

within — containment. L2 sits inside HBM. The containment relation must be acyclic, a child may not exceed its parent’s capacity, and scope narrows as you descend.

managed — explicit if software moves the data, cached if hardware does.

scope — who can see it. device is visible to the whole accelerator; sm is private to one streaming multiprocessor.

replicas — how many copies of the space exist. 132 SMs means 132 scratchpads, and a placement is admitted against one of them, not their sum.

granule — the allocation quantum. A 1 KiB granule means a 100-byte tensor occupies 1 KiB, and admission rounds accordingly.

node — which NUMA domain of the host this space’s memory physically is. The only field here a backend acts on rather than checks against: a placement into a space that declares one is bound to that node, and a dispatch reading it pins itself there. Absent on every accelerator in fleet/, because a GPU has one memory and nothing to choose between. Zero is a real node rather than an absence. See NUMA and the host’s memory domains.

Units are exact

SI prefixes are decimal and IEC prefixes are binary:

GB10⁹ bytes
GiB2³⁰ bytes
TB/s10¹² bytes per second

Conversions are exact integer arithmetic, never floating point. A figure copied off a vendor datasheet means precisely what the datasheet meant, and does not drift by a fraction of a percent on the way through the compiler.

What the compiler derives

From that declaration alone, before any code is generated:

Admission — whether a tensor’s working set fits the space it is placed in, with granule rounding applied. A rejection carries the space, the amount required, the amount available and the margin.

Routing — the cheapest legal path between two spaces, over the declared transfer graph. If you transfer from a space that has no declared route to the destination, that is an error rather than a silently inserted staging copy.

Transfer cost — a roofline over the containment tree. A containment hop is charged at both endpoints, because data has to leave the parent as well as enter the child.

Coherence of the model itself — within is acyclic, no child exceeds its parent, scope narrows downward, and every edge has exactly one cost source. A machine file that contradicts itself is rejected as a machine file, before your program is even considered.

The bundled fleet

The repository ships machine files for real parts under fleet/: H100, H200, B200, A100, MI300X, Apple M4 unified memory, multi-GPU nodes and hosts. Each one cites its sources, and marks figures that have not been validated against hardware as unverified.

That last part matters. A declared bandwidth is often the vendor’s hardware peak rather than an achievable rate — Apple’s 120 GB/s for the base M4 is the memory system’s ceiling, not what a copy loop will reach. The fleet files state the vendor’s claim rather than a number already fitted to a measurement, so the gap between prediction and reality stays visible instead of being quietly tuned away.

Using one

vxc --machine fleet/h100-sxm.vx program.vx -o program

--machine prepends the declarations to your compilation unit. They are visible to capacity and placement checks exactly as if you had written them inline. A name declared by both the machine file and the program is an error, never a silent shadow.

--machine describes an accelerator and says nothing about the host it hangs off. If your program stages through host memory, declare the host too:

vxc --machine fleet/h100-sxm.vx --host default program.vx -o program

--host default means “the machine doing the compiling”. A host file declares no capacity, on purpose: host memory is virtual, and a tensor larger than physical RAM pages rather than failing. A hard limit there would reject programs that actually run.

Writing your own

Start from the closest file in fleet/ and change the numbers. The compiler will tell you if the result is incoherent, which makes the edit-check loop fast. Two habits worth keeping:

  1. Cite every figure. The fleet files carry the source of each number in a comment. When a prediction is wrong, the first question is always whether the model or the measurement is at fault, and a citation answers it in seconds.
  2. Mark what you have not verified. A number from a datasheet and a number from a benchmark are different kinds of thing, and the difference should survive in the file.

NUMA and the host’s memory domains

Every other memory space in this book belongs to an accelerator you have to attach. A two-socket server has two of them already, and they are the hardest case for the model to describe well — because unlike a GPU’s HBM against a host’s DRAM, the two memories here are physically identical.

Node 0’s memory and node 1’s memory are the same DDR4, at the same speed, from the same order. Nothing about the bytes differs. The only thing separating them is which core is asking.

That makes NUMA a good test of whether placement in the type system is describing locality or merely labelling hardware. It also makes it the one heterogeneous memory you can experiment with on a machine you may already own.

It is the same relation as two GPUs

A NUMA domain is a memory that some execution units reach cheaply and others reach across a link. fleet/node-8gpu.vx already says that about two GPUs, as HBM and PEER_HBM with a priced edge between them. A two-socket host is the same shape:

Memory HBM {
  capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 0
}

Memory PEER_HBM {
  capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 1
}

Topology Device {
  arch: x86_64,
  memory: Memory::HBM,
  visible: [Memory::HBM, Memory::PEER_HBM, Memory::L2],
  transfer Memory::CPU_DRAM -> Memory::HBM : 140 GB/s,
  transfer Memory::HBM -> Memory::L2,
  transfer Memory::HBM -> Memory::PEER_HBM : 62 GB/s,
  transfer Memory::PEER_HBM -> Memory::HBM : 62 GB/s
}

The names are roles rather than materials, as everywhere else in fleet/ — there is no HBM within a mile of this part, and HBM means “this SKU’s device memory”. What the pair says is that a tile lives in one domain and reaching it from the other crosses a link with a price.

fleet/xeon-8275cl.vx is this file in full, for the Xeon Platinum 8275CL that AWS sells as c5.metal.

node: is the field that is obeyed

Every other field on a Memory describes the space so the compiler can admit or refuse a placement. node: is different: a backend has to act on it.

Memory PEER_HBM {
  capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 1
}

The space’s name cannot carry this. A declared name reaches the runtime as an FNV hash, by design, and a hash cannot be turned back into a node number — while mbind needs exactly that. So a space declaring a node is given a banded dispatch id instead (arch::NUMA_DISPATCH_BASE + N, in the 1000..1999 range), and the backend decodes it.

Two consequences follow, and both are visible:

  • transfer(t, Memory::PEER_HBM) binds the allocation to node 1, through the mbind syscall. No libnuma dependency and no link flag; it compiles out entirely off Linux.
  • A dispatch whose arguments are placed pins itself to that node before running the kernel.

Zero is a valid node and means the first one, so node: 0 is a declaration rather than an absence. The upper bound is the dispatch band’s rather than the hardware’s; a node number past it is a parse error rather than an id that would collide with something else.

What it buys at compile time

The compile-time half needs no hardware at all. Declaring the domains separately means a tile too large for one of them is refused, where a model that flattened the box into a single space would admit it:

Error[E6009]: transferred tensor needs 42949672960 bytes
              but memory space 'HBM' has capacity 32212254720 bytes

That program was being told something false before. The box really does have 60 GiB; no node has 40 GiB, so the tile could not have been local however it was allocated.

The hop between domains is priced like any other edge, and --diagnostics-json will hand you the per-route figures:

CPU_DRAM -> HBM      | 8 GiB | 61.4 ms  | link_rate
HBM -> PEER_HBM      | 8 GiB | 138.5 ms | link_rate

Checking the model against the machine

utils/campaign/run_numa_probe.sh measures all four (cpu node, memory node) pairs and compares the measured remote/local ratio against the one the compiler derives, reading the prediction out of --diagnostics-json so it cannot drift from the machine file.

sudo apt install numactl
./utils/campaign/run_numa_probe.sh                          # defaults to fleet/xeon-8275cl.vx
MACHINE=fleet/xeon-e5-2666v3.vx ./utils/campaign/run_numa_probe.sh

It compares ratios rather than absolute times, deliberately. Declared bandwidths are memory controller peaks, and a copy moves two bytes of traffic per byte copied, so the model over-predicts any single edge by at least a factor of two. Both sides of a ratio carry that error and it cancels.

On a c5.metal the model predicts 2.258 and the hardware gives 2.348, a 4% error.

The probe refuses some machines, and that is the point

A virtualized instance can report two NUMA nodes, honour --membind, and still spread the pages across both sockets underneath. Neither /proc/PID/numa_maps nor move_pages() will tell you. Both answer truthfully about guest nodes, and it is the guest nodes that are not backed by locality — move_pages() reports 64 of 64 probed pages on the requested node on a c5.metal where a remote read really does cost 2.35×, and the identical 64 of 64 on a c4.8xlarge where it costs nothing. The kernel’s own placement query cannot separate the two.

An AWS c4.8xlarge does exactly this, on two separately provisioned instances. CPU lists, node sizes and ACPI distance table all look right, and then all four pairs measure identically — at a bandwidth above what one socket can deliver, which is the only thing in the whole picture that cannot be explained away.

So the probe tests the machine before it reports anything: bind to one node, interleave across both, and if the two agree within 10% then the bind confined nothing and every number above it is measuring one undivided pool. It says so and exits.

If you want NUMA on EC2, use a bare-metal instance. There is nothing between the guest and the sockets there.

What placement is worth, honestly

Measured on c5.metal, 8 GiB, 96 threads:

PlacementBandwidthvs interleaved
First-touch (naive default)96.8 GB/s0.54×
numactl --interleave=all180.5 GB/s1.00×
Placed per node242.1 GB/s1.34×

1.34× is the figure, not 2.50×. Interleaving costs nothing, needs no source change, and is what a competent operator already does on a two-socket box. Comparing against naive first-touch would be comparing against the worst case.

That 1.34× also needs the threads placed as well as the pages. Vx’s host backend currently runs an outlined kernel on one thread — vx_host_call_kernel is a single ffi_call — so what it delivers today is one thread’s local-versus-remote, about 1.5× on memory-bound work. A threaded host backend is what would close the gap, and is separate work.

utils/campaign/placement_bench.c is the benchmark those numbers come from.

Seeing it happen

VX_DISPATCH_VERBOSE=1 vxc prog.vx --host default --machine fleet/xeon-8275cl.vx --run
[Vx x86] staged 268435456 bytes on NUMA node 0
[Vx x86] handed 268435456 bytes to NUMA node 1
[Vx x86] pinned to NUMA node 1 for this dispatch

VX_NUMA_NO_AFFINITY=1 turns the pinning off, for a process that manages its own affinity — and for telling the two states apart with one binary when measuring.

A binding that cannot be honoured — an offline node, a kernel without NUMA support, a machine that is not Linux — warns once and leaves the memory unplaced. The program is correct in every one of those cases and only slower, so it is not an error; but a run expected to be placed should not quietly read as one that was.

Where to next

Vx in one page

A short reference for someone who already programs: the shape of a program, the types, the statements, and three complete programs with what they print. A tour of Vx explains each part at length, and Vx for Rust programmers lists what to change when writing Vx with Rust habits.

A program

  • One file is one module. import core::option; brings in everything that module declares.
  • fn main() -> i32 is where the program starts. Its result is the exit code.
  • Comments are // to the end of the line, and /// for a doc comment.
  • Statements end in ;. Blocks are { .. }, and indentation means nothing.

Declarations

  • const N : i64 = 4; is a value known while compiling. The type is required.
  • fn add(a : i64, b : i64) -> i64 { .. }. Parameter and return types are required, and a function with no result returns void.
  • struct Point { x : i64, y : i64 }, built with Point { x : 1, y : 2 }.
  • enum Shape { Square(i64), Rect(i64, i64) }, built with Shape::Square(3).
  • trait Describe { fn size(self : &Self) -> i64; }. A trait method can have a default body.
  • impl Point { .. } and impl Describe for Shape { .. }. Each method writes its receiver’s type: self : &Self, self : &mut Self or self : Self. Inside an impl, Self is the impl’s type.
  • Generics: struct Pair<T>, fn largest<T : Describe>(a : &T) -> i64, impl<T> Pair<T>. A generic type is always written with its arguments: Pair<i64> { .. }, Vec<i64>::new(), Option<i64>::Some(3).
  • Const generics: fn dot<const N : i32>(a : Tensor<f32, [N]>) -> f32.
  • macro_rules name { ($x : expr) => { .. }; }, called as name!(..).
  • extern "C" { fn abs(x : i32) -> i32; } declares a C function.

Types

  • Integers i4 i8 i16 i32 i64, u4 u8 u16 u32 u64; floats f16 bf16 f32 f64; bool. The fp8 and fp4 types are in Low-precision numbers. Nothing converts by itself: write x as i64. An integer variable with no written type is i32.
  • Tuples (i64, f32), read as t.0 or taken apart with let (a, b) = t;.
  • References &T and &mut T, checked by the borrow checker; raw pointers *const T and *mut T, used inside unsafe.
  • Tensor<f32, [2, 3]>: the shape is part of the type. ? is a size known only at run time, Tensor<f32, [?, ?]>, read with t.extent(0). A third argument names the memory it lives in: Tensor<f32, [4], Memory::NPU_HBM>. Arrays are tensors: let a : Tensor<f32, [3]> = [1.0, 2.0, 3.0];.
  • Option<T> and Result<T, E> (import core::option;, import core::result;), Vec<T> (import std::vec;), Box<T> (import std::box;).
  • A function that never returns has the type -> !, like panic("..").

Statements and expressions

  • let x = 1; and let mut y : i64 = 2;. Variables are read-only unless mut.
  • if c { .. } else { .. }, which is also an expression: if c { 1 } else { 2 }.
  • for i in 0..n { .. } (n not included), while c { .. }, loop { .. }, with break and continue.
  • match v { Shape::Square(s) => { .. }, _ => { .. } }. Each arm’s body is a block, and the last arm must cover every value that is left.
  • return x;, or a final expression with no ;.
  • Arithmetic + - * / %, comparisons == != < <= > >=, && || ! on bool, and +=, -=, *= and the like.
  • Closures: | x : i64 | x * 2. Parameter types are required.
  • unsafe { .. } allows raw pointer use, calls to unsafe fn, and uninitialized tensors: unsafe { Tensor<f32, [4]>::uninit() }.

Printing and stopping

  • print(x) prints one value. print!("text") prints text. println!("{} and {}", a, b) puts each value in place of a {} and ends the line.
  • eprint! and eprintln! do the same on standard error.
  • panic("message") stops the program with a message on standard error, and abort() without one.

Memory and devices

Code runs on a topology, and data lives in a memory. Moving data is written out, and a device block can only use data in memory that device can reach:

fn main() -> i32 {
    let host : Tensor<f32, [4]> = Tensor<f32, [4]>::new();
    let mut device = transfer(host, Memory::NPU_HBM);
    spawn on(Topology::NPU[0]) {
        for i in 0..4 {
            device[i] += 1.0;
        }
    }
    return 0;
}

Topologies and memory explains the checks, and Machine files how a machine is described.

The standard library

  • core::iter: range(a, b) and the iterator methods map, filter, zip, enumerate, chain, take, skip, rev, fold, sum, count, min, max, any, all, find, nth, collect and more.
  • core::option, core::result, core::cmp, core::clone, core::convert, core::panic.
  • std::vec (Vec<T>: push, pop, get, set, len, iter), std::box, std::io, std::fs, std::tensor.

Three programs

Declarations, traits, enums and Option:

import core::option;

const SIDES : i64 = 4;

struct Pair<T> {
  first : T,
  second : T,
}

enum Shape {
  Square(i64),
  Rect(i64, i64),
}

trait Describe {
  fn size(self : &Self) -> i64;

  // A default method: an impl that does not write it gets this one.
  fn doubled(self : &Self) -> i64 {
    return self.size() * 2;
  }
}

impl Describe for Shape {
  fn size(self : &Shape) -> i64 {
    match *self {
      Shape::Square(s) => {
        return s * s;
      },
      Shape::Rect(w, h) => {
        return w * h;
      }
    }
  }
}

impl<T> Pair<T> {
  fn swap(self : Pair<T>) -> Pair<T> {
    return Pair<T> {
      first : self.second, second : self.first
    };
  }
}

fn largest<T : Describe>(a : &T, b : &T) -> i64 {
  let x = a.size();
  let y = b.size();
  return if x > y {
    x
  } else {
    y
  };
}

fn divmod(a : i64, b : i64) -> (i64, i64) {
  return (a / b, a % b);
}

fn first_even(a : i64, b : i64) -> Option<i64> {
  if a % 2 == 0 {
    return Option<i64>::Some(a);
  }
  if b % 2 == 0 {
    return Option<i64>::Some(b);
  }
  return Option<i64>::None();
}

fn main() -> i32 {
  let sq : Shape = Shape::Square(3);
  let r : Shape = Shape::Rect(2, SIDES);
  println!("{} {} {}", sq.size(), r.doubled(), largest(&sq, &r));

  let p = Pair<i64> {
    first : 1, second : 2
  }.swap();
  let (q, rem) = divmod(17, 5);
  println!("{} {} {} {}", p.first, p.second, q, rem);

  match first_even(3, 8) {
    Option<i64>::Some(v) => {
      println!("even {}", v);
    },
    Option<i64>::None() => {
      println!("none");
    }
  }
  return 0;
}

It prints:

9 16 9
2 1 3 2
even 8

Tensors with a fixed shape and with a shape known only at run time:

// A shape known while compiling is checked while compiling.
fn dot<const N : i32>(a : Tensor<f32, [N]>, b : Tensor<f32, [N]>) -> f32 {
  let mut acc = 0.0;
  for i in 0..N {
    acc += a[i] * b[i];
  }
  return acc;
}

// `?` is a size known only when the program runs.
fn matmul(a : &Tensor<f32, [?, ?]>, b : &Tensor<f32, [?, ?]>) -> Tensor<f32, [?, ?]> {
  let rows = a.extent(0);
  let cols = b.extent(1);
  let mut c : Tensor<f32, [?, ?]> = unsafe {
    Tensor<f32, [?, ?]>::uninit([rows, cols])
  };
  for i in 0..rows {
    for j in 0..cols {
      c[i][j] = 0.0;
      for k in 0..a.extent(1) {
        c[i][j] += a[i][k] * b[k][j];
      }
    }
  }
  return c;
}

fn main() -> i32 {
  let a : Tensor<f32, [3]> = [1.0, 2.0, 3.0];
  let b : Tensor<f32, [3]> = [4.0, 5.0, 6.0];
  println!("dot {}", dot(a, b));

  let m_flat : Tensor<f32, [4]> = [1.0, 2.0, 3.0, 4.0];
  let id_flat : Tensor<f32, [4]> = [1.0, 0.0, 0.0, 1.0];
  let m : Tensor<f32, [2, 2]> = m_flat.reshape([2, 2]);
  let id : Tensor<f32, [2, 2]> = id_flat.reshape([2, 2]);
  let p = matmul(&m, &id);
  println!("{} {} {} {}", p[0][0], p[0][1], p[1][0], p[1][1]);

  let z : Tensor<i32, [2, 3]> = Tensor<i32, [2, 3]>::new();
  let flat : Tensor<i32, [6]> = z.reshape([6]);
  println!("zeros {}", flat[5]);
  return 0;
}

It prints:

dot 32
1 2 3 4
zeros 0

Vec, iterators and Result:

import core::iter;
import core::option;
import core::result;
import std::vec;

fn parse_digit(c : i64) -> Result<i64, i64> {
  if c >= 48 && c <= 57 {
    return Result<i64, i64>::Ok(c - 48);
  }
  return Result<i64, i64>::Err(c);
}

fn main() -> i32 {
  let mut v = Vec<i32>::new();
  for i in 1..6 {
    v.push(i * i);
  }
  let mut total = 0;
  for x in v.iter() {
    total += *x;
  }
  println!("len {} total {}", v.len(), total);

  let odd_squares = range(1i64, 10i64).filter(| x : &i64 | *x % 2 == 1).map(| x : i64 | x * x).sum();
  println!("odd squares {}", odd_squares);

  match parse_digit(55) {
    Result<i64, i64>::Ok(d) => {
      println!("digit {}", d);
    },
    Result<i64, i64>::Err(c) => {
      println!("not a digit: {}", c);
    }
  }
  match v.pop() {
    Option<i64>::Some(last) => {
      println!("popped {}", last);
    },
    Option<i64>::None() => {
      println!("empty");
    }
  }
  return 0;
}

It prints:

len 5 total 55
odd squares 165
digit 7
popped 25

Standard library reference

Every public type and function in the shipped library modules, taken from their signatures, with the documentation each one carries in the source.

Import a module with its path, then use the names it declares:

import std::vec;

fn main() -> i32 {
    let mut v = Vec<i32>::new();
    v.push(10);
    v.push(32);
    return v.get(0) + v.get(1);
}

The toolchain also ships a graph library outside std, imported as graph::traversal and friends.

This page is generated from stdlib/core/*.vx and stdlib/std/*.vx by scripts/tools/gen_stdlib_reference.py. Signatures are exactly what the source declares, and the prose under each is its /// comment. An item with no description has none in the source.

Contents

core::ascii

u8 methods

  • fn is_ascii(self : u8) -> bool
    Is this byte in the ASCII range, 0 to 127?
  • fn is_ascii_uppercase(self : u8) -> bool
    Is this A to Z (65 to 90)?
  • fn is_ascii_lowercase(self : u8) -> bool
    Is this a to z (97 to 122)?
  • fn is_ascii_alphabetic(self : u8) -> bool
    Is this a letter, of either case?
  • fn is_ascii_digit(self : u8) -> bool
    Is this 0 to 9 (48 to 57)?
  • fn is_ascii_alphanumeric(self : u8) -> bool
    Is this a letter or a digit?
  • fn is_ascii_hexdigit(self : u8) -> bool
    Is this a digit, or A to F (65 to 70), or a to f (97 to 102)?
  • fn is_ascii_punctuation(self : u8) -> bool
    Is this one of the 32 punctuation marks: ! to /, : to @, [ to the backtick, or { to ~?
  • fn is_ascii_graphic(self : u8) -> bool
    Is this a visible character, ! (33) to ~ (126)? The space is not one.
  • fn is_ascii_whitespace(self : u8) -> bool
    Is this a space (32), tab (9), line feed (10), form feed (12) or carriage return (13)? The vertical tab (11) is not.
  • fn is_ascii_control(self : u8) -> bool
    Is this a control character, 0 to 31 or delete (127)?
  • fn to_ascii_uppercase(self : u8) -> u8
    The uppercase letter for a lowercase one; any other byte unchanged.
  • fn to_ascii_lowercase(self : u8) -> u8
    The lowercase letter for an uppercase one; any other byte unchanged.
  • fn eq_ignore_ascii_case(self : u8, other : &u8) -> bool
    Are the two bytes the same once letters are put in one case?
  • fn make_ascii_uppercase(self : &mut u8) -> void
    Turns this byte into its uppercase letter, if it is a lowercase one.
  • fn make_ascii_lowercase(self : &mut u8) -> void
    Turns this byte into its lowercase letter, if it is an uppercase one.

core::cell

Types

  • struct SharedCell<T>
    A T that a shared reference can write through.

SharedCell<T> methods

  • fn new(v : T) -> SharedCell<T>
    A new cell holding v.
  • fn into_inner(self : SharedCell<T>) -> T
    The value, out of the cell.
  • fn get(self : &SharedCell<T>) -> *mut T
    A pointer to the value. A write through it needs unsafe.
  • fn get_mut(self : &mut SharedCell<T>) -> &mut T
    The value, through a &mut, which rules out any other access at the same time.

core::clone

Clone, an explicit duplicate of a value.

Types

  • trait Clone

trait Clone methods

  • fn clone(self : &Self) -> Self
    A duplicate of this value, made explicitly. The only required method. A type that can copy itself by a plain read still needs one, because a body written over T : Clone has to be able to call it.
  • fn clone_from(self : &mut Self, source : &Self) -> void
    Replace this value with a duplicate of source. A default written over clone. Override it for a type that can overwrite itself more cheaply than it can build a fresh copy; nothing in core needs to.

Clone for Option<T> methods

  • fn clone(self : &Option<T>) -> Option<T>
    The option with its value cloned, if it has one.

Clone for Result<T, E> methods

  • fn clone(self : &Result<T, E>) -> Result<T, E>
    The result with whichever side it holds cloned.

Clone for T methods, stamped for 13 instantiations

  • fn clone(self : &T) -> T
    A read, since a value of this width is copied by reading it.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64, bool, Ordering

core::cmp

Ordering and equality: PartialEq, Ord, PartialOrd and Ordering.

Types

  • enum Ordering
  • trait PartialEq
  • trait Eq
    Equality that is reflexive: every value equals itself. A marker, since Vx has no supertraits yet to tie it to PartialEq. The floats do not implement it, because a NaN is not equal to itself.
  • trait Ord
  • trait PartialOrd
    A comparison that may answer with nothing, which is what a float needs: NaN is neither less than, equal to, nor greater than anything, itself included.

Ordering methods

  • fn is_lt(self : Ordering) -> bool
    Is this Less?
  • fn is_gt(self : Ordering) -> bool
    Is this Greater?
  • fn is_eq(self : Ordering) -> bool
    Is this Equal?
  • fn is_ne(self : Ordering) -> bool
    Is this anything but Equal?
  • fn is_le(self : Ordering) -> bool
    Is this Less or Equal?
  • fn is_ge(self : Ordering) -> bool
    Is this Greater or Equal?
  • fn reverse(self : Ordering) -> Ordering
    Less becomes Greater and back; Equal stays.
  • fn then_with(self : Ordering, f : Closure0<Ordering>) -> Ordering
    then, with the second comparison left uncomputed unless it is needed.
  • fn then(self : Ordering, other : Ordering) -> Ordering
    This one unless it is Equal, in which case the other. Chains comparisons: order by the first field, and on a tie by the second.

trait PartialEq methods

  • fn eq(self : &Self, other : &Self) -> bool
    Are the two values equal? The only required method of this trait. “Partial” means equality need not be reflexive: a NaN is not equal to itself, and f32 implements this and not Ord for that reason.
  • fn ne(self : &Self, other : &Self) -> bool
    Are the two values different? The negation of eq, and not spelled !=, which answers false for a NaN on both sides (Vx#716).

trait Ord methods

  • fn cmp(self : &Self, other : &Self) -> Ordering
    Where this value sits relative to the other: Less, Equal or Greater. The only required method. Every other method of this trait is a default written over it, so a type joins the ordering by writing this one and nothing else. The order must be total, which is why the floats do not implement this trait: a NaN compares to nothing, and PartialOrd is where they answer.
  • fn lt(self : &Self, other : &Self) -> bool
    Is this value less than the other?
  • fn le(self : &Self, other : &Self) -> bool
    Is this value less than or equal to the other?
  • fn gt(self : &Self, other : &Self) -> bool
    Is this value greater than the other?
  • fn ge(self : &Self, other : &Self) -> bool
    Is this value greater than or equal to the other?
  • fn max(self : Self, other : Self) -> Self
    The greater of the two, taking both by value and handing one back: other when they compare equal. A method rather than a free function, because the compiler reads the bare names max and min as the tensor reductions (Vx#223).
  • fn min(self : Self, other : Self) -> Self
    The lesser of the two: self when they compare equal.
  • fn clamp(self : Self, lo : Self, hi : Self) -> Self
    This value brought inside the range, so lo below it and hi above it.

    Panics

    When lo is greater than hi, which asks for a range no value can be in.

trait PartialOrd methods

  • fn partial_cmp(self : &Self, other : &Self) -> Option<Ordering>
    Where this value sits relative to the other, or nothing when they do not compare. Nothing is what a float answers against a NaN: it is neither less than, equal to, nor greater than anything, itself included. Over a total order this always answers Some, and the integer widths implement it that way so a body written over PartialOrd works for every number.

Functions

  • fn partial_lt<T : PartialOrd>(a : &T, b : &T) -> bool
    a < b for a PartialOrd type: false when the two do not compare.
  • fn partial_le<T : PartialOrd>(a : &T, b : &T) -> bool
    a <= b for a PartialOrd type: false when the two do not compare.
  • fn partial_gt<T : PartialOrd>(a : &T, b : &T) -> bool
    a > b for a PartialOrd type: false when the two do not compare.
  • fn partial_ge<T : PartialOrd>(a : &T, b : &T) -> bool
    a >= b for a PartialOrd type: false when the two do not compare.
  • fn max_by<T>(a : T, b : T, f : Closure2<T, T, Ordering>) -> T
    The greater of two values by f, answering b when they compare equal. A free function because it takes the comparison rather than reading it off the type. The plain max and min are Ord methods here, since those two names are the compiler’s tensor reductions.
  • fn min_by<T>(a : T, b : T, f : Closure2<T, T, Ordering>) -> T
    The lesser of the two by f, answering a when they compare equal.
  • fn max_by_key<T, K : Ord>(a : T, b : T, f : Closure1<T, K>) -> T
    The greater of two values by the key f gives each, answering b on equal keys.
  • fn min_by_key<T, K : Ord>(a : T, b : T, f : Closure1<T, K>) -> T
    The lesser of two values by the key f gives each, answering a on equal keys.

PartialEq for T methods, stamped for 10 instantiations

  • fn eq(self : &T, other : &T) -> bool
    Equality at this width.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, bool

Ord for T methods, stamped for 10 instantiations

  • fn cmp(self : &T, other : &T) -> Ordering
    The three-way comparison at this width.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, bool

PartialOrd for T methods, stamped for 10 instantiations

  • fn partial_cmp(self : &T, other : &T) -> Option<Ordering>
    Always an answer, since this type is totally ordered.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, bool

PartialEq for T methods, stamped for 2 instantiations

  • fn eq(self : &T, other : &T) -> bool
    Equality at this width. A NaN is equal to nothing, itself included.

T = f32, f64

PartialOrd for T methods, stamped for 2 instantiations

  • fn partial_cmp(self : &T, other : &T) -> Option<Ordering>
    The comparison, or nothing when either side is a NaN.

T = f32, f64

core::convert

From, the conversions that cannot fail and lose nothing.

Types

  • trait From<T>
  • enum Infallible
    An enum with no variants, so no value of it can be built. It is the error type of a conversion that cannot fail.

trait From<T> methods

  • fn from(v : T) -> Self
    This type built from a T, losing nothing. A static method, called through the target: i32::from(x). Only implemented where every value of T fits, so there is no failure to report; the narrowing direction is TryFrom’s, which Vx does not have yet.

From<T> for Option<T> methods

  • fn from(v : T) -> Option<T>
    The value wrapped in Some.

Functions

  • fn identity<T>(x : T) -> T
    Returns its argument. Useful where a function is wanted and nothing should happen.

From<T> for U methods, stamped for 55 instantiations

  • fn from(v : T) -> U
    The value widened, which cannot lose anything at these two widths.

(T, U) = i8 → i8, i16 → i16, i32 → i32, i64 → i64, u8 → u8, u16 → u16, u32 → u32, u64 → u64, usize → usize, f32 → f32, f64 → f64, bool → bool, i8 → i16, i8 → i32, i8 → i64, i16 → i32, i16 → i64, i32 → i64, u8 → u16, u8 → u32, u8 → u64, u16 → u32, u16 → u64, u32 → u64, u8 → i16, u8 → i32, u8 → i64, u16 → i32, u16 → i64, u32 → i64, u8 → usize, u16 → usize, u32 → usize, usize → u64, usize → i64, i8 → f32, u8 → f32, i16 → f32, u16 → f32, i8 → f64, u8 → f64, i16 → f64, u16 → f64, i32 → f64, u32 → f64, f32 → f64, bool → i8, bool → i16, bool → i32, bool → i64, bool → u8, bool → u16, bool → u32, bool → u64, bool → usize

core::default

Default, the value a type starts from.

Types

  • trait Default

; the language does not derive it. fn default() -> Self; } /// Zero, spelled at the width by the return type. } impl<T> Default for Option<T> methods

  • fn default() -> Self
    The value this type starts from: zero for a number, false, None. A static method, so it is called through the type – i64::default(), not x.default(). A struct gets one by writing the impl; the language does not derive it.
  • fn default() -> Option<T>
    None, whatever T is: an option’s default is the absent one.

Default for T methods, stamped for 12 instantiations

  • fn default() -> T
    Zero, spelled at this width.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64, bool

core::fmt

Writing integers as text, with the width, fill and flags of a Rust format spec.

Types

  • enum Error
    The writer refused a byte, for example because it is full. It carries no detail, as in Rust.
  • enum Alignment
    Where the value goes when the width leaves room to spare.
  • trait Write
    A byte sink. Only write_u8 is required; the rest are written in terms of it.
  • struct FormatSpec
    The options of a Rust format spec {:[fill]align][+][#][0][width]} that apply to an integer. Build one with FormatSpec::new() and the setters below.
  • struct ByteBuf
    A writer over caller-owned memory: ptr holds cap bytes. The bytes written so far are always followed by a NUL, so as_c_str can be printed; that leaves room for cap - 1. A byte that does not fit is refused with Error, and the bytes before it stay.

trait Write methods

  • fn write_u8(self : &mut Self, b : u8) -> Result<i32, Error>
    Write one byte.
  • fn write_c_str(self : &mut Self, s : *const i8) -> Result<i32, Error>
    Write the bytes of a string literal, without its closing NUL. Any other pointer must also end in a NUL; a trait method cannot be marked unsafe yet to say so.

FormatSpec methods

  • fn new() -> FormatSpec
    No width, a space for fill, no alignment and every flag off: Rust’s {}.
  • fn width(self : FormatSpec, n : i64) -> FormatSpec
    Rust’s {:8}: pad to at least n bytes.
  • fn fill(self : FormatSpec, b : u8) -> FormatSpec
    The * of Rust’s {:*>8}: pad with the byte b instead of a space.
  • fn align(self : FormatSpec, a : Alignment) -> FormatSpec
    Rust’s <, > and ^.
  • fn sign_plus(self : FormatSpec) -> FormatSpec
    Rust’s +.
  • fn alternate(self : FormatSpec) -> FormatSpec
    Rust’s #.
  • fn zero_pad(self : FormatSpec) -> FormatSpec
    Rust’s 0.
  • fn upper(self : FormatSpec) -> FormatSpec
    Rust’s X in place of x.

Functions

  • fn write_repeated<W : Write>(w : &mut W, b : u8, n : i64) -> Result<i32, Error>
    Write b to w n times.
  • fn digit_count(x : u64, radix : u64) -> i64
    How many digits x has in base radix.
  • fn write_digits<W : Write>(w : &mut W, x : u64, radix : u64, upper : bool) -> Result<i32, Error>
    Write the digits of x in base radix, most significant first. p starts at the largest power of radix that is at most x, found by multiplying up while the next power still fits under x, so it never overflows.
  • fn write_sign_and_prefix<W : Write>(w : &mut W, spec : &FormatSpec, negative : bool, radix : u64) -> Result<i32, Error>
    Write the sign, if any, and the 0b/0o/0x prefix, if asked for.
  • fn alignment_code(a : Alignment) -> u8
    The number FormatSpec::align stores for a.
  • fn write_integer<W : Write>(w : &mut W, spec : &FormatSpec, negative : bool, magnitude : u64, radix : u64) -> Result<i32, Error>
    Write an integer given as its sign and magnitude in base radix (2 to 36), padded as spec asks. This is Rust’s Formatter::pad_integral, with the digits made here.

ByteBuf methods

  • unsafe fn new(ptr : *mut i8, cap : i64) -> ByteBuf
    Unsafe because nothing checks that ptr holds cap bytes.
  • fn len(self : &ByteBuf) -> i64
    How many bytes have been written.
  • fn byte(self : &ByteBuf, i : i64) -> u8
    The byte at i, which must be below len.
  • fn as_c_str(self : &ByteBuf) -> *const i8
    The bytes written so far, followed by a NUL.
  • fn clear(self : &mut ByteBuf) -> i32
    Forget what was written, keeping the memory.

Write for ByteBuf methods

  • fn write_u8(self : &mut ByteBuf, b : u8) -> Result<i32, Error>

T methods, stamped for 4 instantiations

  • fn fmt_decimal<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {}: decimal, with a - when negative.
  • fn fmt_binary<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:b}.
  • fn fmt_octal<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:o}.
  • fn fmt_hex<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:x}, or {:X} when spec asks for upper case.

T = i8, i16, i32, i64

T methods, stamped for 5 instantiations

  • fn fmt_decimal<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {}.
  • fn fmt_binary<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:b}.
  • fn fmt_octal<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:o}.
  • fn fmt_hex<W : Write>(self : T, w : &mut W, spec : &FormatSpec) -> Result<i32, Error>
    Rust’s {:x}, or {:X} when spec asks for upper case.

T = u8, u16, u32, u64, usize

core::hash

Types

  • trait Hasher
  • trait Hash
  • struct FxHasher
    rustc’s hasher: fast, not resistant to a chosen-key attack.
  • struct SipState
  • struct SipHasher13
    SipHash-1-3: one round per block, three to finish. Bytes are gathered into buf and a block is mixed in once eight are there, so the answer depends on how many bytes were written, not on how they were grouped.

trait Hasher methods

  • fn write_u8(self : &mut Self, b : u8) -> i32
  • fn finish(self : &Self) -> u64
  • fn write_u16(self : &mut Self, v : u16) -> i32
  • fn write_u32(self : &mut Self, v : u32) -> i32
  • fn write_u64(self : &mut Self, v : u64) -> i32
  • fn write_i8(self : &mut Self, v : i8) -> i32
  • fn write_i16(self : &mut Self, v : i16) -> i32
  • fn write_i32(self : &mut Self, v : i32) -> i32
  • fn write_i64(self : &mut Self, v : i64) -> i32
  • fn write_usize(self : &mut Self, v : usize) -> i32
    Written as a u64, so a usize hashes the same as a u64 holding the same number.
  • fn write_bool(self : &mut Self, v : bool) -> i32

trait Hash methods

  • fn hash<H : Hasher>(self : &Self, state : &mut H) -> i32

FxHasher methods

  • fn new() -> FxHasher

Hasher for FxHasher methods

  • fn write_u8(self : &mut FxHasher, b : u8) -> i32
  • fn finish(self : &FxHasher) -> u64

Functions

  • fn sip_round(s : SipState) -> SipState
    One SipRound. Taken and returned by value because four results do not fit a return.

SipHasher13 methods

  • fn with_keys(k0 : u64, k1 : u64) -> SipHasher13
  • fn new() -> SipHasher13

Hasher for SipHasher13 methods

  • fn write_u8(self : &mut SipHasher13, b : u8) -> i32
  • fn finish(self : &SipHasher13) -> u64

Hash for T methods, stamped for 10 instantiations

  • fn hash<H : Hasher>(self : &T, state : &mut H) -> i32

T = u8, u16, u32, u64, usize, i8, i16, i32, i64, bool

core::hint

Functions

  • unsafe fn assert_unchecked(cond : bool) -> void
    Tells the optimizer that cond is true, so it may drop checks that follow from it. Nothing checks it: if cond is false, the program’s behaviour is undefined. A module that uses this is skipped by the pass that frees heap buffers, because the llvm.intr.assume it becomes does not declare its memory effects.
  • fn must_use<T>(value : T) -> T
    Returns value unchanged, marking a value that should not be ignored.
  • fn select_unpredictable<T>(condition : bool, true_val : T, false_val : T) -> T
    true_val if condition holds, otherwise false_val. Both are already computed, so the answer can be a select rather than a branch the processor must predict.

core::iter::adapters

The iterators Iterator’s adaptor methods build: Map, Filter, Chain and the rest.

Types

  • struct Map<I, F>
    An iterator over another one’s items with f applied to each. Built by map.
  • struct Filter<I, P>
  • struct Take<I>
    An iterator over at most left items of another. Built by take.
  • struct Skip<I>
    An iterator over another’s items with the first drop of them discarded. Built by skip.
  • struct StepBy<I>
    An iterator over every stepth item of another, starting with its first. Built by step_by.
  • struct Chain<A, B>
    An iterator over the items of one iterator, then those of another. Built by chain.
  • struct TakeWhile<I, P>
    An iterator over another’s items for as long as keep accepts them. Built by take_while.
  • struct SkipWhile<I, P>
    An iterator over another’s items from the first one skip refuses. Built by skip_while.
  • struct MapWhile<I, F>
    An iterator over what f answers for another’s items, until it answers nothing. Built by map_while.
  • struct Inspect<I, F>
    An iterator that hands each of another’s items to f on its way past. Built by inspect.
  • struct Scan<I, St, F>
    An iterator over what f answers for another’s items while it carries a state between them, ending where f first answers nothing. Built by scan.
  • struct Copied<I>
    An iterator that answers nothing forever once another has answered nothing once. Built by fuse. An iterator over copies of the values another iterator’s references point at. Built by copied.
  • struct Cloned<I>
    An iterator over clones of the values another iterator’s references point at. Built by cloned.
  • struct Fuse<I>
  • struct Peekable<I, T>
    An iterator whose next item can be looked at without taking it. Built by peekable. T is always I::Item: a struct field cannot name I::Item, so the item it holds on to is typed by a parameter of its own, which peekable fills in.
  • struct FlatMap<I, F, U>
    An iterator over the items of the iterators f answers for another’s items, one after another. Built by flat_map. U is what f answers, held while its items are handed out, and a parameter of its own for the reason Peekable’s T is.
  • struct Flatten<I, U>
    An iterator over the items of each of another’s items, which are iterators themselves. Built by flatten.
  • struct Rev<I>
    An iterator over another’s items from the back. Built by rev.
  • struct Zip<A, B>
    An iterator over pairs of two others’ items, taken in step, ending when either does. Built by zip.
  • struct Enumerate<I>
    An iterator over another’s items, each paired with how far along it is, counting from zero. Built by enumerate.
  • struct Cycle<I>
    An iterator over another one’s items, again and again. Built by cycle. orig is kept untouched. Each pass runs over a clone of it held in cur, made when the pass starts. The clone is taken here rather than in cycle, because a default method of Iterator is checked for every iterator type, and most of them are not Clone; this impl is only checked for the types that are actually cycled.

Iterator for Map<I, Closure1<I : : Item, U>> methods

  • fn next(self : &mut Map<I, Closure1<I : : Item, U>>) -> Option<U>
    The inner iterator’s next item with f applied.

DoubleEndedIterator for Map<I, Closure1<I : : Item, U>> methods

  • fn next_back(self : &mut Map<I, Closure1<I : : Item, U>>) -> Option<U>
    The inner iterator’s last item with f applied.

ExactSizeIterator for Map<I, Closure1<I : : Item, U>> methods

  • fn len(self : &Map<I, Closure1<I : : Item, U>>) -> i64
    As many as the inner iterator has.

Iterator for Filter<I, Closure1<&I : : Item, bool>> methods

  • fn next(self : &mut Filter<I, Closure1<&I : : Item, bool>>) -> Option<I : : Item>
    The inner iterator’s next item that keep accepts.

DoubleEndedIterator for Filter<I, Closure1<&I : : Item, bool>> methods

  • fn next_back(self : &mut Filter<I, Closure1<&I : : Item, bool>>) -> Option<I : : Item>
    The inner iterator’s last item that keep accepts.

Iterator for Take<I> methods

  • fn next(self : &mut Take<I>) -> Option<I : : Item>
    The inner iterator’s next item, until left of them have been handed out.

ExactSizeIterator for Take<I> methods

  • fn len(self : &Take<I>) -> i64
    The inner iterator’s count, up to left.

DoubleEndedIterator for Take<I> methods

  • fn next_back(self : &mut Take<I>) -> Option<I : : Item>
    The last of the items next would hand out, passing over the inner iterator’s items beyond them.

Iterator for Skip<I> methods

  • fn next(self : &mut Skip<I>) -> Option<I : : Item>
    The inner iterator’s next item, once the skipped ones have been consumed.

ExactSizeIterator for Skip<I> methods

  • fn len(self : &Skip<I>) -> i64
    The inner iterator’s count less the ones still to be skipped.

DoubleEndedIterator for Skip<I> methods

  • fn next_back(self : &mut Skip<I>) -> Option<I : : Item>
    The inner iterator’s last item, while it is not one of the skipped ones.

Iterator for StepBy<I> methods

  • fn next(self : &mut StepBy<I>) -> Option<I : : Item>
    The inner iterator’s first item, and after that the one step places further on.

ExactSizeIterator for StepBy<I> methods

  • fn len(self : &StepBy<I>) -> i64
    How many of the inner iterator’s items land on a step.

DoubleEndedIterator for StepBy<I> methods

  • fn next_back(self : &mut StepBy<I>) -> Option<I : : Item>
    The last item that lands on a step, passing over the inner iterator’s items after it.

Iterator for Chain<A, B> methods

  • fn next(self : &mut Chain<A, B>) -> Option<A : : Item>
    The first iterator’s next item, or the second’s once the first is finished.

DoubleEndedIterator for Chain<A, B> methods

  • fn next_back(self : &mut Chain<A, B>) -> Option<A : : Item>
    The second iterator’s last item, or the first’s once the second is finished.

Iterator for TakeWhile<I, Closure1<&I : : Item, bool>> methods

  • fn next(self : &mut TakeWhile<I, Closure1<&I : : Item, bool>>) -> Option<I : : Item>
    The inner iterator’s next item, until the first keep refuses; nothing from then on.

Iterator for SkipWhile<I, Closure1<&I : : Item, bool>> methods

  • fn next(self : &mut SkipWhile<I, Closure1<&I : : Item, bool>>) -> Option<I : : Item>
    The inner iterator’s next item, once the leading ones skip accepts are consumed.

Iterator for MapWhile<I, Closure1<I : : Item, Option<U>>> methods

  • fn next(self : &mut MapWhile<I, Closure1<I : : Item, Option<U>>>) -> Option<U>
    f of the inner iterator’s next item, which is nothing once f says so.

Iterator for Inspect<I, Closure1<&I : : Item, i32>> methods

  • fn next(self : &mut Inspect<I, Closure1<&I : : Item, i32>>) -> Option<I : : Item>
    The inner iterator’s next item, after f has seen it.

DoubleEndedIterator for Inspect<I, Closure1<&I : : Item, i32>> methods

  • fn next_back(self : &mut Inspect<I, Closure1<&I : : Item, i32>>) -> Option<I : : Item>
    The inner iterator’s last item, after f has seen it.

ExactSizeIterator for Inspect<I, Closure1<&I : : Item, i32>> methods

  • fn len(self : &Inspect<I, Closure1<&I : : Item, i32>>) -> i64
    As many as the inner iterator has.

Iterator for Scan<I, St, Closure2<&mut St, I : : Item, Option<B>>> methods

  • fn next(self : &mut Scan<I, St, Closure2<&mut St, I : : Item, Option<B>>>) -> Option<B>
    f of the state and the inner iterator’s next item.

> Iterator for Copied<I> methods

  • fn next(self : &mut Copied<I>) -> Option<T>
    A copy of what the inner iterator’s next reference points at.

> Iterator for Cloned<I> methods

  • fn next(self : &mut Cloned<I>) -> Option<T>
    A clone of what the inner iterator’s next reference points at.

Iterator for Fuse<I> methods

  • fn next(self : &mut Fuse<I>) -> Option<I : : Item>
    The inner iterator’s next item, or nothing for good once it has run out.

ExactSizeIterator for Fuse<I> methods

  • fn len(self : &Fuse<I>) -> i64
    As many as the inner iterator has, and none once it has run out.

Iterator for Peekable<I, I : : Item> methods

  • fn next(self : &mut Peekable<I, I : : Item>) -> Option<I : : Item>
    The item peek looked at, if it looked, and otherwise the inner iterator’s next.

Peekable<I, I : : Item> methods

  • fn peek(self : &mut Peekable<I, I : : Item>) -> Option<I : : Item>
    The item next would answer, left where it is. Answers a copy, not a reference into the iterator.
  • fn next_if(self : &mut Peekable<I, I : : Item>, accept : Closure1<&I : : Item, bool>) -> Option<I : : Item>
    The next item if accept takes it, and otherwise nothing, with the item left in place.

ExactSizeIterator for Peekable<I, I : : Item> methods

  • fn len(self : &Peekable<I, I : : Item>) -> i64
    As many as the inner iterator has, and the one peek holds.

Iterator for FlatMap<I, Closure1<I : : Item, U>, U> methods

  • fn next(self : &mut FlatMap<I, Closure1<I : : Item, U>, U>) -> Option<U : : Item>
    The current inner iterator’s next item, moving to the next one when it runs out.

Iterator for Flatten<I, U> methods

  • fn next(self : &mut Flatten<I, U>) -> Option<U : : Item>
    The current inner iterator’s next item, moving to the next one when it runs out.

Iterator for Rev<I> methods

  • fn next(self : &mut Rev<I>) -> Option<I : : Item>
    The inner iterator’s last item.

DoubleEndedIterator for Rev<I> methods

  • fn next_back(self : &mut Rev<I>) -> Option<I : : Item>
    The inner iterator’s first item.

ExactSizeIterator for Rev<I> methods

  • fn len(self : &Rev<I>) -> i64
    As many as the inner iterator has.

Iterator for Zip<A, B> methods

  • fn next(self : &mut Zip<A, B>) -> Option<(A : : Item, B : : Item)>
    The next item of each, paired, or nothing once either has run out.

ExactSizeIterator for Zip<A, B> methods

  • fn len(self : &Zip<A, B>) -> i64
    As many as the shorter of the two has.

DoubleEndedIterator for Zip<A, B> methods

  • fn next_back(self : &mut Zip<A, B>) -> Option<(A : : Item, B : : Item)>
    The last pair, once the longer iterator’s extra items at the back have been dropped.

Iterator for Enumerate<I> methods

  • fn next(self : &mut Enumerate<I>) -> Option<(i64, I : : Item)>
    The inner iterator’s next item with its position.

ExactSizeIterator for Enumerate<I> methods

  • fn len(self : &Enumerate<I>) -> i64
    As many as the inner iterator has.

DoubleEndedIterator for Enumerate<I> methods

  • fn next_back(self : &mut Enumerate<I>) -> Option<(i64, I : : Item)>
    The inner iterator’s last item with its position, which is how many come before it.

Iterator for Cycle<I> methods

  • fn next(self : &mut Cycle<I>) -> Option<I : : Item>
    The next item of the current pass, or the first of a new pass once it has run out. Nothing, forever, when the original iterator has no items at all.

core::iter::traits

The Iterator trait: one required next, and the methods written over it.

Types

  • trait Iterator
  • trait DoubleEndedIterator
    An iterator that can also answer from its far end, which is what rev needs. Not a subtrait of Iterator, because Vx has no subtraits; Self::Item here is the one the type’s Iterator impl binds.
  • trait ExactSizeIterator
    An iterator that knows how many items it has left, which is what rev needs from take, skip and step_by. Not a subtrait of Iterator, because Vx has no subtraits.
  • trait IntoIterator
    A value that can be turned into an iterator, which is what a for loop does with what it is given when that is not an iterator already: for x in v over a Vec.
  • trait FromIterator<A>
    A collection that can be built from an iterator’s items, which is what collect builds.
  • trait Extend<A>
    A collection that grows by an iterator’s items, which is what partition and unzip fill.
  • trait Sum<A>
    A type whose values can be added up from an iterator’s items, which is what sum does.
  • trait Product<A>
    A type whose values can be multiplied together from an iterator’s items, which is what product does.

trait Iterator methods

  • fn next(self : &mut Self) -> Option<Self : : Item>
    The next item, or nothing once the sequence is finished. The only required method. Every other method of this trait is a default written over it, so a type becomes iterable by writing this one. Calling it again after it has answered nothing is allowed and answers nothing again; an iterator that would resume is not something this trait promises either way.
  • fn count(self : &mut Self) -> i64
    How many items are left, consuming them all to find out.
  • fn last(self : &mut Self) -> Option<Self : : Item>
    The final item, consuming the sequence. Nothing when it is already finished.
  • fn nth(self : &mut Self, n : i64) -> Option<Self : : Item>
    The item n places along, counting the next one as zero, discarding those before it. Nothing when the sequence finishes first. The items skipped are consumed either way.
  • fn any(self : &mut Self, f : Closure1<Self : : Item, bool>) -> bool
    Does f accept any item? Stops at the first it does, leaving the rest unconsumed.
  • fn all(self : &mut Self, f : Closure1<Self : : Item, bool>) -> bool
    Does f accept every item? Stops at the first it does not. True for a sequence that is already finished, which is the usual convention: there is no item to disagree.
  • fn find(self : &mut Self, f : Closure1<Self : : Item, bool>) -> Option<Self : : Item>
    The first item f accepts, or nothing. Stops there, so the rest is unconsumed.
  • fn position(self : &mut Self, f : Closure1<Self : : Item, bool>) -> Option<i64>
    How far along the first item f accepts is, counting the next one as zero.
  • fn for_each(self : &mut Self, f : Closure1<Self : : Item, i32>) -> i32
    Hand every item to f, consuming the sequence. f answers an i32 rather than nothing, and this returns the last of them, because no closure literal can return void yet (Vx#711). Both will return nothing when it can.
  • fn fold<B>(self : &mut Self, init : B, f : Closure2<B, Self : : Item, B>) -> B
    f over every item, carrying a value from one to the next: init goes in with the first item, and what f answers goes in with the next. The last answer is the result, or init for a sequence that is already finished.
  • fn try_fold<Acc, R>(self : &mut Self, init : Acc, f : Closure2<Acc, Self : : Item, R>) -> R
    fold that can stop early. f answers an Option or a Result: Some or Ok carries its value on to the next item, and the first None or Err is returned at once, leaving the rest of the sequence unconsumed. When every item carries on, the answer is the last value wrapped the same way, or init wrapped for a sequence that is already finished.
  • fn try_for_each<R>(self : &mut Self, f : Closure1<Self : : Item, R>) -> R
    for_each that can stop early: the first None or Err that f answers is returned at once, and the rest of the sequence is left unconsumed. Otherwise the answer is Some(0) or Ok(0). f answers an Option<i32> or Result<i32, E> whose value is discarded, because no closure literal can return void yet.
  • fn sum(self : &mut Self) -> Self : : Item
    Every item added up, consuming the sequence. Zero for one that is already finished. The item type says how, by implementing Sum, and the result has the item’s type.
  • fn product(self : &mut Self) -> Self : : Item
    Every item multiplied together, consuming the sequence. One for one already finished. The item type says how, by implementing Product.
  • fn max(self : &mut Self) -> Option<Self : : Item>
    The greatest item, or nothing for a sequence already finished. Of several equal greatest items, the last.
  • fn min(self : &mut Self) -> Option<Self : : Item>
    The least item, or nothing for a sequence already finished. Of several equal least items, the first.
  • fn max_by(self : &mut Self, compare : Closure2<&Self : : Item, &Self : : Item, Ordering>) -> Option<Self : : Item>
    The greatest item as compare orders them, compare(a, b) saying where a sits against b. Of several equal greatest items, the last.
  • fn min_by(self : &mut Self, compare : Closure2<&Self : : Item, &Self : : Item, Ordering>) -> Option<Self : : Item>
    The least item as compare orders them. Of several equal least items, the first.
  • fn max_by_key<B>(self : &mut Self, key : Closure1<&Self : : Item, B>) -> Option<Self : : Item>
    The item whose key is greatest. Of several with equal greatest keys, the last.
  • fn min_by_key<B>(self : &mut Self, key : Closure1<&Self : : Item, B>) -> Option<Self : : Item>
    The item whose key is least. Of several with equal least keys, the first.
  • fn map<U>(self : Self, f : Closure1<Self : : Item, U>) -> Map<Self, Closure1<Self : : Item, U>>
    An iterator over these items with f applied to each.
  • fn filter(self : Self, keep : Closure1<&Self : : Item, bool>) -> Filter<Self, Closure1<&Self : : Item, bool>>
    An iterator over the items keep accepts.
  • fn take(self : Self, n : i64) -> Take<Self>
    An iterator over at most the first n items.
  • fn skip(self : Self, n : i64) -> Skip<Self>
    An iterator over the items after the first n.
  • fn step_by(self : Self, step : i64) -> StepBy<Self>
    An iterator over the first item and every stepth one after it.

    Panics

    When step is not positive: a step of zero would hand out the first item forever.
  • fn chain<B>(self : Self, other : B) -> Chain<Self, B>
    An iterator over these items and then other’s, which must be of the same type.
  • fn take_while(self : Self, keep : Closure1<&Self : : Item, bool>) -> TakeWhile<Self, Closure1<&Self : : Item, bool>>
    An iterator over the leading items keep accepts, stopping at the first it refuses.
  • fn skip_while(self : Self, skip : Closure1<&Self : : Item, bool>) -> SkipWhile<Self, Closure1<&Self : : Item, bool>>
    An iterator over the items from the first one skip refuses onward.
  • fn map_while<U>(self : Self, f : Closure1<Self : : Item, Option<U>>) -> MapWhile<Self, Closure1<Self : : Item, Option<U>>>
    An iterator over what f answers for each item, ending where f first answers nothing.
  • fn inspect(self : Self, f : Closure1<&Self : : Item, i32>) -> Inspect<Self, Closure1<&Self : : Item, i32>>
    An iterator over these items that hands each to f on its way past. f answers an i32, which is discarded, because no closure literal can return void yet.
  • fn scan<St, B>(self : Self, initial : St, f : Closure2<&mut St, Self : : Item, Option<B>>) -> Scan<Self, St, Closure2<&mut St, Self : : Item, Option<B>>>
    An iterator over what f answers for each item, with f given initial to keep and change from one item to the next. Ends where f first answers nothing.
  • fn cycle(self : Self) -> Cycle<Self>
    An iterator over these items, repeated forever. Each pass restarts from a clone of this iterator as it was when cycle was called, so the type must implement Clone. An iterator with no items gives one with none.
  • fn copied(self : Self) -> Copied<Self>
    An iterator over copies of what these items point at, for an iterator that hands out references to a Copy type, as Vec::iter does.
  • fn cloned(self : Self) -> Cloned<Self>
    An iterator over clones of what these items point at, for an iterator that hands out references to a Clone type.
  • fn fuse(self : Self) -> Fuse<Self>
    An iterator that answers nothing forever once these items have run out.
  • fn peekable(self : Self) -> Peekable<Self, Self : : Item>
    An iterator whose next item can be looked at, by peek, without taking it.
  • fn flat_map<U>(self : Self, f : Closure1<Self : : Item, U>) -> FlatMap<Self, Closure1<Self : : Item, U>, U>
    An iterator over the items of each iterator f answers, in order. f answers an iterator.
  • fn flatten(self : Self) -> Flatten<Self, Self : : Item>
    An iterator over the items of each of these items, which are iterators themselves.
  • fn rev(self : Self) -> Rev<Self>
    An iterator over these items from the back, for an iterator that can answer from both ends.
  • fn zip<U>(self : Self, other : U) -> Zip<Self, U>
    An iterator over pairs of these items and other’s, in step, as long as both last.
  • fn enumerate(self : Self) -> Enumerate<Self>
    An iterator over these items, each paired with its position, counting from zero. The position is an i64.
  • fn reduce(self : &mut Self, f : Closure2<Self : : Item, Self : : Item, Self : : Item>) -> Option<Self : : Item>
    fold with the first item as the starting value, so nothing for a sequence already finished.
  • fn collect<B>(self : &mut Self) -> B
    Every item, gathered into a new collection of whatever type the result is assigned to: let v : Vec<i64> = it.collect();. That type says how, by implementing FromIterator.
  • fn partition<B>(self : &mut Self, f : Closure1<&Self : : Item, bool>) -> (B, B)
    Every item, split in two by f: the ones it accepts first, the rest second. Each half is a collection of whatever type the result is assigned to, which starts from its Default and grows through Extend.
  • fn unzip<FromA, FromB>(self : &mut Self) -> (FromA, FromB)
    An iterator of pairs, split into two collections: the first of each pair in one, the second in the other.
  • fn cmp<Other>(self : &mut Self, other : Other) -> Ordering
    These items against other’s, in step, by their Ord: the first pair that differs decides. When one sequence runs out first and every pair so far was equal, the shorter one is Less, as for words in a dictionary.
  • fn partial_cmp<Other>(self : &mut Self, other : Other) -> Option<Ordering>
    cmp by the items’ PartialOrd, so nothing as soon as a pair does not compare, which is what a NaN does.
  • fn eq<Other>(self : &mut Self, other : Other) -> bool
    Are these items equal to other’s, one for one and as many of them?
  • fn ne<Other>(self : &mut Self, other : Other) -> bool
    Do these items differ from other’s anywhere, or in how many there are?
  • fn lt<Other>(self : &mut Self, other : Other) -> bool
    Are these items less than other’s, in the order partial_cmp gives? False when some pair does not compare.
  • fn le<Other>(self : &mut Self, other : Other) -> bool
    Less than or equal, by partial_cmp. False when some pair does not compare.
  • fn gt<Other>(self : &mut Self, other : Other) -> bool
    Greater than, by partial_cmp. False when some pair does not compare.
  • fn ge<Other>(self : &mut Self, other : Other) -> bool
    Greater than or equal, by partial_cmp. False when some pair does not compare.

binds. trait DoubleEndedIterator methods

  • fn next_back(self : &mut Self) -> Option<Self : : Item>
    The last item not yet handed out from either end, or nothing once they meet.
  • fn rfold<B>(self : &mut Self, init : B, f : Closure2<B, Self : : Item, B>) -> B
    fold, from the back.
  • fn rfind(self : &mut Self, f : Closure1<&Self : : Item, bool>) -> Option<Self : : Item>
    The last item f accepts, searching from the back.
  • fn nth_back(self : &mut Self, n : i64) -> Option<Self : : Item>
    The item n places from the back, counting the last as zero.

trait ExactSizeIterator methods

  • fn len(self : &Self) -> i64
    How many items are left.
  • fn is_empty(self : &Self) -> bool
    Whether no items are left.

trait IntoIterator methods

  • fn into_iter(self : Self) -> Self : : IntoIter
    An iterator over this value’s items, consuming the value.

trait FromIterator<A> methods

  • fn from_iter<I : Iterator>(iter : I) -> Self
    A new collection holding every item iter has left, in order.

trait Extend<A> methods

  • fn extend<I : Iterator>(self : &mut Self, iter : I) -> i32
    Add every item iter has left, in order.
  • fn extend_one(self : &mut Self, item : A) -> i32
    Add one item.

trait Sum<A> methods

  • fn sum<I : Iterator>(iter : I) -> Self
    Every item iter has left, added up. Zero when it has none.

trait Product<A> methods

  • fn product<I : Iterator>(iter : I) -> Self
    Every item iter has left, multiplied together. One when it has none.

Sum<T> for T methods, stamped for 11 instantiations

  • fn sum<I : Iterator>(iter : I) -> T
    Every item added up, from zero.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Product<T> for T methods, stamped for 11 instantiations

  • fn product<I : Iterator>(iter : I) -> T
    Every item multiplied together, from one.

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

core::iter

Range and range; importing it brings the trait and the adaptors too.

Types

  • struct Range
    The numbers from at up to but not including end. A half-open span of i64, from at up to but not including end.

Iterator for Range methods

  • fn next(self : &mut Range) -> Option<i64>
    The next value in the span, or nothing once end is reached.

DoubleEndedIterator for Range methods

  • fn next_back(self : &mut Range) -> Option<i64>
    The last value in the span not yet handed out from either end.

ExactSizeIterator for Range methods

  • fn len(self : &Range) -> i64
    How many values are left in the span.

Clone for Range methods

  • fn clone(self : &Range) -> Range
    A second span over the same values, which is what cycle restarts from.

Functions

  • fn range(at : i64, end : i64) -> Range
    The numbers at .. end.

core::libm

Types

  • struct LibmDyadic128
    (-1)^negative * 2^exponent * mantissa, where the mantissa is the 128-bit integer hi * 2^64 + lo. Normalized when the mantissa is zero or its top bit is set.
  • struct LibmTrigReduced
    The reduced argument for sinf, cosf and tanf: k = round(x * 32 / pi) and y = x * 32 / pi - k, from range_reduction_fma.h. 32 / pi is held in five doubles; up to 2^45 two of them are enough, and beyond that small_range_reduction would lose the integer bits, so large_range_reduction first drops the part of the product that only adds whole multiples of 64.
  • struct LibmSinCosParts
    sin(k * pi / 32), cos(k * pi / 32), sin(y * pi / 32) and cos(y * pi / 32) - 1 for the reduced argument: sincosf_eval from sincosf_utils.h.
  • struct LibmDoubleDouble
    A double-double: hi + lo, with |lo| at most half a unit in the last place of hi. LLVM libc’s DoubleDouble, from FPUtil/double_double.h.
  • struct LibmI128
    hi * 2^64 + lo read as a signed 128-bit integer.
  • struct LibmLogReduced
    The result of libm_log_range_reduction: the sum of the table values it added, and the reduced argument.
  • struct LibmZivResult
    The result of libm_exp_ziv_denorm: ok is false when the error bound does not decide the rounding.
  • struct LibmExp2Reduced
    The reduction exp2 and its subnormal path share: k = round(x * 2^12), and the fast pass’s exp_mid and lo.
  • struct LibmTrig64Reduced
    The result of reducing x by multiples of pi/128: x = k pi / 128 + u, with |u| at most about pi/256. For a large x, the pieces the 128-bit pass needs to redo the reduction.
  • struct LibmSinCos
    sin(u) and cos(u) as double-doubles, and the bound on their error: sincos_eval from sincos_eval.h, Taylor polynomials in u.hi with u.lo added to first order.
  • struct LibmAsinEval
    asin(x) / x as a double-double for u = x^2 (or the reduced argument of the larger range), from row round(32 u.hi) of LIBM_ASIN_COEFFS: asin_eval from asin_utils.h. err is the error bound so far, which it scales by the size of the reduced argument.
  • struct LibmAsinSmall128
    The reduced argument of the 128-bit pass for |x| < 1/2: u = x^2 - idx / 64 with idx = round(64 x^2), from x^2 as a double-double. Its idx comes back in k.
  • struct LibmAsinLarge
    For 1/2 <= |x| < 1, asin(|x|) = pi/2 - 2 asin(sqrt(u)) with u = (1 - |x|) / 2. This holds the pieces both asin and acos need: v = sqrt(u) as vh / 2 + vl / 2, and the double-double 2 sqrt(u) * asin(sqrt(u)) / sqrt(u) with its error bound.
  • struct LibmExp10Reduced
    The reduction exp10 and its subnormal path share: k = round(x * log2(10) * 2^12), and the fast pass’s exp_mid and lo.
  • struct LibmU256
    A 256-bit unsigned integer as four 64-bit words, w0 the lowest: LLVM libc’s UInt<256>.
  • struct LibmU256Carry
    The sum of a and b and whether it carried out of the top word.
  • struct LibmMulAcc
    The running two-word sum of quick_mul_hi, with the carries out of it counted.
  • struct LibmDyadic256
    (-1)^negative * 2^exponent * m, with m a 256-bit integer: LLVM libc’s DyadicFloat<256>, which the last pass of pow uses.
  • struct LibmMaybeF64
    What pow answers for the inputs check_special_inputs of pow_utils.h settles: x or y with mostly zero mantissa bits, such as zeros, ones, powers of two and NaNs. has is false when the general path must answer.
  • struct LibmPow64State
    The state pow carries out of check_exceptional_cases in pow_utils.h: the answer when it has one, else x and y (made positive, or moved in range), x’s exponent and mantissa (a subnormal normalized), and the result’s sign.
  • struct LibmPow64Exact
    The exact value of x^y, as m * 2^e, when it has one with at most 54 bits in m: is_exact_rounding_boundary from pow_utils.h. Such a value can lie exactly halfway between two doubles, which an error bound can never decide.
  • struct LibmExpParts
    e^a as 2^hi * m, with m a double-double between about 0.7 and 1.5, for |a| below about 745: the reduction and double-double pass of libm_exp, without scaling by 2^hi, so that a value past the largest double can still be divided.

Functions

  • fn libm_f64_to_bits(x : f64) -> u64
  • fn libm_f64_from_bits(v : u64) -> f64
  • fn libm_f32_to_bits(x : f32) -> u32
  • fn libm_f32_from_bits(v : u32) -> f32
  • fn libm_multiply_add(x : f64, y : f64, z : f64) -> f64
    x * y + z with one rounding: fputil::multiply_add with FMA.
  • fn libm_sqrt_f64(x : f64) -> f64
    The correctly rounded square root: fputil::sqrt<double>.
  • fn libm_log_eval(x : f64) -> f64
    log(x) for a positive, normal x, accurate enough for the single-precision functions: log_eval from acoshf_utils.h, accurate version. With x = 2^e * m, it is e * log(2) + log(1 / r) + log(1 + dx), where r from the table makes dx = r * m - 1 small enough for a degree-6 polynomial (Sollya: fpminimax(log(1 + x)/x, 6, [|1, D...|], [-2^-7, 2^-6])).
  • fn libm_asinhf(x : f32) -> f32
    The inverse hyperbolic sine, correctly rounded: asinhf from asinhf.h.
  • fn libm_acoshf(x : f32) -> f32
    The inverse hyperbolic cosine, correctly rounded; NaN below 1: acoshf from acoshf.h.
  • fn libm_atanhf(x : f32) -> f32
    The inverse hyperbolic tangent, correctly rounded; infinite at +-1 and NaN beyond: atanhf from atanhf.h.
  • fn libm_multiply_add_f32(x : f32, y : f32, z : f32) -> f32
    fmaf(x, y, z): x * y + z in single precision, rounded once.
  • fn libm_f32_is_signaling_nan(x_u : u32) -> bool
    Whether the bits of an f32 are a signaling NaN: all exponent bits set, a nonzero mantissa, and its top bit (the quiet bit) clear.
  • fn libm_logf(x : f32) -> f32
    The natural logarithm, correctly rounded: logf from logf.h. With x = 2^m * 1.mant and f the top 8 bits of 1.mant, it is m * log(2) + log(f) + log(1 + d / f), the last a degree-5 polynomial (Sollya: fpminimax(log(1 + x)/x, 4, [|1, D...|], [-2^-8, 2^-7])).
  • fn libm_log2f(x : f32) -> f32
    The logarithm to base two, correctly rounded: log2f from log2f.h, the same reduction as libm_logf with a degree-5 polynomial for log2(1 + x) (Sollya: fpminimax(log2(1 + x)/x, 4, [|1, D...|], [-2^-8, 2^-7])).
  • fn libm_log10f(x : f32) -> f32
    The logarithm to base ten, correctly rounded: log10f from log10f.h. Powers of ten up to 10^10 are exact.
  • fn libm_log1pf(x : f32) -> f32
    log(1 + x), correctly rounded: log1pf from log1pf.h. For |x| <= 2^-6 a degree-8 polynomial (Sollya: fpminimax(log(1 + x)/x, 7, [|D...|], [-2^-6; 2^-6])); otherwise 1 + x is exact in double precision and libm_log_eval takes its logarithm.
  • fn libm_f16_to_bits(x : f16) -> u16
    The bits of an f16.
  • fn libm_f16_from_bits(v : u16) -> f16
    The f16 with these bits.
  • fn libm_f16_log_special(x_u : u16) -> u32
    What ln, log2 and log10 of an f16 answer for zero, one, a negative number, infinity and NaN, as bits; 65536, which no f16 has, for any other input. The shared start of logf16, log2f16 and log10f16.
  • fn libm_logf16(x : f16) -> f16
    The natural logarithm of an f16, correctly rounded: logf16 from logf16.h. With x = 2^m * 1.mant and f the top 6 bits of 1.mant, it is m * log(2) + log(f) + log(1 + d / f), the last a degree-3 polynomial in single precision (Sollya: fpminimax(log(1 + x)/x, 2, [|SG...|], [-2^-5, 2^-5])).
  • fn libm_log2f16(x : f16) -> f16
    The logarithm to base two of an f16, correctly rounded: log2f16 from log2f16.h, the same reduction as libm_logf16 (Sollya: fpminimax(log2(1 + x)/x, 2, [|SG...|], [-2^-5, 2^-5])).
  • fn libm_log10f16(x : f16) -> f16
    The logarithm to base ten of an f16, correctly rounded: log10f16 from log10f16.h, the same reduction as libm_logf16.
  • fn libm_log1pf16(x : f16) -> f16
    log(1 + x) of an f16, correctly rounded. LLVM libc has no log1pf16: this is its log1pf on the input widened to f32, rounded to f16. Rounding twice lands one step off for two inputs, found by checking all 65,536 inputs against ln(1 + x) computed to 60 digits; they are listed here.
  • fn libm_bf16_to_bits(x : bf16) -> u16
    The bits of a bf16.
  • fn libm_bf16_from_bits(v : u16) -> bf16
    The bf16 with these bits.
  • fn libm_f32_from_bf16(x : bf16) -> f32
    A bf16 widened to f32, exactly, by moving its bits. as f32 is exact too, but LLVM then narrows a comparison such as (x as f32) == 0.0 back to bf16, and on a CPU with AVX512-BF16 it evaluates that through vcvtneps2bf16, which reads a subnormal as zero.
  • fn libm_bf16_from_f32(x : f32) -> bf16
    An f32 rounded to the nearest bf16, ties to even, as integer operations on its bits. as bf16 cannot be used: on a CPU with AVX512-BF16, LLVM converts with vcvtneps2bf16, which reads a subnormal input as zero and flushes a subnormal result to zero.
  • fn libm_bf16_bits_from_f32(x : f32) -> u16
    The bits of libm_bf16_from_f32(x). A bf16 function makes its result once, at its only return: on a CPU with AVX512-BF16, LLVM joins bf16 values from two branches through vcvtneps2bf16, which flushes a subnormal to zero.
  • fn libm_log_bf16(x : bf16) -> bf16
    The natural logarithm of a bf16, correctly rounded: log_bf16 from log_bf16.h. With x = 2^e * (1 + m), it is e * log(2) + log(1 + m), the last read from a table indexed by the 7 bits of m.
  • fn libm_log_bf16_bits(x_bits : u16) -> u16
    The bits of libm_log_bf16, from the bits of its input.
  • fn libm_log2_bf16(x : bf16) -> bf16
    The logarithm to base two of a bf16, correctly rounded. LLVM libc has no log2 for bf16: this is its log2f on the input widened to f32, rounded to bf16.
  • fn libm_log10_bf16(x : bf16) -> bf16
    The logarithm to base ten of a bf16, correctly rounded. LLVM libc has no log10 for bf16: this is its log10f on the input widened to f32, rounded to bf16.
  • fn libm_log1p_bf16(x : bf16) -> bf16
    log(1 + x) of a bf16, correctly rounded. LLVM libc has no log1p for bf16: this is its log1pf on the input widened to f32, rounded to bf16.
  • fn libm_u64_mul_hi(a : u64, b : u64) -> u64
    The high 64 bits of the 128-bit product a * b.
  • fn libm_u64_leading_zeros(a : u64) -> u64
    The number of zero bits above the highest set bit; 64 for zero.
  • fn libm_dyadic_shl(d : LibmDyadic128, s : u64) -> LibmDyadic128
    d with its mantissa shifted left by s bits and the exponent unchanged; zero once s reaches 128.
  • fn libm_dyadic_shr(d : LibmDyadic128, s : u64) -> LibmDyadic128
    d with its mantissa shifted right by s bits and the exponent unchanged; zero once s reaches 128. The bits shifted out are lost.
  • fn libm_dyadic_normalize(d : LibmDyadic128) -> LibmDyadic128
    d with its mantissa’s top bit moved to bit 127, and the exponent lowered to match.
  • fn libm_dyadic_from_f64(x : f64) -> LibmDyadic128
    x exactly, normalized: the DyadicFloat(double) constructor.
  • fn libm_dyadic_shift_right(d : LibmDyadic128, s : i32) -> LibmDyadic128
    d with its mantissa shifted right by s bits and the exponent raised to match; the bits shifted out are lost.
  • fn libm_dyadic_add(a0 : LibmDyadic128, b0 : LibmDyadic128) -> LibmDyadic128
    a + b, within 2 units in the last place: quick_add. The inputs are normalized; the exponents are aligned by shifting the smaller one right, dropping its low bits.
  • fn libm_dyadic_mul(a : LibmDyadic128, b : LibmDyadic128) -> LibmDyadic128
    a * b, rounded toward zero and within 2 units in the last place: quick_mul. The mantissa is quick_mul_hi of the two, the high 128 bits of their product without the low halves’ product a.lo * b.lo and without the carry out of the bits below.
  • fn libm_dyadic_to_f64(d : LibmDyadic128) -> f64
    The nearest double to d, ties to even, for a normalized d: fast_as<double>. The top 53 bits make d_hi; the round and sticky bits below them, scaled to d_hi’s last place, are added with one FMA, which rounds the sum correctly.
  • fn libm_round_even_f32(x : f32) -> f32
    x rounded to the nearest integer, ties to even: fputil::nearest_integer.
  • fn libm_expf(x : f32) -> f32
    exp(x), correctly rounded: expf from expf_double_eval.h. With x = hi + mid + lo, hi an integer and mid a multiple of 1/128, it is exp(hi) * exp(mid) * exp(lo): two tables and a degree-4 polynomial (Sollya: fpminimax(expm1(x)/x, 3, [|D...|], [-2^-8, 2^-8])).
  • fn libm_exp2f(x : f32) -> f32
    2^x, correctly rounded: exp2f from exp2f_double_eval.h. Near zero a degree-6 polynomial; otherwise 2^x = 2^hi * 2^mid * 2^lo with mid a multiple of 1/32, 2^hi added straight into the exponent of 2^mid from a table, and 2^lo a degree-5 polynomial.
  • fn libm_expm1f(x : f32) -> f32
    exp(x) - 1, correctly rounded: expm1f from expm1f.h. Near zero a polynomial; otherwise expf’s reduction with the final step exp(hi + mid) * exp(lo) - 1 as one FMA.
  • fn libm_expf16(x : f16) -> f16
  • fn libm_exp2f16(x : f16) -> f16
  • fn libm_expm1f16(x : f16) -> f16
  • fn libm_exp_bf16(x : bf16) -> bf16
  • fn libm_exp2_bf16(x : bf16) -> bf16
  • fn libm_expm1_bf16(x : bf16) -> bf16
  • fn libm_exp_pm_eval(x : f32, is_sinh : bool) -> f64
    sinh(x) (is_sinh) or cosh(x) of an f32, before rounding: exp_pm_eval from sinhfcoshf_utils.h. With x = (hi + mid) * log(2) + lo, mid a multiple of 1/32, e^x and e^-x share one reduction: 2^(hi + mid) and 2^-(hi + mid) from EXP_2_MID, and the even and odd halves of a degree-5 polynomial for e^lo and e^-lo.
  • fn libm_round_even_f64(x : f64) -> f64
    x rounded to the nearest integer, ties to even, in double precision.
  • fn libm_sinhf(x : f32) -> f32
    The hyperbolic sine, correctly rounded: sinhf from sinhf.h. Near zero a polynomial; otherwise libm_exp_pm_eval.
  • fn libm_coshf(x0 : f32) -> f32
    The hyperbolic cosine, correctly rounded: coshf from coshf.h.
  • fn libm_tanhf(x : f32) -> f32
    The hyperbolic tangent, correctly rounded: tanhf from tanhf.h. Near zero a Taylor polynomial; otherwise (e^2x - 1) / (e^2x + 1) written as (r - mh) / (r + mh) with mh = 2^-(hi + mid) and r a polynomial for e^2lo.
  • fn libm_sinhf16(x : f16) -> f16
  • fn libm_coshf16(x : f16) -> f16
  • fn libm_tanhf16(x : f16) -> f16
  • fn libm_sinh_bf16(x : bf16) -> bf16
  • fn libm_cosh_bf16(x : bf16) -> bf16
  • fn libm_tanh_bf16(x : bf16) -> bf16
  • fn libm_trig_reduce(xd : f64, x_abs : u32) -> LibmTrigReduced
  • fn libm_sincosf_eval(xd : f64, x_abs : u32) -> LibmSinCosParts
  • fn libm_sinf(x : f32) -> f32
    The sine, correctly rounded: sinf from sinf_double_eval.h. Near zero a polynomial; otherwise sin(x) = sin(y*pi/32) cos(k*pi/32) + cos(y*pi/32) sin(k*pi/32).
  • fn libm_cosf(x : f32) -> f32
    The cosine, correctly rounded: cosf from cosf_double_eval.h.
  • fn libm_tanf(x : f32) -> f32
    The tangent, correctly rounded: tanf from tanf.h. Near zero a polynomial; otherwise the sine over the cosine, from one reduction.
  • fn libm_sinf16(x : f16) -> f16
  • fn libm_cosf16(x : f16) -> f16
  • fn libm_tanf16(x : f16) -> f16
  • fn libm_sin_bf16(x : bf16) -> bf16
  • fn libm_cos_bf16(x : bf16) -> bf16
  • fn libm_tan_bf16(x : bf16) -> bf16
  • fn libm_asin_eval(xsq : f64) -> f64
    asin(x)/x - 1 in terms of xsq = x^2, for the single-precision functions: asin_eval from asin_utils.h.
  • fn libm_asinf(x : f32) -> f32
    The arcsine, correctly rounded: asinf from asinf.h. Below about 0.5 an odd polynomial; above, asin(x) = pi/2 - 2 asin(sqrt((1 - x) / 2)).
  • fn libm_acosf(x : f32) -> f32
    The arccosine, correctly rounded: acosf from acosf.h. Up to 0.5, pi/2 - asin(x); above, 2 asin(sqrt((1 - |x|) / 2)), from pi for a negative x.
  • fn libm_atan_eval(x : f64, i : i64) -> f64
    atan(i/16 + x) - atan(i/16) divided by x: atan_eval from atan_utils.h.
  • fn libm_atanf(x : f32) -> f32
    The arctangent, correctly rounded: atanf from atanf.h. Near zero and beyond 16 a Taylor polynomial (of -1/x beyond 16); between, atan(k/16) + atan_eval with k = round(16 |x|), or of 1/|x| above 1.
  • fn libm_dd_exact_add(a : f64, b : f64) -> LibmDoubleDouble
    a + b as a double-double, exactly, for |a| >= |b| (Fast2Sum): exact_add.
  • fn libm_dd_add(a : LibmDoubleDouble, b : LibmDoubleDouble) -> LibmDoubleDouble
    a + b: add.
  • fn libm_dd_quick_mult(a : LibmDoubleDouble, b : LibmDoubleDouble) -> LibmDoubleDouble
    a * b, dropping the product of the low parts: quick_mult.
  • fn libm_dd_multiply_add(a : LibmDoubleDouble, b : LibmDoubleDouble, c : LibmDoubleDouble) -> LibmDoubleDouble
    a * b + c: the double-double multiply_add.
  • fn libm_dd_nudge(rr : LibmDoubleDouble, mask : u64) -> f64
    rr.hi, moved one unit toward rr.lo when its bits below the target precision (mask) are all zero, so that rounding it to the target sees which side of a boundary the exact value is on. From the end of atan2f_double_double.
  • fn libm_atan2_double_double(num_d : f64, den_d : f64, q_d : f64, idx : i64, k_d : f64, final_sign : f64, const_hi : f64, const_lo : f64) -> LibmDoubleDouble
    The accurate pass of atan2f, for a result too near a rounding boundary to trust the fast one: atan2f_double_double from atan2f.h, the same reduction in double-double. It answers the double-double; libm_dd_nudge turns that into a double that rounds right.
  • fn libm_atan2f(y : f32, x : f32) -> f32
    atan2(y, x), the angle of the point (x, y), correctly rounded: atan2f from atan2f.h. With q = min(|x|, |y|) / max(|x|, |y|) it is atan(q) from atan_eval, adjusted for the quadrant; a result too near a rounding boundary is redone in double-double.
  • fn libm_atan2_value(y : f32, x : f32, mask : u64, always_accurate : bool) -> f64
    atan2(y, x) as a double that rounds correctly to a format whose bits below its precision are mask in a double: the body of atan2f. always_accurate skips the fast path, for a format that the fast path’s check does not cover.
  • fn libm_asinf16(x : f16) -> f16
  • fn libm_acosf16(x : f16) -> f16
  • fn libm_atanf16(x : f16) -> f16
  • fn libm_asin_bf16(x : bf16) -> bf16
  • fn libm_acos_bf16(x : bf16) -> bf16
  • fn libm_atan_bf16(x : bf16) -> bf16
  • fn libm_f32_round_odd(d : f64) -> f32
    d rounded to an f32 with round-to-odd: truncated toward zero, with the last bit set when anything was dropped. Rounding that again to a format at least two bits narrower is the same as rounding d once.
  • fn libm_atan2f16(y : f16, x : f16) -> f16
    atan2(y, x) for f16, correctly rounded: the double-double accurate pass of atan2f on the inputs widened to f32, rounded once to f16.
  • fn libm_atan2_bf16(y : bf16, x : bf16) -> bf16
    atan2(y, x) for bf16, correctly rounded: the double-double accurate pass of atan2f on the inputs widened to f32, rounded to f32 with round-to-odd and then to bf16.
  • fn libm_hypotf(x : f32, y : f32) -> f32
    sqrt(x^2 + y^2) without overflow or underflow in between, correctly rounded: hypotf from hypotf.h. The sum of squares is computed in double and its square root rounded to f32; when that double sits near a halfway point of f32, the error of the sum decides which way it goes.
  • fn libm_hypot16_special(x_bits : u16, y_bits : u16, exp_mask : u16, quiet : u16, keep_sign : bool) -> u32
    The result of hypot on infinities and NaNs, as the bits of a 16-bit float whose exponent mask is exp_mask and quiet bit quiet, or 65536 (no 16-bit value) when neither input is one: a signaling NaN in either gives the quiet NaN, then an infinity in either gives infinity, then the NaN input itself. The order of hypotf16 and hypotbf16. They differ in which NaN they answer: hypotf16 the larger in size, without its sign, and hypotbf16 (keep_sign) x if it is a NaN, else y, with its sign.
  • fn libm_hypotf16(x : f16, y : f16) -> f16
    sqrt(x^2 + y^2) for f16: the square root of the sum in double, rounded once to f16.
  • fn libm_hypot_bf16_bits(x_bits : u16, y_bits : u16) -> u16
    The bits of hypot of two bf16s, given by their bits: the square root of the sum in double, rounded to f32 with round-to-odd and then to bf16. In bits so that the bf16 result is made once, at the end (see libm_bf16_bits_from_f32).
  • fn libm_hypot_bf16(x : bf16, y : bf16) -> bf16
    sqrt(x^2 + y^2) for bf16, correctly rounded.
  • fn libm_exp10f(x : f32) -> f32
    10^x, correctly rounded: exp10f from exp10f_double_eval.h, with x = (hi + mid) * log10(2) + lo and 10^lo a degree-5 polynomial. Powers of ten up to 10^10 are exact.
  • fn libm_u32_trailing_zeros(a : u32) -> u32
    The number of zero bits below the lowest set bit; 32 for zero.
  • fn libm_u32_leading_zeros(a : u32) -> u32
    The number of zero bits above the highest set bit; 32 for zero.
  • fn libm_f32_bits_is_integer(x_u : u32) -> bool
    Whether the f32 with bits x_u is a whole number (or infinite): is_integer from powf.h.
  • fn libm_f32_bits_is_odd_integer(x_u : u32) -> bool
    Whether the f32 with bits x_u is an odd whole number: is_odd_integer from powf.h.
  • fn libm_dd_quick_mult_d(x : f64, b : LibmDoubleDouble) -> LibmDoubleDouble
    x * b as a double-double, for a double x: quick_mult(double, DoubleDouble).
  • fn libm_larger_exponent(a : f64, b : f64) -> bool
    Whether the exponent of a is at least that of b: larger_exponent from powf.h.
  • fn libm_powf_double_double(idx_x : i64, dx : f64, y6 : f64, lo6_hi : f64, exp2_hi_mid : LibmDoubleDouble) -> LibmDoubleDouble
    The accurate pass of powf, as a double-double: powf_double_double from powf.h, the logarithm refined by a second table step and both polynomials in double-double.
  • fn libm_pow_value(x0 : f32, y0 : f32, mask : u64, narrow : bool) -> f64
    x^y as a double: powf from powf.h. x^y = 2^(y log2 x): log2 x from the LOG2_R tables and a polynomial, 2^(.) from EXP2_MID1 and a polynomial, both in double; a result too near a rounding boundary is redone in double-double and moved off a halfway point by libm_dd_nudge with mask. Special cases: y of 0, 1/2, 1, 2, small whole y computed exactly, infinities, NaNs, zeros and negative x. With narrow false the result rounds to the correct f32, as powf does. With narrow true, for f16 and bf16 inputs, every result takes the double-double pass, and exp2f and exp10f, which would round twice, are not used for x of 2 and 10.
  • fn libm_powf(x : f32, y : f32) -> f32
    x^y, correctly rounded: powf from powf.h.
  • fn libm_powf16(x : f16, y : f16) -> f16
    x^y for f16, correctly rounded: libm_pow_value on the inputs widened to f32, always through the double-double pass, rounded once.
  • fn libm_pow_bf16(x : bf16, y : bf16) -> bf16
    x^y for bf16, correctly rounded: as for f16, rounded to f32 with round-to-odd and then to bf16.
  • fn libm_sqrt_f32(x : f32) -> f32
    The correctly rounded square root of an f32.
  • fn libm_i128_from_i64(v : i64) -> LibmI128
    v widened to 128 bits.
  • fn libm_i128_add(a : LibmI128, b : LibmI128) -> LibmI128
    a + b.
  • fn libm_i128_shl(a : LibmI128, s : u64) -> LibmI128
    a << s, for s below 128.
  • fn libm_i128_mul_i64(a : LibmI128, s : i64) -> LibmI128
    a * s, keeping the low 128 bits.
  • fn libm_i128_is_negative(a : LibmI128) -> bool
    Whether a is negative.
  • fn libm_i128_negate(a : LibmI128) -> LibmI128
    -a.
  • fn libm_i128_bits_from_68(a : LibmI128) -> i32
    Bits 68 to 99 of a, as (int)(a >> 68) with an arithmetic shift.
  • fn libm_dyadic_from_parts(negative : u64, exponent : u64, hi : u64, lo : u64) -> LibmDyadic128
    The dyadic float at [4 i] to [4 i + 3] of a table written by DYADIC: negative, exponent, mantissa high and low.
  • fn libm_log_range_reduction(m_x : f64, sum0 : LibmDyadic128, step : i32) -> LibmLogReduced
    Steps 2 to 4 of the range reduction for the accurate pass of log: log_range_reduction from log_range_reduction.h, on m_x with units of 2^-60. It adds -log(r) of each step to sum from step_2, step_3 and step_4 and answers what is left.
  • fn libm_log_step(table : i32, k : i32, i : i64) -> LibmDyadic128
    Entry i of step k (1 to 4) of a logarithm’s range reduction table: log’s while table is 0, log2’s while it is 1 and log10’s while it is 2.
  • fn libm_log_accurate(e_x : i32, index : i64, m_x : f64) -> f64
    The accurate pass of log: log_accurate from log.h, in 128-bit dyadic floats. e_x and index are the fast pass’s exponent and table index, m_x its reduced argument.
  • fn libm_log(x : f64) -> f64
    The natural logarithm of a double, correctly rounded: log from log.h. x = 2^e * m, and log(m) = -log(r) + log(1 + u) with r from RD and u = r * m - 1 exact; log(1 + u) is a degree-7 polynomial. When the error bound does not decide the rounding, the accurate pass redoes it in 128-bit dyadic floats.
  • fn libm_log2_accurate(e_x : i32, index : i64, m_x : f64) -> f64
    The accurate pass of log2: log2_accurate from log2.h, in 128-bit dyadic floats.
  • fn libm_log2(x : f64) -> f64
    The logarithm to base 2 of a double, correctly rounded: log2 from log2.h. The same reduction as libm_log; the natural logarithm of the reduced argument is turned into base 2 by a double-double multiply by log2(e).
  • fn libm_log10_accurate(e_x : i32, index : i64, m_x : f64) -> f64
    The accurate pass of log10: log10_accurate from log10.h, in 128-bit dyadic floats.
  • fn libm_log10(x : f64) -> f64
    The logarithm to base 10 of a double, correctly rounded: log10 from log10.h. The same reduction as libm_log, turned into base 10 by a double-double multiply by log10(e). LLVM libc also answers 10^1 to 10^22 from a table when the error bound does not decide the rounding; with FMA the bound decides all 22, so the table is left out.
  • fn libm_log1p_step(k : i32, i : i64) -> LibmDyadic128
    Entry i of LOG_R1, LOG_R2 or LOG_R3 of log1p, for k of 1, 2 or 3.
  • fn libm_log1p_accurate(e_x : i32, index : i64, m_hi : f64, m_lo : f64) -> f64
    The accurate pass of log1p: log1p_accurate from log1p.h, in 128-bit dyadic floats. m_hi + m_lo is the fast pass’s reduced argument.
  • fn libm_log1p(x : f64) -> f64
    log(1 + x) of a double, correctly rounded and accurate for a small x: log1p from log1p.h. 1 + x is formed exactly as a double-double, reduced by R1, and the result is e * log(2) - log(r) + log(1 + v) with a degree-5 polynomial for the last term.
  • fn libm_log1p_value(x : f64, always_accurate : bool) -> f64
    libm_log1p, which with always_accurate takes the accurate pass even when the fast one decides the rounding, so that a test can reach it.
  • fn libm_exp_coeff_dd(which : i32, i : i64) -> LibmDoubleDouble
    Coefficient i of the double-double polynomial of exp (which 0), exp2 (1) or expm1 (2).
  • fn libm_exp_poly_dd(which : i32, dx : LibmDoubleDouble) -> LibmDoubleDouble
    The degree-6 double-double polynomial of exp (which 0), exp2 (1) or expm1 (2) at dx, by Horner’s rule: polyeval.
  • fn libm_exp_coeff_128(which : i32, i : i64) -> LibmDyadic128
    Coefficient i of the 128-bit polynomial of exp (which 0), exp2 (1) or expm1 (2).
  • fn libm_exp_poly_128(which : i32, degree : i64, dx : LibmDyadic128) -> LibmDyadic128
    The 128-bit polynomial of exp (which 0), exp2 (1) or expm1 (2) at dx, with coefficients 0 to degree, by Horner’s rule: polyeval.
  • fn libm_exp_mid_dd(idx1 : i64, idx2 : i64) -> LibmDoubleDouble
    2^(idx1 / 64) * 2^(idx2 / 4096) as a double-double, from the hi and mid parts of the two tables.
  • fn libm_exp_mid_128(idx1 : i64, idx2 : i64) -> LibmDyadic128
    2^(idx1 / 64) * 2^(idx2 / 4096) in 128 bits, from all three parts of the two tables.
  • fn libm_f64_scale(x : f64, hi : i32) -> f64
    x times 2^hi, by adding hi to the exponent field. x and the result are normal.
  • fn libm_exp_ziv_denorm(hi : i32, mid : f64, lo : f64, err0 : f64) -> LibmZivResult
    2^hi * (mid + lo) rounded to a double that may be subnormal, when err bounds the error of lo well enough to decide the rounding: ziv_test_denorm from exp_utils.h. The value is scaled up by 2^1022 and 1.0 is added, which makes it round at the same place a subnormal would.
  • fn libm_exp_exceptional(x : f64) -> f64
    exp(x) for the x the fast path leaves out: |x| <= 2^-53, an x whose result overflows or rounds to zero, infinities and NaN. set_exceptional from exp.h.
  • fn libm_exp_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
    The 128-bit pass of exp: exp_f128 from exp.h. dx = x - kd * 2^-12 * log(2) is recomputed in three parts.
  • fn libm_exp_dd(x : f64, kd : f64, exp_mid : LibmDoubleDouble) -> LibmDoubleDouble
    The double-double pass of exp: exp_double_double from exp.h.
  • fn libm_exp(x : f64) -> f64
    e raised to a double, correctly rounded: exp from exp.h. x = log(2) * (hi + mid1 + mid2) + lo, with hi an integer, mid1 a multiple of 1/64 and mid2 of 1/4096, so exp(x) = 2^hi * 2^mid1 * 2^mid2 * exp(lo). The two middle factors come from tables and exp(lo) from a short polynomial. When the error bound does not decide the rounding, the result is computed again with double-doubles, then with 128-bit floats.
  • fn libm_exp2_128(dx : f64, hi : i32, idx1 : i64, idx2 : i64) -> f64
    The 128-bit pass of exp2: exp2_f128 from exp2.h.
  • fn libm_exp2_reduce(x : f64) -> LibmExp2Reduced
    x = hi + mid1 + mid2 + dx with mid1 a multiple of 1/64, mid2 of 1/4096 and |dx| <= 2^-13, and the fast pass’s estimate 2^(mid1 + mid2) * 2^dx ~ exp_mid.hi + lo.
  • fn libm_exp2_denorm(x : f64) -> f64
    exp2(x) when the result may be subnormal, -1075 < x <= -1022: exp2_denorm from exp2.h.
  • fn libm_exp2(x : f64) -> f64
    Two raised to a double, correctly rounded: exp2 from exp2.h. The same scheme as libm_exp, without the multiplication by log2(e), so the reduction is exact.
  • fn libm_expm1_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
    The 128-bit pass of expm1: expm1_f128 from expm1.h. 2^(hi + mid) * exp(dx) - 1 is computed as (2^mid - 2^-hi) + 2^mid * dx * P(dx), then scaled by 2^hi.
  • fn libm_expm1_exceptional(x : f64) -> f64
    expm1(x) for the x the fast path leaves out: |x| <= 2^-53, x <= -37.5, an x whose result overflows, infinities and NaN. set_exceptional from expm1.h.
  • fn libm_expm1(x : f64) -> f64
    e^x - 1 for a double, correctly rounded, and accurate for a small x where exp(x) - 1 would lose every significant digit: expm1 from expm1.h. The same reduction as libm_exp; the - 1 is folded into the table value as 2^mid - 2^-hi, which is exact.
  • fn libm_dd_two_sum(a : f64, b : f64) -> LibmDoubleDouble
    a + b as a double-double, exactly, in either order of size (TwoSum): exact_add<false>.
  • fn libm_dd_exact_mult(a : f64, b : f64) -> LibmDoubleDouble
    a * b as a double-double, exactly: exact_mult with FMA.
  • fn libm_dd_div(a : LibmDoubleDouble, b : LibmDoubleDouble) -> LibmDoubleDouble
    a / b as a double-double: div from double_double.h, one correction step on a.hi / b.hi.
  • fn libm_trig64_reduce_small(x : f64) -> LibmTrig64Reduced
    Reduction for |x| < 2^16: range_reduction_small from range_reduction_double_common.h. k = round(x * 128/pi) and u = x - k pi/128, with pi/128 in three parts whose first two products with k are exact.
  • fn libm_trig64_reduce_none(x : f64) -> LibmTrig64Reduced
    x itself as u, with k = 0, for an x too small to need reducing.
  • fn libm_trig64_reduce_large(x : f64) -> LibmTrig64Reduced
    Reduction for |x| >= 2^16: LargeRangeReduction::fast from range_reduction_double_fma.h. x is split as 2^(16 (idx - 3)) * x_reduced, and x_reduced is multiplied by the row of 128/pi that starts where the integer part of the product stops mattering. The fractional part, times pi/128, is u.
  • fn libm_trig64_pi_over_128_f128() -> LibmDyadic128
    pi/128 in 128 bits.
  • fn libm_trig64_reduce_f128(x : f64, r : LibmTrig64Reduced) -> LibmDyadic128
    u in 128 bits for the accurate pass: range_reduction_small_f128 for |x| < 2^16, else LargeRangeReduction::accurate from the pieces libm_trig64_reduce_large kept.
  • fn libm_trig64_sin_k(k : i64) -> LibmDoubleDouble
    sin(k pi / 128) as a double-double, for any integer k.
  • fn libm_trig64_sin_k_f128(k : i64) -> LibmDyadic128
    sin(k pi / 128) in 128 bits, for any integer k, from the table of the first quarter turn.
  • fn libm_trig64_poly_f128(which : i32, u_sq : LibmDyadic128) -> LibmDyadic128
    The 128-bit polynomial which (0 sin(u) / u, 1 cos(u), 2 tan(u) / u) at u_sq = u^2, by Horner’s rule: polyeval.
  • fn libm_trig64_coeff_f128(which : i32, i : i64) -> LibmDyadic128
    Coefficient i of the 128-bit polynomial which of libm_trig64_poly_f128.
  • fn libm_trig64_sincos_eval(u : LibmDoubleDouble) -> LibmSinCos
  • fn libm_trig64_nan(x : f64) -> f64
    What sin, cos and tan answer for infinities and NaNs: a NaN, quiet.
  • fn libm_sin_accurate(x : f64, r : LibmTrig64Reduced) -> f64
    The 128-bit pass of sin: sin_accurate from sin.h. sin(k pi/128 + u) = sin(k pi/128) cos(u) + cos(k pi/128) sin(u).
  • fn libm_sin(x : f64) -> f64
    The sine of a double, correctly rounded: sin from sin.h. x = k pi/128 + u; the sine and cosine of k pi/128 come from a table and those of u from short polynomials. When the error bound does not decide the rounding, the result is computed again in 128 bits.
  • fn libm_cos_accurate(x : f64, r : LibmTrig64Reduced) -> f64
    The 128-bit pass of cos: cos_accurate from cos.h. cos(k pi/128 + u) = cos(k pi/128) cos(u) - sin(k pi/128) sin(u).
  • fn libm_cos(x : f64) -> f64
    The cosine of a double, correctly rounded: cos from cos.h, the same scheme as libm_sin.
  • fn libm_dyadic_div_newton(a : LibmDyadic128, b0 : LibmDyadic128, q : f64) -> LibmDyadic128
    a / b in 128 bits, from the first guess q of 1 / b: two Newton-Raphson steps, newton_raphson_div from tan.h.
  • fn libm_tan(x : f64) -> f64
    The tangent of a double, correctly rounded: tan from tan.h. x = k pi/128 + u, and tan(x) = (sin(k pi/128) + cos(k pi/128) tan(u)) / (cos(k pi/128) - sin(k pi/128) tan(u)), with tan(u) a short polynomial and the division done in double-doubles. When the error bound does not decide the rounding, the result is computed again in 128 bits.
  • fn libm_pi_over_two_f128() -> LibmDyadic128
    pi/2 and pi in 128 bits.
  • fn libm_pi_f128() -> LibmDyadic128
  • fn libm_asin_eval_dd(u : LibmDoubleDouble, err0 : f64) -> LibmAsinEval
  • fn libm_asin_coeff_f128(idx : i64, j : i64) -> LibmDyadic128
    Coefficient j of row idx of LIBM_ASIN_COEFFS_F128.
  • fn libm_asin_eval_f128(u : LibmDyadic128, idx : i64) -> LibmDyadic128
    asin(x) / x in 128 bits from row idx, at the reduced argument u: the 128-bit asin_eval, by Horner’s rule.
  • fn libm_asin_small_f128(x_sq : LibmDoubleDouble) -> LibmAsinSmall128
  • fn libm_asin_large(x_abs : f64) -> LibmAsinLarge
  • fn libm_asin_large_f128(s : LibmAsinLarge, negative : bool) -> LibmDyadic128
    2 sqrt(u) asin(sqrt(u)) / sqrt(u) in 128 bits, from the pieces of libm_asin_large, with the sign of negative.
  • fn libm_asin_nan(x : f64) -> f64
    What asin and acos answer for |x| > 1 and NaN: a quiet NaN, the input itself if it is one.
  • fn libm_asin(x : f64) -> f64
    The arcsine of a double, correctly rounded: asin from asin.h. For |x| < 1/2 a polynomial in x^2, from one of 9 tables; above that pi/2 - 2 asin(sqrt((1 - |x|) / 2)). When the error bound does not decide the rounding, the result is computed again in 128 bits.
  • fn libm_acos(x : f64) -> f64
    The arccosine of a double, correctly rounded: acos from acos.h. pi/2 - asin(x) for |x| < 1/2; above that 2 asin(sqrt((1 - |x|) / 2)), from pi for a negative x. When the error bound does not decide the rounding, the result is computed again in 128 bits.
  • fn libm_atan_eval_dd(x : LibmDoubleDouble) -> LibmDoubleDouble
    atan(x) as a double-double for a small double-double x: atan_eval from atan_utils.h, x - x^3/3 + x^5/5 - x^7/7 + x^9/9 with x.lo added to first order.
  • fn libm_atan_i(i : i64) -> LibmDoubleDouble
    atan(i / 64) as a double-double.
  • fn libm_atan(x : f64) -> f64
    The arctangent of a double: atan from atan.h. With k = round(64 |x|) / 64, atan(|x|) = atan(k) + atan((|x| - k) / (1 + |x| k)); above 1 the same on 1 / |x|, from pi/2. LLVM libc has no 128-bit pass for this one, so the last bit can be off.
  • fn libm_atan2(y : f64, x : f64) -> f64
    The angle to the point (x, y): atan2 from atan2.h. The smaller of |x| and |y| is divided by the larger, reduced as in libm_atan, and the quadrant adds 0, pi/2 or pi. Values near the ends of the exponent range are scaled first. LLVM libc has no 128-bit pass for this one, so the last bit can be off.
  • fn libm_atan2_const_adj(x_sign : bool, y_sign : bool, recip : bool) -> LibmDoubleDouble
    The constant atan2 adds for the quadrant, CONST_ADJ[x_sign][y_sign][recip]: 0, -0, pi/2, -pi/2 or -pi as double-doubles.
  • fn libm_atan2_except(y_except : i32, x_except : i32, x_sign : bool) -> LibmDoubleDouble
    What atan2 answers when either input is a zero or an infinity, before the sign of y: EXCEPTS[y_except][x_except][x_sign].
  • fn libm_hypot_exact(x : f64, y : f64) -> f64
    sqrt(x^2 + y^2) for doubles, exactly rounded with integers: the generic hypot from FPUtil/Hypot.h for double, in round-to-nearest. The squares of the two 54-bit mantissas are added as 128-bit integers, and the square root is taken one bit at a time.
  • fn libm_hypot_special(x : f64, y : f64) -> f64
    hypot when an input is an infinity or a NaN: a signaling NaN gives the quiet NaN, then an infinity gives infinity, then the NaN input itself, x first.
  • fn libm_hypot_denorm(a0 : f64, b0 : f64) -> f64
    hypot when both inputs are below 2^-1022 in size: hypot_denorm from hypot.h. Both are scaled up by 2^1022 and the double-double square root is checked with 1.0 added, so it rounds at the place a subnormal result would.
  • fn libm_hypot(x : f64, y : f64) -> f64
    sqrt(x^2 + y^2) for doubles, correctly rounded: hypot from hypot.h. The squares are added as double-doubles and the square root is corrected once, after scaling by 2^-600 or 2^600 when the inputs are very large or very small. When the error bound does not decide the rounding, libm_hypot_exact does it with integers.
  • fn libm_exp10_poly_dd(dx : LibmDoubleDouble) -> LibmDoubleDouble
    The degree-6 double-double polynomial of exp10 at dx, by Horner’s rule: polyeval.
  • fn libm_exp10_poly_128(dx : LibmDyadic128) -> LibmDyadic128
    The degree-7 128-bit polynomial of exp10 at dx, by Horner’s rule: polyeval.
  • fn libm_exp10_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
    The 128-bit pass of exp10: exp10_f128 from exp10.h.
  • fn libm_exp10_dd(x : f64, kd : f64, exp_mid : LibmDoubleDouble) -> LibmDoubleDouble
    The double-double pass of exp10: exp10_double_double from exp10.h.
  • fn libm_exp10_reduce(x : f64) -> LibmExp10Reduced
  • fn libm_exp10_denorm(x : f64) -> f64
    exp10(x) when the result may be subnormal: exp10_denorm from exp10.h.
  • fn libm_exp10(x : f64) -> f64
    Ten raised to a double, correctly rounded: exp10 from exp10.h. The same scheme as libm_exp, with log2(10) in place of log2(e); 10^1 to 10^22, which are exact, and 10^23 are answered directly when the fast pass cannot decide them.
  • fn libm_u64_trailing_zeros(a : u64) -> u64
    The number of zero bits below the lowest set bit; 64 for zero.
  • fn libm_u256(w0 : u64, w1 : u64, w2 : u64, w3 : u64) -> LibmU256
  • fn libm_u256_zero() -> LibmU256
  • fn libm_u256_is_zero(a : LibmU256) -> bool
  • fn libm_u256_word(a : LibmU256, i : i64) -> u64
    Word i of a, 0 for i outside 0 to 3.
  • fn libm_u256_add_carry(a : LibmU256, b : LibmU256) -> LibmU256Carry
  • fn libm_u256_add(a : LibmU256, b : LibmU256) -> LibmU256
    a + b, wrapping.
  • fn libm_u256_sub(a : LibmU256, b : LibmU256) -> LibmU256
    a - b, wrapping.
  • fn libm_u256_ge(a : LibmU256, b : LibmU256) -> bool
    Whether a >= b.
  • fn libm_u256_shl(a : LibmU256, s : u64) -> LibmU256
    a << s; zero once s reaches 256.
  • fn libm_u256_shr(a : LibmU256, s : u64) -> LibmU256
    a >> s; zero once s reaches 256.
  • fn libm_u256_leading_zeros(a : LibmU256) -> u64
    The number of zero bits above the highest set bit; 256 for zero.
  • fn libm_u256_bit(a : LibmU256, p : u64) -> bool
    Bit p of a, false for p past 255.
  • fn libm_u256_any_below(a : LibmU256, p : u64) -> bool
    Whether any of the bits of a below bit p is set (all of them for p past 255).
  • fn libm_mul_acc_add(m : LibmMulAcc, a : u64, b : u64) -> LibmMulAcc
    acc + a * b, counting a carry out of the two words: mul_add_with_carry.
  • fn libm_mul_acc_advance(m : LibmMulAcc) -> LibmMulAcc
    The accumulator moved down a word, with the carries as its new high word: Accumulator::advance. Its old low word is the result’s next word.
  • fn libm_u256_quick_mul_hi(a : LibmU256, b : LibmU256) -> LibmU256
    The high 256 bits of a * b, without the products below the top half and the carries out of them: UInt<256>::quick_mul_hi, at most 3 below the exact high half.
  • fn libm_u128_quick_mul_hi(a : u128, b : u128) -> u128
    The high 128 bits of a * b, without the product of the low words: UInt<128>::quick_mul_hi.
  • fn libm_frac128_at(t : i32, i : i64) -> u128
    Entry i of a table of 128-bit fractions, low word first.
  • fn libm_frac256_at(t : i32, i : i64) -> LibmU256
    Entry i of a table of 256-bit fractions: 0 LOG2_POLY_256, 1 EXP2_POLY_256, 2 EXP2_MID_FRAC256, 3 LOG2_RD_FRAC256.
  • fn libm_frac256_to_128(a : LibmU256) -> u128
    The top 128 bits of a 256-bit fraction: to_frac128.
  • fn libm_dyadic_from_u128(negative : bool, exponent : i32, m : u128) -> LibmDyadic128
    A 128-bit fraction as a dyadic float 2^exponent * m, normalized.
  • fn libm_dyadic_mantissa(d : LibmDyadic128) -> u128
    The mantissa of a 128-bit dyadic float as one integer.
  • fn libm_dyadic256_normalize(d : LibmDyadic256) -> LibmDyadic256
    d with its mantissa’s top bit moved to bit 255, and the exponent lowered to match.
  • fn libm_dyadic256_from_f64(x : f64) -> LibmDyadic256
    x exactly, normalized: the DyadicFloat(double) constructor.
  • fn libm_dyadic256_add(a0 : LibmDyadic256, b0 : LibmDyadic256) -> LibmDyadic256
    a + b: quick_add.
  • fn libm_dyadic256_mul(a : LibmDyadic256, b : LibmDyadic256) -> LibmDyadic256
    a * b: quick_mul, the mantissa from quick_mul_hi.
  • fn libm_dyadic256_to_f64(d : LibmDyadic256) -> f64
    The nearest double to d, ties to even: fast_as<double> for 256 bits. The top 53 bits make d_hi; the round and sticky bits below them are added with one FMA.
  • fn libm_pow64_is_odd_integer(x : f64) -> bool
    Whether x is an odd integer: is_odd_integer from pow_utils.h.
  • fn libm_pow64_is_integer(x : f64) -> bool
    Whether x is an integer: is_integer from pow_utils.h.
  • fn libm_pow64_overflow(negative : bool) -> f64
    Infinity or zero with the sign negative: set_overflow and set_underflow in round-to-nearest.
  • fn libm_pow64_underflow(negative : bool) -> f64
  • fn libm_pow64_ziv_denorm(hi : i32, mid : f64, lo : f64, err0 : f64, negative : bool) -> LibmZivResult
    2^hi * (mid + lo) rounded to a double that may be subnormal and of either sign, when err is small enough to decide the rounding: ziv_test_denorm from pow_utils.h. The value is scaled by 2^(hi + 1022) and 1 (or -1) is added, so it rounds at the place a subnormal would.
  • fn libm_some_f64(v : f64) -> LibmMaybeF64
  • fn libm_none_f64() -> LibmMaybeF64
  • fn libm_f64_bits_is_snan(a : u64) -> bool
    Whether the bits of a double are a signaling NaN.
  • fn libm_pow64_special(x : f64, y : f64) -> LibmMaybeF64
  • fn libm_pow64_exceptional(s0 : LibmPow64State) -> LibmPow64State
  • fn libm_pow64_exact(x : f64, y : f64) -> LibmPow64Exact
  • fn libm_pow64_exact_to_f64(negative : bool, m : u64, e : i32) -> f64
    The exact value m * 2^e with the sign negative, rounded to a double.
  • fn libm_pow64_log2_f128(x_e : i32, idx_x : i64, dx : f64) -> LibmDyadic128
    log2(x) in 128 bits from x = 2^x_e * (1 + dx) / r, with -log2(r) from row idx_x: log2_f128 from pow_accurate_128.h. log2(1 + dx) is a polynomial in fixed point, evaluated in 64 bits for its top terms and in 128 bits for the rest.
  • fn libm_pow64_exp2_f128(z : LibmDyadic128) -> LibmDyadic128
    2^z in 128 bits: exp2_f128 from pow_accurate_128.h. z = hi + idx/64 + lo; 2^(idx/64) comes from a table and 2^lo - 1 from a polynomial in fixed point.
  • fn libm_pow64_log2_f256(x_e : i32, idx_x : i64, dx : f64) -> LibmDyadic256
    log2(x) in 256 bits: log2_f256 from pow_accurate_256.h, the same scheme as libm_pow64_log2_f128 with three precisions.
  • fn libm_pow64_exp2_f256(z : LibmDyadic256) -> LibmDyadic256
    2^z in 256 bits: exp2_f256 from pow_accurate_256.h.
  • fn libm_pow64_accurate_256(y : f64, is_neg : bool, x_e : i32, idx_x : i64, dx : f64) -> f64
    The last pass of pow, in 256 bits: pow_accurate_256 from pow_accurate_256.h.
  • fn libm_pow64_accurate(x : f64, y : f64, is_neg : bool, x_e : i32, idx_x : i64, dx : f64) -> f64
    The accurate pass of pow, in 128 bits: pow_accurate from pow_accurate_128.h. When the result lands too near a rounding boundary to trust, it checks whether x^y is exactly a boundary, and otherwise redoes it in 256 bits.
  • fn libm_pow(x0 : f64, y0 : f64) -> f64
    x raised to y for doubles, correctly rounded: pow from pow.h. log2(x) is computed as a double-double from a table and a polynomial, multiplied by y, and 2^(y log2(x)) comes from a table of 2^(i/64) and a polynomial. When the error bound does not decide the rounding, the accurate passes redo it in 128 and then 256 bits, after checking for a result that lies exactly on a rounding boundary.
  • fn libm_exp_parts(a : f64) -> LibmExpParts
  • fn libm_f64_ldexp(v : f64, e : i32) -> f64
    v * 2^e for a normal v, in two steps so that a result past the largest double overflows to infinity instead of wrapping the exponent field.
  • fn libm_exp_half_sum(a : f64, sign : f64) -> f64
    (e^a + sign * e^-a) / 2 for a from 2^-6 to 711, from the double-double e^a of libm_exp_parts: 2^(hi - 1) * (m + sign * 2^(-2 hi) / m), rounded once. When 2^(-2 hi) is below 2^-120 the second term cannot change the result and is left out.
  • fn libm_odd_series_small(x : f64, c_hi : f64, c_lo : f64, rest : f64) -> f64
    x + x^3 (c + x^2 rest) for a small x, with c = c_hi + c_lo a double-double and x^3 kept as a double-double, so that only x^5 rest, which is far below the last place, is rounded on its own: the series of sinh and tanh below 1/64.
  • fn libm_sinh(x : f64) -> f64
    The hyperbolic sine of a double. Not a port: LLVM libc has no double-precision sinh. Below 1/64 in size, the Taylor series up to x^11 from libm_odd_series_small. Above that, (e^|x| - e^-|x|) / 2 from libm_exp_half_sum, whose double-double error is far below one unit in the last place. The result is almost always correctly rounded, but that is not proven.
  • fn libm_cosh(x : f64) -> f64
    The hyperbolic cosine of a double. Not a port: LLVM libc has no double-precision cosh. Below 1/64 in size, 1 + x^2 / 2 + x^4 Q(x^2); above that (e^|x| + e^-|x|) / 2 from libm_exp_half_sum, which has no cancellation. The result is almost always correctly rounded, but that is not proven.
  • fn libm_tanh(x : f64) -> f64
    The hyperbolic tangent of a double. Not a port: LLVM libc has no double-precision tanh. Below 1/64 in size, the Taylor series up to x^13 from libm_odd_series_small. Above that, (E - 1) / (E + 1) with E = e^(2 |x|) as a double-double, divided as double-doubles; from 19.06 up it rounds to 1. The result is almost always correctly rounded, but that is not proven.
  • fn libm_asinhf16(x : f16) -> f16
    The inverse hyperbolic sine of an f16, correctly rounded: asinhf16 from asinhf16.h. Below 2^-2 in size a polynomial in f32; above, log(|x| + sqrt(x^2 + 1)) with the double log_eval.
  • fn libm_acoshf16(x : f16) -> f16
    The inverse hyperbolic cosine of an f16, correctly rounded; NaN below 1: acoshf16 from acoshf16.h. Just above 1, sqrt(2 d) times a polynomial in d = x - 1; above that, log(x + sqrt(x^2 - 1)) with the double log_eval, rounded to f32 and then to f16 as LLVM does.
  • fn libm_atanhf16_log_eval(x : f32) -> f32
    log(|x|) in f32 from a table and a degree-7 polynomial: log_eval_f from atanhf16.h. The sign of x is not read.
  • fn libm_atanhf16(x : f16) -> f16
    The inverse hyperbolic tangent of an f16, correctly rounded; infinite at 1 and NaN past it: atanhf16 from atanhf16.h. Below 0.25 in size a polynomial in f32; above, log((1 + x) / (1 - x)) / 2.
  • fn libm_dd_add_any(a : LibmDoubleDouble, b : LibmDoubleDouble) -> LibmDoubleDouble
    a + b for double-doubles of either order of size: TwoSum on the high parts.
  • fn libm_dd_sqrt(a : LibmDoubleDouble) -> LibmDoubleDouble
    The square root of a double-double, as a double-double: the double root corrected once.
  • fn libm_log_dd(a : LibmDoubleDouble) -> LibmDoubleDouble
    The natural logarithm of a positive normal double-double, as a double-double, with a relative error far below a unit in the last place of a double. It uses the reduction of libm_log: x = 2^e * m, log(m) = -log(r) + log(1 + u) with r from RD and u = r * m - 1 exact. log(1 + u) is u - u^2 / 2 with u^2 exact, plus u^3 (1/3 + u R(u)) with u^3 and 1/3 as double-doubles; the low part of a adds a.lo / a.hi.
  • fn libm_log2_dd() -> LibmDoubleDouble
    log(2) as a double-double.
  • fn libm_asinh(x : f64) -> f64
    The inverse hyperbolic sine of a double. Not a port: LLVM libc has no double-precision asinh. Below 1/64 in size, its Taylor series up to x^11 from libm_odd_series_small; above, log(|x| + sqrt(x^2 + 1)) with every step a double-double, and from 2^40 up log(|x|) + log(2). The result is almost always correctly rounded, but that is not proven.
  • fn libm_acosh(x : f64) -> f64
    The inverse hyperbolic cosine of a double; NaN below 1. Not a port: LLVM libc has no double-precision acosh. log(x + sqrt(x^2 - 1)) with every step a double-double (x^2 - 1 is exact before the square root), and from 2^40 up log(x) + log(2). The result is almost always correctly rounded, but that is not proven.
  • fn libm_atanh(x : f64) -> f64
    The inverse hyperbolic tangent of a double; infinite at 1 and NaN past it. Not a port: LLVM libc has no double-precision atanh. Below 1/64 in size, its Taylor series up to x^13 from libm_odd_series_small; above, log((1 + |x|) / (1 - |x|)) / 2 with the ratio and the log as double-doubles. The result is almost always correctly rounded, but that is not proven.
  • fn libm_asinh_bf16(x : bf16) -> bf16
    The inverse hyperbolic functions of a bf16: the double result rounded once, to odd in f32 and then to bf16, which gives the same bf16 as rounding the double directly.
  • fn libm_acosh_bf16(x : bf16) -> bf16
  • fn libm_atanh_bf16(x : bf16) -> bf16
  • fn libm_cbrtf(x : f32) -> f32
    The cube root of an f32, correctly rounded: cbrtf from cbrtf.h. x = 2^(3e + s) * m, and the cube root of m comes from a polynomial for one of 16 slices of [1, 2), times 2^(s/3), computed in double. A double result too near a rounding boundary of f32 is an exact cube root, and is moved onto it.
  • fn libm_cbrtf16(x : f16) -> f16
    The cube root of an f16, correctly rounded: cbrtf16 from cbrtf16.h, the scheme of libm_cbrtf in f32. LLVM answers a signaling NaN with the number 32256 (it converts the bits of the quiet NaN as an integer); this answers the quiet NaN.
  • fn libm_cbrt_bf16(x : bf16) -> bf16
    The cube root of a bf16: LLVM libc’s cbrtbf16, cbrtf of it rounded to bf16.
  • fn libm_cbrt_error(x_3 : LibmDoubleDouble, a_sq : LibmDoubleDouble) -> f64
    x0^3 * a^2 - 1 from the double-doubles x0^3 and a^2: get_error from cbrt.h with FMA.
  • fn libm_cbrt(x : f64) -> f64
    The cube root of a double, correctly rounded: cbrt from cbrt.h. x = 2^(3e + s) * a'; a polynomial gives x0 ~ a^(-1/3), and Newton steps on x^3 a^2 = 1 refine it, so that a * x ~ a^(1/3). One step in double-doubles usually decides the rounding; otherwise a second, and then a third in 128 bits, where a result on or next to a rounding boundary is an exact cube root and is moved onto it.
  • fn libm_cbrt_value(x0_in : f64, always_accurate : bool) -> f64
    libm_cbrt, or with always_accurate its third step on every input, skipping the two checks that would stop earlier: the fixtures use it to reach that step, which no known input needs.
  • fn libm_cbrt_update_exponent(r : f64, out_e : u64) -> f64
    r, which lies in [1, 2) in size, with its exponent replaced by out_e: update_exponent from cbrt.h.
  • fn libm_erf_eval(x : f32) -> f64
    erf(x) in double for a finite |x| below 4: the polynomial of erff.h for the slice |x| is in.
  • fn libm_erff(x : f32) -> f32
    The error function of an f32, correctly rounded: erff from erff.h. From 4 up in size it rounds to 1; below, a polynomial for one of 32 slices, in double, with two inputs whose double result would round the wrong way answered from a list.
  • fn libm_erff16(x : f16) -> f16
    The error function of an f16, correctly rounded: erff16 from erff16.h, the polynomial of erff in f32. From 3 up in size it rounds to +-1.
  • fn libm_erf_bf16(x : bf16) -> bf16
    The error function of a bf16: the double result of erff’s polynomial rounded once to bf16. LLVM libc has no bf16 erf.
  • fn libm_rsqrt_special(x_u : u64, sign_bit : u64, inf : u64, quiet : u64) -> LibmZivResult
    What rsqrt answers for zero, a negative number, infinity and NaN, as the bits of a float with exponent mask inf and quiet bit quiet, or no answer (ok false) for a positive finite x.
  • fn libm_rsqrtf(x : f32) -> f32
    1 / sqrt(x) for an f32: rsqrtf from rsqrtf.h, the double result rounded to f32.
  • fn libm_rsqrtf16(x : f16) -> f16
    1 / sqrt(x) for an f16, correctly rounded: rsqrtf16 from rsqrtf16.h, in f32, with two mantissas whose f32 result would round the wrong way nudged first.
  • fn libm_rsqrt(x : f64) -> f64
    1 / sqrt(x) for a double, correctly rounded. Not a port: LLVM libc has no double-precision rsqrt. The square root as a double-double, then one double-double division, rounded once; 1 / sqrt(x) is never exactly halfway between two doubles unless it is a double, so that rounding is right.
  • fn libm_rsqrt_bf16(x : bf16) -> bf16
    1 / sqrt(x) for a bf16: the double result rounded once to bf16. LLVM libc has no bf16 rsqrt.

core::marker

The traits that say something about a type without giving it a method.

Types

  • trait Copy
    A type that is duplicated rather than moved when it is assigned. Opt-in: a type is copyable only when it says so, and only when every field already is. That is what keeps it away from placement – a tensor or a placed buffer cannot declare it, so they stay linear without a special case, and duplicating placed data stays an explicit transfer. Enforced for a struct and for a payload-free enum. A generic enum is not treated as linear at all, so Option and Result survive a move whether or not they declare this (Vx#715).
  • trait Send
    A type whose values may move into another task. The compiler derives it: a struct or an enum is Send when each of its fields is. A raw pointer and a closure type are not. A type that holds a raw pointer and is safe to move writes unsafe impl Send for T {}, as Vec does.
  • trait Sync
    A type that two tasks may use through &T at the same time. The compiler derives it as it derives Send. SharedCell is never Sync, because code can write its value through &. An atomic holds a SharedCell and opts in with unsafe impl.
  • trait Sized
    A type whose size is known at compile time, which every Vx type is. The bound exists to be written; nothing is excluded by it.
  • struct PhantomData<T>
    A field that records a type without storing a value of it. An empty struct occupies nothing, so a struct carrying one is no larger. It is how a type parameter that appears in no field is still named by the type.

core::mem

Moving values around without looking at what they are.

Types

  • struct ManuallyDrop<T>
    Holds a value and never drops it. Its value can still be read, and moved out.

Functions

  • fn size_of<T>() -> i64
    The size of T in bytes.
  • fn swap<T>(a : &mut T, b : &mut T) -> void
    Each value ends up where the other one was. Through raw pointers, as Rust’s does: *a cannot move a value out from behind a reference, and nothing is dropped, since each value is moved, not copied.
  • fn replace<T>(dest : &mut T, src : T) -> T
    src goes in, and what was there comes back. Through a raw pointer, as swap.
  • fn drop<T>(_x : T) -> void
    Drops the value now, rather than at the end of the caller’s block.
  • fn forget<T>(x : T) -> void
    Ends the value’s life without dropping it: what it owns is never released.
  • fn needs_drop<T>() -> bool
    Whether dropping a T does any work: runs a drop, or frees memory. The checker answers each call with the type it names, so this body is never used.

core::num::float_to_decimal

Types

  • struct FloatDecimal
    A finite float as digits * 10^exponent, or a NaN or an infinity. Zero has digits == 0. negative is the sign bit, so it is set for -0.0 and for a NaN with its sign bit set.

Functions

  • fn ryu_pow5_bits(e : i64) -> i64
    ceil(log2(5^e)), and 1 for e = 0, for 0 <= e <= 3528: pow5bits.
  • fn ryu_log10_pow2(e : i64) -> i64
    floor(log10(2^e)) for 0 <= e <= 1650: log10Pow2.
  • fn ryu_log10_pow5(e : i64) -> i64
    floor(log10(5^e)) for 0 <= e <= 2620: log10Pow5.
  • fn ryu_offset_bits(word : u64, i : i64) -> u128
    The two-bit correction for power i from an offsets table entry.
  • fn ryu_pow5(i : i64) -> u128
    5^i in the 125-bit form Ryu multiplies by: double_computePow5.
  • fn ryu_inv_pow5(i : i64) -> u128
    2^k / 5^i, rounded up, in the form Ryu multiplies by: double_computeInvPow5.
  • fn ryu_mul_shift64(m : u64, mul : u128, j : i64) -> u64
    (m * mul) >> j, for j of at least 64: mulShift64.
  • fn ryu_mul_shift32(m : u64, factor : u64, shift : i64) -> u64
    (m * factor) >> shift for a 32-bit m and 32 < shift < 96: mulShift32.
  • fn ryu_pow5_factor(value : u64) -> i64
    How many times 5 divides value, which must not be zero: pow5Factor.
  • fn ryu_digit_count(v : u64) -> i64
    The number of decimal digits in v; 1 for zero.
  • fn ryu_trimmed(negative : bool, digits : u64, exponent : i64) -> FloatDecimal
    digits * 10^exponent with any trailing zeros of digits moved into the exponent.
  • fn ryu_f64_digits(negative : bool, ieee_mantissa : u64, ieee_exponent : u64) -> FloatDecimal
    The shortest digits of a finite, nonzero f64 given its mantissa and exponent fields: d2d.
  • fn float_decimal_from_f64_bits(bits : u64) -> FloatDecimal
    The value bits holds as an f64, with its shortest digits when it is finite.
  • fn ryu_mul_pow5_inv_div_pow2(m : u64, q : i64, j : i64) -> u64
    (m * 2^k / 5^q) >> j for f32: mulPow5InvDivPow2 over the double tables.
  • fn ryu_mul_pow5_div_pow2(m : u64, i : i64, j : i64) -> u64
    (m * 5^i) >> j for f32: mulPow5divPow2 over the double tables.
  • fn ryu_f32_digits(negative : bool, ieee_mantissa : u64, ieee_exponent : u64) -> FloatDecimal
    The shortest digits of a finite, nonzero f32 given its mantissa and exponent fields: f2d.
  • fn float_decimal_from_f32_bits(bits : u32) -> FloatDecimal
    The value bits holds as an f32, with its shortest digits when it is finite.
  • unsafe fn ryu_write_digits(digits : u64, n : i64, point : i64, buf : *mut i8, at : i64) -> i64
    Write the n digits of digits to buf[at..], with a . after the first point of them when 0 < point < n. Returns how many bytes that took.
  • unsafe fn ryu_write_zeros(count : i64, buf : *mut i8, at : i64) -> i64
    Write count zeros to buf[at..].
  • unsafe fn ryu_write_special(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
    Write NaN, inf or -inf to buf[at..] and return how many bytes that took.
  • fn float_decimal_display_len(d : FloatDecimal) -> i64
    How many bytes Rust’s {} takes for d.
  • unsafe fn float_decimal_write_display(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
    Write what Rust’s {} prints for d to buf[at..] and return how many bytes that took: plain decimal notation, never an exponent, and no .0 on a whole number. Unsafe because nothing checks that the buffer has float_decimal_display_len(d) bytes.
  • fn float_decimal_scientific_len(d : FloatDecimal) -> i64
    How many bytes Rust’s {:e} takes for d.
  • unsafe fn float_decimal_write_scientific(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
    Write what Rust’s {:e} prints for d to buf[at..] and return how many bytes that took: one digit, the rest after a . if there are any, then e and the exponent, as in 1.5e-7. Unsafe because nothing checks that the buffer has float_decimal_scientific_len(d) bytes.

core::num

The integer and float methods, stamped over every width.

Types

  • enum FpCategory
    The five kinds of float value classify tells apart.

usize methods

  • fn min_value(self : usize) -> usize
    Zero.
  • fn max_value(self : usize) -> usize
    The largest usize, which is the largest i64.
  • fn bits(self : usize) -> usize
    64, the width in memory. The top bit is always clear.
  • fn count_ones(self : usize) -> usize
    How many bits are set.
  • fn count_zeros(self : usize) -> usize
    How many of the 64 bits are clear. At least one, the top bit.
  • fn leading_zeros(self : usize) -> usize
    Zero bits above the highest set bit. At least one, and all 64 for zero.
  • fn trailing_zeros(self : usize) -> usize
    Zero bits below the lowest set bit. All 64, for zero.
  • fn trailing_ones(self : usize) -> usize
    Set bits below the lowest clear bit. ! is refused on a usize, so the bits are flipped in MLIR.
  • fn is_power_of_two(self : usize) -> bool
    Zero is not a power of two.
  • fn abs_diff(self : usize, other : usize) -> usize
    The distance between two values, which always fits.
  • fn pow(self : usize, exp : usize) -> usize
    This raised to exp. Traps if the answer does not fit, as * does.
  • fn div_euclid(self : usize, rhs : usize) -> usize
    Plain division: an unsigned quotient is already the Euclidean one.
  • fn rem_euclid(self : usize, rhs : usize) -> usize
    Plain remainder, which is never negative.
  • fn checked_pow(self : usize, exp : usize) -> Option<usize>
    This raised to exp, or nothing if the answer does not fit.
  • fn saturating_pow(self : usize, exp : usize) -> usize
    This raised to exp, held at the largest value if it does not fit.
  • fn wrapping_pow(self : usize, exp : usize) -> usize
    This raised to exp, wrapping at the top of the range.
  • fn ilog(self : usize, base : usize) -> usize
    The logarithm to base, rounded down. Refused at zero, and for a base below two.
  • fn ilog2(self : usize) -> usize
    The logarithm to base two, rounded down. Refused at zero.
  • fn ilog10(self : usize) -> usize
    The logarithm to base ten, rounded down. Refused at zero.
  • fn checked_ilog(self : usize, base : usize) -> Option<usize>
    The logarithm to base, or nothing at zero or for a base below two.
  • fn checked_ilog2(self : usize) -> Option<usize>
    The logarithm to base two, or nothing at zero.
  • fn checked_ilog10(self : usize) -> Option<usize>
    The logarithm to base ten, or nothing at zero.
  • fn is_multiple_of(self : usize, rhs : usize) -> bool
    Does rhs divide this exactly? Zero is a multiple only of zero.
  • fn next_multiple_of(self : usize, rhs : usize) -> usize
    The smallest multiple of rhs at or above this. Refused on a zero rhs, and when the answer does not fit.
  • fn checked_next_multiple_of(self : usize, rhs : usize) -> Option<usize>
    The smallest multiple of rhs at or above this, or nothing on a zero rhs or when the answer does not fit.
  • fn checked_next_power_of_two(self : usize) -> Option<usize>
    The smallest power of two at or above this, or nothing above 2^62, the largest power of two a usize holds.
  • fn next_power_of_two(self : usize) -> usize
    One for zero and one. Refused above 2^62, the largest power of two a usize holds.
  • fn isqrt(self : usize) -> usize
    The square root, rounded down, digit by digit in base four.
  • fn midpoint(self : usize, rhs : usize) -> usize
    Halfway between two values, rounded down, without overflowing.
  • fn div_ceil(self : usize, rhs : usize) -> usize
    The quotient rounded up. Refused on a zero divisor.
  • fn checked_add(self : usize, rhs : usize) -> Option<usize>
    The sum, or nothing if it would pass the largest usize.
  • fn checked_sub(self : usize, rhs : usize) -> Option<usize>
    The difference, or nothing if it would go below zero.
  • fn checked_mul(self : usize, rhs : usize) -> Option<usize>
    The product, or nothing if it would pass the largest usize. Checked by dividing first, since multiplying first would trap.
  • fn checked_div(self : usize, rhs : usize) -> Option<usize>
    Nothing on division by zero, the only division that fails here.
  • fn checked_rem(self : usize, rhs : usize) -> Option<usize>
    Nothing on a zero divisor, as checked_div.
  • fn saturating_add(self : usize, rhs : usize) -> usize
    Held at the largest usize instead of trapping.
  • fn saturating_sub(self : usize, rhs : usize) -> usize
    Held at zero instead of trapping.
  • fn saturating_mul(self : usize, rhs : usize) -> usize
    Held at the largest usize instead of trapping.
  • fn wrapping_add(self : usize, rhs : usize) -> usize
    The sum modulo 2^63: past the largest usize it starts again from zero.
  • fn wrapping_sub(self : usize, rhs : usize) -> usize
    The difference modulo 2^63: below zero it starts again from the largest usize.
  • fn wrapping_mul(self : usize, rhs : usize) -> usize
    The product modulo 2^63.
  • fn wrapping_neg(self : usize) -> usize
    Zero minus this, modulo 2^63.
  • fn overflowing_add(self : usize, rhs : usize) -> (usize, bool)
    The sum modulo 2^63, and whether it passed the largest usize.
  • fn overflowing_sub(self : usize, rhs : usize) -> (usize, bool)
    The difference modulo 2^63, and whether it went below zero.
  • fn overflowing_mul(self : usize, rhs : usize) -> (usize, bool)
    The product modulo 2^63, and whether it passed the largest usize.
  • fn overflowing_neg(self : usize) -> (usize, bool)
    Zero minus this, modulo 2^63, and whether it wrapped: every value but zero does.
  • fn overflowing_div(self : usize, rhs : usize) -> (usize, bool)
    The quotient, which never overflows.
  • fn overflowing_rem(self : usize, rhs : usize) -> (usize, bool)
    The remainder, which never overflows.

Functions

  • fn decimal_digit_pairs() -> *const i8
    “00” to “99” back to back, so the pair for k starts at byte 2 * k.

f64 methods

  • fn rsqrt(self : f64) -> f64
    One over the square root, correctly rounded, from a double-double square root (LLVM libc has no double-precision rsqrt). Infinite at zero and NaN below it.
  • fn cbrt(self : f64) -> f64
    The cube root, correctly rounded (LLVM libc’s cbrt). Defined for negative numbers too.
  • fn asinh(self : f64) -> f64
    The inverse hyperbolic sine, almost always correctly rounded, from core::libm’s own double-double code (LLVM libc has no double-precision version).
  • fn acosh(self : f64) -> f64
    The inverse hyperbolic cosine; NaN below 1, almost always correctly rounded, from core::libm’s own double-double code (LLVM libc has no double-precision version).
  • fn atanh(self : f64) -> f64
    The inverse hyperbolic tangent; infinite at 1 and NaN past it, almost always correctly rounded, from core::libm’s own double-double code (LLVM libc has no double-precision version).
  • fn ln(self : f64) -> f64
    The natural logarithm, correctly rounded (LLVM libc’s log).
  • fn log2(self : f64) -> f64
    The logarithm to base two, correctly rounded (LLVM libc’s log2).
  • fn log10(self : f64) -> f64
    The logarithm to base ten, correctly rounded (LLVM libc’s log10).
  • fn ln_1p(self : f64) -> f64
    ln of one plus this, correctly rounded and accurate for a small argument (LLVM libc’s log1p).
  • fn exp(self : f64) -> f64
    e raised to this, correctly rounded (LLVM libc’s exp).
  • fn exp2(self : f64) -> f64
    Two raised to this, correctly rounded (LLVM libc’s exp2).
  • fn exp_m1(self : f64) -> f64
    exp minus one, correctly rounded, so a small argument keeps its digits (LLVM libc’s expm1).
  • fn sin(self : f64) -> f64
    The sine of this many radians, correctly rounded (LLVM libc’s sin).
  • fn cos(self : f64) -> f64
    The cosine of this many radians, correctly rounded (LLVM libc’s cos).
  • fn tan(self : f64) -> f64
    The tangent of this many radians, correctly rounded (LLVM libc’s tan).
  • fn asin(self : f64) -> f64
    The angle in radians whose sine is this, between -pi/2 and pi/2, correctly rounded (LLVM libc’s asin).
  • fn acos(self : f64) -> f64
    The angle in radians whose cosine is this, between 0 and pi, correctly rounded (LLVM libc’s acos).
  • fn atan(self : f64) -> f64
    The angle in radians whose tangent is this. atan2 is the form that keeps the quadrant. LLVM libc’s atan, which can be off in the last bit.
  • fn atan2(self : f64, x : f64) -> f64
    The angle to the point (x, this), which is atan with the quadrant kept. LLVM libc’s atan2, which can be off in the last bit.
  • fn hypot(self : f64, other : f64) -> f64
    The length of the hypotenuse, the square root of self * self + other * other, correctly rounded and without overflow or underflow in the squares (LLVM libc’s hypot).
  • fn powf(self : f64, n : f64) -> f64
    This raised to n, correctly rounded (LLVM libc’s pow).
  • fn sinh(self : f64) -> f64
    The hyperbolic sine, from core::libm’s exp (LLVM libc has no double-precision sinh): almost always correctly rounded, and within one unit in the last place.
  • fn cosh(self : f64) -> f64
    The hyperbolic cosine, from core::libm’s exp (LLVM libc has no double-precision cosh): almost always correctly rounded, and within one unit in the last place.
  • fn tanh(self : f64) -> f64
    The hyperbolic tangent, from core::libm’s exp (LLVM libc has no double-precision tanh): almost always correctly rounded, and within one unit in the last place.
  • fn max_value() -> f64
    The largest finite value.
  • fn min_value() -> f64
    The most negative finite value.
  • fn epsilon() -> f64
    The gap between 1.0 and the next larger value.
  • fn min_positive() -> f64
    The smallest positive normal value.
  • fn infinity() -> f64
    Positive infinity.
  • fn neg_infinity() -> f64
    Negative infinity.
  • fn nan() -> f64
    A quiet NaN.

f32 methods

  • fn erf(self : f32) -> f32
    The error function, correctly rounded (LLVM libc’s erff).
  • fn rsqrt(self : f32) -> f32
    One over the square root (LLVM libc’s rsqrtf, the double result rounded to f32). Infinite at zero and NaN below it.
  • fn cbrt(self : f32) -> f32
    The cube root, correctly rounded (LLVM libc’s cbrtf). Defined for negative numbers too.
  • fn asinh(self : f32) -> f32
    The inverse hyperbolic sine, correctly rounded (LLVM libc’s asinhf).
  • fn acosh(self : f32) -> f32
    The inverse hyperbolic cosine, correctly rounded; NaN below one (LLVM libc’s acoshf).
  • fn atanh(self : f32) -> f32
    The inverse hyperbolic tangent, correctly rounded; infinite at plus or minus one and NaN beyond (LLVM libc’s atanhf).
  • fn ln(self : f32) -> f32
    The natural logarithm, correctly rounded (LLVM libc’s logf).
  • fn log2(self : f32) -> f32
    The logarithm to base two, correctly rounded (LLVM libc’s log2f).
  • fn log10(self : f32) -> f32
    The logarithm to base ten, correctly rounded (LLVM libc’s log10f).
  • fn ln_1p(self : f32) -> f32
    ln of one plus this, correctly rounded (LLVM libc’s log1pf).
  • fn exp(self : f32) -> f32
    e raised to this, correctly rounded (LLVM libc’s expf).
  • fn exp2(self : f32) -> f32
    Two raised to this, correctly rounded (LLVM libc’s exp2f).
  • fn exp_m1(self : f32) -> f32
    exp minus one, correctly rounded, so a small argument keeps its digits (LLVM libc’s expm1f).
  • fn sinh(self : f32) -> f32
    The hyperbolic sine, correctly rounded (LLVM libc’s sinhf).
  • fn cosh(self : f32) -> f32
    The hyperbolic cosine, correctly rounded (LLVM libc’s coshf).
  • fn tanh(self : f32) -> f32
    The hyperbolic tangent, correctly rounded (LLVM libc’s tanhf).
  • fn sin(self : f32) -> f32
    The sine of this many radians, correctly rounded (LLVM libc’s sinf).
  • fn cos(self : f32) -> f32
    The cosine of this many radians, correctly rounded (LLVM libc’s cosf).
  • fn tan(self : f32) -> f32
    The tangent of this many radians, correctly rounded (LLVM libc’s tanf).
  • fn asin(self : f32) -> f32
    The arcsine, between -pi/2 and pi/2, correctly rounded (LLVM libc’s asinf).
  • fn acos(self : f32) -> f32
    The arccosine, between 0 and pi, correctly rounded (LLVM libc’s acosf).
  • fn atan(self : f32) -> f32
    The arctangent, between -pi/2 and pi/2, correctly rounded (LLVM libc’s atanf).
  • fn atan2(self : f32, x : f32) -> f32
    The angle to the point (x, this), which is atan with the quadrant kept; correctly rounded (LLVM libc’s atan2f).
  • fn hypot(self : f32, other : f32) -> f32
    The length of the hypotenuse, the square root of self * self + other * other, without overflow or underflow in between; correctly rounded (LLVM libc’s hypotf).
  • fn powf(self : f32, n : f32) -> f32
    This raised to n, correctly rounded (LLVM libc’s powf).
  • fn max_value() -> f32
    The largest finite value.
  • fn min_value() -> f32
    The most negative finite value.
  • fn epsilon() -> f32
    The gap between 1.0 and the next larger value.
  • fn min_positive() -> f32
    The smallest positive normal value.
  • fn infinity() -> f32
    Positive infinity.
  • fn neg_infinity() -> f32
    Negative infinity.
  • fn nan() -> f32
    A quiet NaN.

f16 methods

  • fn erf(self : f16) -> f16
    The error function, correctly rounded (LLVM libc’s erff16).
  • fn rsqrt(self : f16) -> f16
    One over the square root, correctly rounded (LLVM libc’s rsqrtf16). Infinite at zero and NaN below it.
  • fn cbrt(self : f16) -> f16
    The cube root, correctly rounded (LLVM libc’s cbrtf16). Defined for negative numbers too.
  • fn asinh(self : f16) -> f16
    The inverse hyperbolic sine, correctly rounded (LLVM libc’s asinhf16).
  • fn acosh(self : f16) -> f16
    The inverse hyperbolic cosine; NaN below 1, correctly rounded (LLVM libc’s acoshf16).
  • fn atanh(self : f16) -> f16
    The inverse hyperbolic tangent; infinite at 1 and NaN past it, correctly rounded (LLVM libc’s atanhf16).
  • fn ln(self : f16) -> f16
    The natural logarithm, correctly rounded (LLVM libc’s logf16).
  • fn log2(self : f16) -> f16
    The logarithm to base two, correctly rounded (LLVM libc’s log2f16).
  • fn log10(self : f16) -> f16
    The logarithm to base ten, correctly rounded (LLVM libc’s log10f16).
  • fn ln_1p(self : f16) -> f16
    ln of one plus this, correctly rounded (through LLVM libc’s log1pf).
  • fn exp(self : f16) -> f16
    e raised to this, correctly rounded (through LLVM libc’s expf).
  • fn exp2(self : f16) -> f16
    Two raised to this, correctly rounded (through LLVM libc’s exp2f).
  • fn exp_m1(self : f16) -> f16
    exp minus one, correctly rounded, so a small argument keeps its digits (through LLVM libc’s expm1f).
  • fn sinh(self : f16) -> f16
    The hyperbolic sine, correctly rounded (through LLVM libc’s sinhf).
  • fn cosh(self : f16) -> f16
    The hyperbolic cosine, correctly rounded (through LLVM libc’s coshf).
  • fn tanh(self : f16) -> f16
    The hyperbolic tangent, correctly rounded (through LLVM libc’s tanhf).
  • fn sin(self : f16) -> f16
    The sine of this many radians, correctly rounded (through LLVM libc’s sinf).
  • fn cos(self : f16) -> f16
    The cosine of this many radians, correctly rounded (through LLVM libc’s cosf).
  • fn tan(self : f16) -> f16
    The tangent of this many radians, correctly rounded (through LLVM libc’s tanf).
  • fn asin(self : f16) -> f16
    The arcsine, between -pi/2 and pi/2, correctly rounded (through LLVM libc’s asinf).
  • fn acos(self : f16) -> f16
    The arccosine, between 0 and pi, correctly rounded (through LLVM libc’s acosf).
  • fn atan(self : f16) -> f16
    The arctangent, between -pi/2 and pi/2, correctly rounded (through LLVM libc’s atanf).
  • fn atan2(self : f16, x : f16) -> f16
    The angle to the point (x, this), which is atan with the quadrant kept; correctly rounded (through LLVM libc’s atan2f).
  • fn hypot(self : f16, other : f16) -> f16
    The length of the hypotenuse, the square root of self * self + other * other, without overflow or underflow in between; correctly rounded (in double, rounded once).
  • fn powf(self : f16, n : f16) -> f16
    This raised to n, correctly rounded.
  • fn max_value() -> f16
    The largest finite value.
  • fn min_value() -> f16
    The most negative finite value.
  • fn epsilon() -> f16
    The gap between 1.0 and the next larger value.
  • fn min_positive() -> f16
    The smallest positive normal value.
  • fn infinity() -> f16
    Positive infinity.
  • fn neg_infinity() -> f16
    Negative infinity.
  • fn nan() -> f16
    A quiet NaN.

bf16 methods

  • fn erf(self : bf16) -> bf16
    The error function: erff’s double result rounded once to bf16 (LLVM libc has no bf16 erf).
  • fn rsqrt(self : bf16) -> bf16
    One over the square root: the double result rounded once to bf16. Infinite at zero and NaN below it.
  • fn cbrt(self : bf16) -> bf16
    The cube root: LLVM libc’s cbrtbf16, the f32 cube root rounded to bf16. Defined for negative numbers too.
  • fn asinh(self : bf16) -> bf16
    The inverse hyperbolic sine, the double result rounded once to bf16.
  • fn acosh(self : bf16) -> bf16
    The inverse hyperbolic cosine; NaN below 1, the double result rounded once to bf16.
  • fn atanh(self : bf16) -> bf16
    The inverse hyperbolic tangent; infinite at 1 and NaN past it, the double result rounded once to bf16.
  • fn ln(self : bf16) -> bf16
    The natural logarithm, correctly rounded (LLVM libc’s log_bf16).
  • fn log2(self : bf16) -> bf16
    The logarithm to base two, correctly rounded (through LLVM libc’s log2f).
  • fn log10(self : bf16) -> bf16
    The logarithm to base ten, correctly rounded (through LLVM libc’s log10f).
  • fn ln_1p(self : bf16) -> bf16
    ln of one plus this, correctly rounded (through LLVM libc’s log1pf).
  • fn exp(self : bf16) -> bf16
    e raised to this, correctly rounded (through LLVM libc’s expf).
  • fn exp2(self : bf16) -> bf16
    Two raised to this, correctly rounded (through LLVM libc’s exp2f).
  • fn exp_m1(self : bf16) -> bf16
    exp minus one, correctly rounded, so a small argument keeps its digits (through LLVM libc’s expm1f).
  • fn sinh(self : bf16) -> bf16
    The hyperbolic sine, correctly rounded (through LLVM libc’s sinhf).
  • fn cosh(self : bf16) -> bf16
    The hyperbolic cosine, correctly rounded (through LLVM libc’s coshf).
  • fn tanh(self : bf16) -> bf16
    The hyperbolic tangent, correctly rounded (through LLVM libc’s tanhf).
  • fn sin(self : bf16) -> bf16
    The sine of this many radians, correctly rounded (through LLVM libc’s sinf).
  • fn cos(self : bf16) -> bf16
    The cosine of this many radians, correctly rounded (through LLVM libc’s cosf).
  • fn tan(self : bf16) -> bf16
    The tangent of this many radians, correctly rounded (through LLVM libc’s tanf).
  • fn asin(self : bf16) -> bf16
    The arcsine, between -pi/2 and pi/2, correctly rounded (through LLVM libc’s asinf).
  • fn acos(self : bf16) -> bf16
    The arccosine, between 0 and pi, correctly rounded (through LLVM libc’s acosf).
  • fn atan(self : bf16) -> bf16
    The arctangent, between -pi/2 and pi/2, correctly rounded (through LLVM libc’s atanf).
  • fn atan2(self : bf16, x : bf16) -> bf16
    The angle to the point (x, this), which is atan with the quadrant kept; correctly rounded (through LLVM libc’s atan2f).
  • fn hypot(self : bf16, other : bf16) -> bf16
    The length of the hypotenuse, the square root of self * self + other * other, without overflow or underflow in between; correctly rounded (in double, rounded once).
  • fn powf(self : bf16, n : bf16) -> bf16
    This raised to n, correctly rounded.
  • fn max_value() -> bf16
    The largest finite value.
  • fn min_value() -> bf16
    The most negative finite value.
  • fn epsilon() -> bf16
    The gap between 1.0 and the next larger value.
  • fn min_positive() -> bf16
    The smallest positive normal value.
  • fn infinity() -> bf16
    Positive infinity.
  • fn neg_infinity() -> bf16
    Negative infinity.
  • fn nan() -> bf16
    A quiet NaN.

T methods, stamped for 4 instantiations

  • fn min_value(self : T) -> T
    The smallest value of this width.
  • fn max_value(self : T) -> T
    The largest value of this width.
  • fn bits(self : T) -> T
    How many bits this width has.
  • fn count_ones(self : T) -> T
    How many bits are set. The hardware instruction, so a negative operand is counted right; a loop shifting right would copy the sign bit forever.
  • fn count_zeros(self : T) -> T
    How many bits are clear.
  • fn leading_zeros(self : T) -> T
    Zero bits above the highest set bit. All of them, for zero.
  • fn trailing_zeros(self : T) -> T
    Zero bits below the lowest set bit. All of them, for zero.
  • fn is_power_of_two(self : T) -> bool
    Zero and the negatives are not.
  • fn is_positive(self : T) -> bool
    Is this greater than zero? Zero is neither positive nor negative.
  • fn is_negative(self : T) -> bool
    Is this less than zero?
  • fn abs(self : T) -> T
    Refused at the smallest value, which has no positive counterpart.
  • fn signum(self : T) -> T
    -1, 0 or 1, by sign.
  • fn abs_diff(self : T, other : T) -> T
    The distance between two values.
  • fn pow(self : T, exp : T) -> T
    By squaring. Overflow wraps, as every arithmetic operator here does.
  • fn rem_euclid(self : T, rhs : T) -> T
    Never negative, whatever the signs: -7 % 4 is -3 where this is 1.
  • fn div_euclid(self : T, rhs : T) -> T
    The quotient pairing with rem_euclid.
  • fn checked_pow(self : T, exp : T) -> Option<T>
    This raised to exp, or nothing if the answer does not fit.
  • fn wrapping_pow(self : T, exp : T) -> T
    This raised to exp, wrapping round at the width. The same as pow, under the name that says so.
  • fn ilog(self : T, base : T) -> T
    The logarithm to base, rounded down. Refused at zero and below, and for a base below two.
  • fn ilog10(self : T) -> T
    The logarithm to base ten, rounded down. Refused at zero and below.
  • fn checked_ilog(self : T, base : T) -> Option<T>
    The logarithm to base, or nothing at zero and below or for a base below two.
  • fn checked_ilog2(self : T) -> Option<T>
    The logarithm to base two, or nothing at zero and below.
  • fn checked_ilog10(self : T) -> Option<T>
    The logarithm to base ten, or nothing at zero and below.
  • fn saturating_pow(self : T, exp : T) -> T
    This raised to exp, held at the end of the range it overflows towards: the bottom for a negative base to an odd power, the top otherwise.
  • fn checked_abs(self : T) -> Option<T>
    The absolute value, or nothing for the smallest value, whose positive does not fit.
  • fn wrapping_abs(self : T) -> T
    The absolute value, wrapping: the smallest value gives itself.
  • fn saturating_abs(self : T) -> T
    The absolute value, held at the largest value for the smallest.
  • fn saturating_neg(self : T) -> T
    The negation, held at the largest value for the smallest.
  • fn wrapping_div(self : T, rhs : T) -> T
    The quotient, wrapping: the smallest value divided by -1 gives itself. Refused on a zero divisor, as / is.
  • fn wrapping_rem(self : T, rhs : T) -> T
    The remainder, wrapping: the smallest value by -1 gives 0.
  • fn ilog2(self : T) -> T
    Rounded down. Refused at zero and below.
  • fn isqrt(self : T) -> T
    The square root, rounded down. Refused below zero. Digit by digit in base four, so nothing overflows and no float is involved.
  • fn checked_isqrt(self : T) -> Option<T>
    The square root rounded down, or nothing below zero.
  • fn midpoint(self : T, rhs : T) -> T
    Halfway between two values, rounded towards zero, without overflowing. >> rounds down, so a negative answer with a half left over is moved up by one.
  • fn next_power_of_two(self : T) -> T
    One for anything at or below one. The top power of two does not fit in a signed width, so a value beyond it is refused.
  • fn rotate_left(self : T, n : T) -> T
    Wrapping round. The right half is masked because >> copies the sign bit.
  • fn rotate_right(self : T, n : T) -> T
    The bits rotated right.
  • fn swap_bytes(self : T) -> T
    The bytes reversed. The mask is a parameter because 255 does not fit in an i8; it is accepted there and wraps to -1, which is right by luck.
  • fn reverse_bits(self : T) -> T
    The bits reversed.
  • fn checked_add(self : T, rhs : T) -> Option<T>
    Nothing if it would not fit. The bound is rearranged so the check itself cannot overflow.
  • fn checked_sub(self : T, rhs : T) -> Option<T>
    Nothing if it would not fit.
  • fn checked_mul(self : T, rhs : T) -> Option<T>
    Checked by dividing back out, exact when the product fit. The two divisions that would themselves overflow are ruled out first.
  • fn checked_div(self : T, rhs : T) -> Option<T>
    Nothing on division by zero, or on the one division that overflows.
  • fn checked_rem(self : T, rhs : T) -> Option<T>
    Refused in the same two cases as checked_div.
  • fn checked_neg(self : T) -> Option<T>
    Nothing for the smallest value.
  • fn saturating_add(self : T, rhs : T) -> T
    Held at the end of the range instead of wrapping past it.
  • fn saturating_sub(self : T, rhs : T) -> T
    Held at the end of the range instead of wrapping past it.
  • fn wrapping_add(self : T, rhs : T) -> T
    The sum, wrapping round at the width. Vx’s + already wraps, so the body is the operator; the name is what a reader who wants that on purpose looks for, and what a hash function is written in.
  • fn wrapping_sub(self : T, rhs : T) -> T
    The difference, wrapping round at the width.
  • fn wrapping_mul(self : T, rhs : T) -> T
    The product, keeping the low bits and discarding the rest.
  • fn wrapping_neg(self : T) -> T
    Zero minus this, wrapping. The smallest value negates to itself, because its positive is one past the largest.
  • fn saturating_mul(self : T, rhs : T) -> T
    Clamped to the width rather than wrapping. Which end it clamps to is the sign the product would have had, which is whether the two operands agree in sign.
  • fn leading_ones(self : T) -> T
    How many set bits the value starts with, counting from the top.
  • fn trailing_ones(self : T) -> T
    How many set bits the value ends with, counting from the bottom.
  • fn overflowing_add(self : T, rhs : T) -> (T, bool)
    The wrapped sum, and whether it wrapped.
  • fn overflowing_sub(self : T, rhs : T) -> (T, bool)
    The wrapped difference, and whether it wrapped.
  • fn overflowing_mul(self : T, rhs : T) -> (T, bool)
    The wrapped product, and whether it wrapped.
  • fn overflowing_neg(self : T) -> (T, bool)
    The negation, and whether it wrapped: only the smallest value does, to itself.
  • fn overflowing_abs(self : T) -> (T, bool)
    The absolute value, and whether it wrapped: only the smallest value does, to itself.
  • fn overflowing_div(self : T, rhs : T) -> (T, bool)
    The quotient, and whether it wrapped: the smallest value divided by -1 gives itself. Refused on a zero divisor, as / is.
  • fn overflowing_rem(self : T, rhs : T) -> (T, bool)
    The remainder, and whether it wrapped: the smallest value by -1 gives 0 and says so, since the division beside it overflows.

T = i8, i16, i32, i64

T methods, stamped for 4 instantiations

  • fn min_value(self : T) -> T
    Zero, at every unsigned width.
  • fn max_value(self : T) -> T
    The largest value of this width.
  • fn bits(self : T) -> T
    How many bits this width has.
  • fn count_ones(self : T) -> T
    How many bits are set.
  • fn count_zeros(self : T) -> T
    How many bits are clear.
  • fn leading_zeros(self : T) -> T
    Zero bits above the highest set bit. All of them, for zero.
  • fn trailing_zeros(self : T) -> T
    Zero bits below the lowest set bit. All of them, for zero.
  • fn is_power_of_two(self : T) -> bool
    Zero is not a power of two.
  • fn abs_diff(self : T, other : T) -> T
    The distance between two values, which is never negative and so always fits.
  • fn pow(self : T, exp : T) -> T
    By squaring. Overflow wraps, as every arithmetic operator here does.
  • fn div_euclid(self : T, rhs : T) -> T
    Plain division: an unsigned quotient is already the Euclidean one.
  • fn rem_euclid(self : T, rhs : T) -> T
    Plain remainder, which at this width is never negative.
  • fn checked_pow(self : T, exp : T) -> Option<T>
    This raised to exp, or nothing if the answer does not fit.
  • fn wrapping_pow(self : T, exp : T) -> T
    This raised to exp, wrapping round at the width. The same as pow, under the name that says so.
  • fn ilog(self : T, base : T) -> T
    The logarithm to base, rounded down. Refused at zero and below, and for a base below two.
  • fn ilog10(self : T) -> T
    The logarithm to base ten, rounded down. Refused at zero and below.
  • fn checked_ilog(self : T, base : T) -> Option<T>
    The logarithm to base, or nothing at zero and below or for a base below two.
  • fn checked_ilog2(self : T) -> Option<T>
    The logarithm to base two, or nothing at zero and below.
  • fn checked_ilog10(self : T) -> Option<T>
    The logarithm to base ten, or nothing at zero and below.
  • fn saturating_pow(self : T, exp : T) -> T
    This raised to exp, held at the largest value if it overflows.
  • fn is_multiple_of(self : T, rhs : T) -> bool
    Does rhs divide this exactly? Zero is a multiple only of zero, and every value of one.
  • fn next_multiple_of(self : T, rhs : T) -> T
    The smallest multiple of rhs at or above this. Refused on a zero rhs, and when the answer does not fit.
  • fn checked_next_multiple_of(self : T, rhs : T) -> Option<T>
    The smallest multiple of rhs at or above this, or nothing on a zero rhs or when the answer does not fit.
  • fn checked_next_power_of_two(self : T) -> Option<T>
    The smallest power of two at or above this, or nothing above the top power of two.
  • fn ilog2(self : T) -> T
    Rounded down. Refused at zero, which has no logarithm.
  • fn isqrt(self : T) -> T
    The square root, rounded down, digit by digit in base four.
  • fn midpoint(self : T, rhs : T) -> T
    Halfway between two values, rounded down, without overflowing.
  • fn div_ceil(self : T, rhs : T) -> T
    The quotient rounded up. Refused on a zero divisor.
  • fn next_power_of_two(self : T) -> T
    One for anything at or below one. Refused above the top power of two, which is the half of the range that has no next power to reach.
  • fn rotate_left(self : T, n : T) -> T
    Wrapping round. No mask, unlike the signed rotate: >> brings in zeros here.
  • fn rotate_right(self : T, n : T) -> T
    The bits rotated right.
  • fn swap_bytes(self : T) -> T
    The bytes reversed.
  • fn reverse_bits(self : T) -> T
    The bits reversed.
  • fn checked_add(self : T, rhs : T) -> Option<T>
    Nothing if it would not fit. The bound is rearranged so the check cannot overflow.
  • fn checked_sub(self : T, rhs : T) -> Option<T>
    Nothing if it would go below zero, which is where an unsigned width ends.
  • fn checked_mul(self : T, rhs : T) -> Option<T>
    Checked by dividing back out, exact when the product fit.
  • fn checked_div(self : T, rhs : T) -> Option<T>
    Nothing on division by zero, which is the only division that fails here.
  • fn checked_rem(self : T, rhs : T) -> Option<T>
    Refused on a zero divisor, as checked_div is.
  • fn saturating_add(self : T, rhs : T) -> T
    Held at the top of the range instead of wrapping past it.
  • fn saturating_sub(self : T, rhs : T) -> T
    Held at zero instead of wrapping below it.
  • fn wrapping_add(self : T, rhs : T) -> T
    The sum, wrapping round at the width. Vx’s + already wraps.
  • fn wrapping_sub(self : T, rhs : T) -> T
    The difference, wrapping round at the width, so subtracting past zero lands near the top.
  • fn wrapping_mul(self : T, rhs : T) -> T
    The product, keeping the low bits and discarding the rest.
  • fn wrapping_neg(self : T) -> T
    Zero minus this, wrapping.
  • fn saturating_mul(self : T, rhs : T) -> T
    Clamped to the top of the width rather than wrapping. There is no other end to clamp to without a sign.
  • fn leading_ones(self : T) -> T
    How many set bits the value starts with, counting from the top.
  • fn trailing_ones(self : T) -> T
    How many set bits the value ends with, counting from the bottom.
  • fn overflowing_add(self : T, rhs : T) -> (T, bool)
    The wrapped sum, and whether it wrapped.
  • fn overflowing_sub(self : T, rhs : T) -> (T, bool)
    The wrapped difference, and whether it went below zero.
  • fn overflowing_mul(self : T, rhs : T) -> (T, bool)
    The wrapped product, and whether it wrapped.
  • fn overflowing_neg(self : T) -> (T, bool)
    Zero minus this, wrapping, and whether it wrapped: every value but zero does.
  • fn overflowing_div(self : T, rhs : T) -> (T, bool)
    The quotient, which never wraps at an unsigned width.
  • fn overflowing_rem(self : T, rhs : T) -> (T, bool)
    The remainder, which never wraps at an unsigned width.

T = u8, u16, u32, u64

T methods, stamped for 2 instantiations

  • fn decimal_len(self : T) -> i64
    How many bytes the decimal form takes.
  • unsafe fn write_decimal(self : T, buf : *mut i8, at : i64) -> i64
    Write the decimal form to buf[at..] and return how many bytes that took. Unsafe because nothing checks that the buffer has room: 20 bytes past at holds every integer of every width. No NUL is written.

T = u32, u64

T methods, stamped for 2 instantiations

  • fn decimal_len(self : T) -> i64
    How many bytes the decimal form takes.
  • unsafe fn write_decimal(self : T, buf : *mut i8, at : i64) -> i64
    Write the decimal form to buf[at..] and return how many bytes that took. Unsafe for the reason u64::write_decimal is.

T = u8, u16

T methods, stamped for 4 instantiations

  • fn decimal_len(self : T) -> i64
    How many bytes the decimal form takes, counting the -.
  • unsafe fn write_decimal(self : T, buf : *mut i8, at : i64) -> i64
    Write the decimal form to buf[at..] and return how many bytes that took. Unsafe for the reason u64::write_decimal is.

T = i8, i16, i32, i64

T methods, stamped for 8 instantiations

  • fn decimal_c_str(self : T) -> *const i8
    This number in decimal, as a string constant in the program: no work and no allocation at run time. A comptime function, so the number has to be known while compiling, and a call with a run-time value is an error. A u64 from 2^63 up cannot be worked out yet: the evaluator’s integers are i64.

T = i8, i16, i32, i64, u8, u16, u32, u64

T methods, stamped for 4 instantiations

  • fn pi() -> T
    Pi.
  • fn tau() -> T
    2 pi, a full turn in radians.
  • fn e() -> T
    Euler’s number.
  • fn frac_pi_2() -> T
    Pi / 2.
  • fn frac_pi_3() -> T
    Pi / 3.
  • fn frac_pi_4() -> T
    Pi / 4.
  • fn frac_pi_6() -> T
    Pi / 6.
  • fn frac_pi_8() -> T
    Pi / 8.
  • fn frac_1_pi() -> T
    1 / pi.
  • fn frac_2_pi() -> T
    2 / pi.
  • fn frac_2_sqrt_pi() -> T
    2 / sqrt(pi).
  • fn sqrt_2() -> T
    Sqrt(2).
  • fn frac_1_sqrt_2() -> T
    1 / sqrt(2).
  • fn ln_2() -> T
    Ln(2).
  • fn ln_10() -> T
    Ln(10).
  • fn log2_e() -> T
    Log2(e).
  • fn log10_e() -> T
    Log10(e).
  • fn log2_10() -> T
    Log2(10).
  • fn log10_2() -> T
    Log10(2).
  • fn sqrt(self : T) -> T
    The positive square root.
  • fn abs(self : T) -> T
    The distance from zero, so the sign is dropped.
  • fn fast_exp(self : T) -> T
    e raised to this, by the target’s own exp: on a GPU, a few fast instructions in the type’s own width. Not correctly rounded, as exp is; a spawn loop calling it still runs in parallel.
  • fn fast_tanh(self : T) -> T
    The hyperbolic tangent, by the target’s own tanh, as fast_exp is to exp.
  • fn floor(self : T) -> T
    The largest whole number no greater than this.
  • fn ceil(self : T) -> T
    The smallest whole number no less than this.
  • fn round(self : T) -> T
    The nearest whole number, halves going away from zero.
  • fn trunc(self : T) -> T
    The whole part, so the fraction is dropped and the sign is kept.
  • fn fract(self : T) -> T
    The fractional part, which carries this value’s sign.
  • fn copysign(self : T, sign : T) -> T
    This value’s magnitude with sign’s sign.
  • fn mul_add(self : T, a : T, b : T) -> T
    self * a + b with one rounding instead of two.
  • fn rem_euclid(self : T, rhs : T) -> T
    Never negative, whatever the signs: -7.0 % 4.0 is -3.0 where this is 1.0.
  • fn div_euclid(self : T, rhs : T) -> T
    The whole quotient pairing with rem_euclid.
  • fn powi(self : T, n : i32) -> T
    This raised to a whole power, by squaring. A negative power gives the reciprocal.
  • fn max(self : T, other : T) -> T
    The larger of the two. A NaN is ignored: the other value is the answer. A NaN on the left fails the comparison, so only the right needs a test.
  • fn min(self : T, other : T) -> T
    The smaller of the two. A NaN is ignored: the other value is the answer.
  • fn clamp(self : T, lo : T, hi : T) -> T
    Held between lo and hi. A NaN stays a NaN. Refused when lo is above hi or either bound is a NaN.
  • fn recip(self : T) -> T
    One divided by this.
  • fn to_degrees(self : T) -> T
    This many radians in degrees.
  • fn to_radians(self : T) -> T
    This many degrees in radians.
  • fn is_nan(self : T) -> bool
    Is this the value that is equal to nothing, itself included?
  • fn signum(self : T) -> T
    One with this value’s sign, or the value itself when it is a NaN. A zero answers 1 with the zero’s sign, not 0.
  • fn is_finite(self : T) -> bool
    Is this a real number, rather than an infinity or a NaN?
  • fn is_infinite(self : T) -> bool
    Is this an infinity, of either sign?
  • fn log(self : T, base : T) -> T
    The logarithm to base, as ln(self) / ln(base).
  • fn round_ties_even(self : T) -> T
    The nearest whole number, halves going to the even neighbour: 2.5 gives 2.0.
  • fn sin_cos(self : T) -> (T, T)
    The sine and the cosine, as a pair.
  • fn midpoint(self : T, other : T) -> T
    Halfway between two values, without overflowing near the largest value or losing the answer to underflow near the smallest.
  • fn is_sign_negative(self : T) -> bool
    Is the sign bit set? True for -0.0, and for a NaN whose sign bit is set.
  • fn is_sign_positive(self : T) -> bool
    Is the sign bit clear? True for 0.0, and for a NaN whose sign bit is clear.
  • fn is_normal(self : T) -> bool
    Is this neither zero, infinite, subnormal nor a NaN?
  • fn is_subnormal(self : T) -> bool
    Is this nonzero but smaller in size than the smallest normal value?
  • fn classify(self : T) -> FpCategory
    Which of the five kinds of float value this is.

T = f32, f64, f16, bf16

T methods, stamped for 2 instantiations

  • fn to_bits(self : T) -> U
    The bits of this value, as an unsigned integer of the same width.
  • fn from_bits(v : U) -> T
    The value these bits spell. Static: f32::from_bits(1065353216) is 1.0.
  • fn next_up(self : T) -> T
    The smallest value greater than this one. A NaN and positive infinity give themselves, and either zero gives the smallest positive value.
  • fn next_down(self : T) -> T
    The largest value less than this one. A NaN and negative infinity give themselves, and either zero gives the negative value nearest zero.
  • fn total_cmp(self : &T, other : &T) -> Ordering
    A total order over every value, NaNs included: negative NaNs, negative infinity, the negatives, -0.0, 0.0, the positives, positive infinity, positive NaNs. It compares the bits as signed integers, with the bits of a negative value turned round so that more negative sorts lower.
  • fn radix() -> u32
    The base the format counts in: 2.
  • fn mantissa_digits() -> u32
    How many binary digits the significand holds, the leading one included.
  • fn digits() -> u32
    How many decimal digits survive a round trip through this width.
  • fn min_exp() -> i32
    One more than the smallest power of two a normal value can have.
  • fn max_exp() -> i32
    One more than the largest power of two a finite value can have.
  • fn min_10_exp() -> i32
    The smallest power of ten that is a normal value.
  • fn max_10_exp() -> i32
    The largest power of ten that is a finite value.

(T, U) = f32 → u32, f64 → u64

T methods, stamped for 2 instantiations

  • fn shortest_decimal(self : T) -> FloatDecimal
    The fewest decimal digits that read back as this same value, as digits * 10^exponent (Ryu’s algorithm). NaN and the infinities are flagged.
  • fn decimal_len(self : T) -> i64
    How many bytes write_decimal writes: the length of Rust’s format!("{}", x).
  • unsafe fn write_decimal(self : T, buf : *mut i8, at : i64) -> i64
    Write what Rust’s {} prints to buf[at..] and return how many bytes that took: the shortest digits in plain notation, as in 0.1, -2, 1e21 as 22 digits, inf and NaN. Unsafe because nothing checks that the buffer has decimal_len() bytes. No NUL is written.
  • fn scientific_len(self : T) -> i64
    How many bytes write_scientific writes: the length of Rust’s format!("{:e}", x).
  • unsafe fn write_scientific(self : T, buf : *mut i8, at : i64) -> i64
    Write what Rust’s {:e} prints to buf[at..] and return how many bytes that took: the shortest digits as 1.5e-7, 1e0 or -0e0. Unsafe for the reason write_decimal is.

T = f64, f32

core::ops

The callable types a closure literal lowers into.

Types

  • struct Closure0<Ret>
  • struct Closure1<Arg, Ret>
  • struct Closure2<Arg1, Arg2, Ret>
  • struct Closure3<Arg1, Arg2, Arg3, Ret>
  • trait Try
    A value that either carries on, holding an output, or stops early. It is what Iterator::try_fold reads from its closure’s answers, and Option and Result implement it: Some and Ok carry on, None and Err stop. Only what those two need: no residual type and no ?.
  • trait Drop
    What happens when a value’s owner gives it up. The compiler calls drop where the owner is dropped – at the end of its block, before a return, break or continue leaves it, or when the variable is given a new value – and then drops the value’s fields, in the order they are declared. A program does not call it; core::mem::drop gives a value up early.
  • trait Add
    a + b.
  • trait Sub
    a - b.
  • trait Mul
    a * b.
  • trait Div
    a / b.
  • trait Rem
    a % b.
  • trait Neg
    -a.
  • trait Not
    !a.
  • trait BitAnd
    a & b.
  • trait BitOr
    a | b.
  • trait BitXor
    a ^ b.
  • trait Shl
    a << b.
  • trait Shr
    a >> b.
  • trait Index
    a[i], read: a reference to the element at index.
  • trait IndexMut
    a[i] = x: a mutable reference to the element at index.

trait Try methods

  • fn is_continue(self : &Self) -> bool
    Does this carry on? False means stop here and hand this value back.
  • fn into_output(self : Self) -> Self : : Output
    The output of one that carries on.
  • fn from_output(output : Self : : Output) -> Self
    One that carries on, holding output.

trait Drop methods

  • fn drop(self : &mut Self) -> void

trait Add methods

  • fn add(self : Self, rhs : Self) -> Self : : Output

trait Sub methods

  • fn sub(self : Self, rhs : Self) -> Self : : Output

trait Mul methods

  • fn mul(self : Self, rhs : Self) -> Self : : Output

trait Div methods

  • fn div(self : Self, rhs : Self) -> Self : : Output

trait Rem methods

  • fn rem(self : Self, rhs : Self) -> Self : : Output

trait Neg methods

  • fn neg(self : Self) -> Self : : Output

trait Not methods

  • fn not(self : Self) -> Self : : Output

trait BitAnd methods

  • fn bitand(self : Self, rhs : Self) -> Self : : Output

trait BitOr methods

  • fn bitor(self : Self, rhs : Self) -> Self : : Output

trait BitXor methods

  • fn bitxor(self : Self, rhs : Self) -> Self : : Output

trait Shl methods

  • fn shl(self : Self, rhs : Self) -> Self : : Output

trait Shr methods

  • fn shr(self : Self, rhs : Self) -> Self : : Output

trait Index methods

  • fn index(self : &Self, index : i32) -> &Self : : Output

trait IndexMut methods

  • fn index_mut(self : &mut Self, index : i32) -> &mut Self : : Output

Add for T methods, stamped for 11 instantiations

  • fn add(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Sub for T methods, stamped for 11 instantiations

  • fn sub(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Mul for T methods, stamped for 11 instantiations

  • fn mul(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Div for T methods, stamped for 11 instantiations

  • fn div(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Rem for T methods, stamped for 11 instantiations

  • fn rem(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64

Neg for T methods, stamped for 6 instantiations

  • fn neg(self : T) -> T

T = i8, i16, i32, i64, f32, f64

Not for T methods, stamped for 9 instantiations

  • fn not(self : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, bool

BitAnd for T methods, stamped for 9 instantiations

  • fn bitand(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, bool

BitOr for T methods, stamped for 9 instantiations

  • fn bitor(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, bool

BitXor for T methods, stamped for 9 instantiations

  • fn bitxor(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64, bool

Shl for T methods, stamped for 8 instantiations

  • fn shl(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64

Shr for T methods, stamped for 8 instantiations

  • fn shr(self : T, rhs : T) -> T

T = i8, i16, i32, i64, u8, u16, u32, u64

core::option

Option<T>, for a value that may be absent.

Types

  • enum Option<T>

Try for Option<T> methods

  • fn is_continue(self : &Option<T>) -> bool
    Is there a value?
  • fn into_output(self : Option<T>) -> T
    The value.
  • fn from_output(output : T) -> Option<T>
    Some(output).

Option<T> methods

  • fn is_some(self : &Option<T>) -> Bool
    Is there a value?
  • fn is_none(self : &Option<T>) -> Bool
    Is there no value?
  • fn unwrap(self : Option<T>) -> T
    The value, or a stop. Reach for unwrap_or where there is a sensible answer for the absent case; this one ends the program.
  • fn expect(self : Option<T>, msg : *const i8) -> T
    The value, or a stop with the message msg. unwrap with a message of the caller’s.
  • fn unwrap_or(self : Option<T>, default : T) -> T
    The value, or the given one. default is evaluated by the caller either way, so keep it cheap. An unwrap_or_else that takes a closure instead needs a type parameter this cannot yet spell.
  • fn or(self : Option<T>, other : Option<T>) -> Option<T>
    This one if it holds a value, otherwise the other. Both sides are the same type, which is why this fits while and_then does not.
  • fn and(self : Option<T>, other : Option<T>) -> Option<T>
    The other one if this holds a value, otherwise nothing.
  • fn xor(self : Option<T>, other : Option<T>) -> Option<T>
    Whichever one holds a value, and nothing when both do or neither does.
  • fn map<U>(self : Option<T>, f : Closure1<T, U>) -> Option<U>
    The value with f applied, if there is one.
  • fn and_then<U>(self : Option<T>, f : Closure1<T, Option<U>>) -> Option<U>
    map for an f that answers with an Option of its own, without the nesting.
  • fn zip<U>(self : Option<T>, other : Option<U>) -> Option<(T, U)>
    Both values as a pair when both are there, otherwise nothing.
  • fn filter(self : Option<T>, p : Closure1<T, bool>) -> Option<T>
    The value if it is there and p accepts it, otherwise nothing.
  • fn map_or<U>(self : Option<T>, default : U, f : Closure1<T, U>) -> U
    f applied to the value, or the given answer when there is none. Both are the same type, which is what separates this from map.
  • fn unwrap_or_else(self : Option<T>, f : Closure0<T>) -> T
    The value, or the answer f gives. Unlike unwrap_or, nothing is computed when there is a value.
  • fn is_some_and(self : Option<T>, p : Closure1<T, bool>) -> bool
    Is there a value, and does p accept it?
  • fn is_none_or(self : Option<T>, p : Closure1<T, bool>) -> bool
    Is there no value, or does p accept the one there is? The mirror of is_some_and.
  • fn or_else(self : Option<T>, f : Closure0<Option<T>>) -> Option<T>
    This one if it holds a value, otherwise what f gives. or is the form that evaluates the other side either way.
  • fn map_or_else<U>(self : Option<T>, d : Closure0<U>, f : Closure1<T, U>) -> U
    f applied to the value, or what d gives when there is none. map_or is the form that takes the fallback as a value.
  • fn take(self : &mut Option<T>) -> Option<T>
    The value, leaving nothing behind.
  • fn replace(self : &mut Option<T>, v : T) -> Option<T>
    The value, leaving v behind.

Option<Option<T>> methods

  • fn flatten(self : Option<Option<T>>) -> Option<T>
    One level of Option removed: Some(Some(x)) is Some(x), anything else is None.

Option<(T, U)> methods

  • fn unzip(self : Option<(T, U) -> void
    A pair split in two: Some((a, b)) gives (Some(a), Some(b)), and None gives two Nones. The reverse of zip.

core::panic

Functions

  • fn unreachable() -> !
    Stops the program: code that should never run did.
  • fn todo() -> !
    Stops the program: this code is not written yet.
  • fn unimplemented() -> !
    Stops the program: this case is not supported.

core::ptr

Raw pointers: making one, and reading or writing through it.

Functions

  • fn null<T>() -> *const T
    A pointer to nothing.
  • fn null_mut<T>() -> *mut T
    A mutable pointer to nothing.
  • unsafe fn read<T>(p : *const T) -> T
    The value the pointer addresses. The caller promises there is one.
  • unsafe fn write<T>(p : *mut T, v : T) -> void
    Puts a value where the pointer addresses. The caller promises it may.
  • fn offset<T>(p : *const T, count : i64) -> *const T
    The pointer count elements further on, or back for a negative count. Like Rust’s wrapping_offset, it only computes the address.
  • fn offset_mut<T>(p : *mut T, count : i64) -> *mut T
    offset for a mutable pointer.
  • fn add<T>(p : *const T, count : usize) -> *const T
    The pointer count elements further on.
  • fn add_mut<T>(p : *mut T, count : usize) -> *mut T
    add for a mutable pointer.
  • fn sub<T>(p : *const T, count : usize) -> *const T
    The pointer count elements back.
  • fn sub_mut<T>(p : *mut T, count : usize) -> *mut T
    sub for a mutable pointer.
  • fn eq<T>(a : *const T, b : *const T) -> bool
    Whether the two pointers hold the same address.
  • fn is_null<T>(p : *const T) -> bool
    Whether the pointer is null.
  • fn is_null_mut<T>(p : *mut T) -> bool
    is_null for a mutable pointer.

core::result

Result<T, E>, for an operation that may fail.

Types

  • enum Result<T, E>

Try for Result<T, E> methods

  • fn is_continue(self : &Result<T, E>) -> bool
    Is this Ok?
  • fn into_output(self : Result<T, E>) -> T
    The Ok value.
  • fn from_output(output : T) -> Result<T, E>
    Ok(output).

Result<T, E> methods

  • fn is_ok(self : &Result<T, E>) -> bool
    Did it succeed?
  • fn is_err(self : &Result<T, E>) -> bool
    Did it fail? The negation of is_ok.
  • fn unwrap(self : Result<T, E>) -> T
    The value, or a stop. unwrap_or is the form with an answer for the failing case.
  • fn expect(self : Result<T, E>, msg : *const i8) -> T
    The value, or a stop with the message msg. unwrap with a message of the caller’s.
  • fn unwrap_or(self : Result<T, E>, default : T) -> T
    The value, or the given one on failure. default is evaluated by the caller either way, so keep it cheap; unwrap_or_else is the form that computes nothing when there is a value.
  • fn ok(self : Result<T, E>) -> Option<T>
    The success dropped, leaving what there is of one.
  • fn err(self : Result<T, E>) -> Option<E>
    The failure as an Option, the mirror of ok.
  • fn map<U>(self : Result<T, E>, f : Closure1<T, U>) -> Result<U, E>
    f over the success, the failure untouched.
  • fn map_err<F>(self : Result<T, E>, f : Closure1<E, F>) -> Result<T, F>
    f over the failure, the success untouched.
  • fn and_then<U>(self : Result<T, E>, f : Closure1<T, Result<U, E>>) -> Result<U, E>
    map for an f that may itself fail, without the nesting.
  • fn unwrap_or_else(self : Result<T, E>, f : Closure1<E, T>) -> T
    The value, or what f makes of the failure.
  • fn unwrap_err(self : Result<T, E>) -> E
    The failure, or a stop. The mirror of unwrap.
  • fn expect_err(self : Result<T, E>, msg : *const i8) -> E
    The failure, or a stop with the message msg. The mirror of expect.
  • fn is_ok_and(self : Result<T, E>, p : Closure1<T, bool>) -> bool
    Did it succeed, and does p accept the value?
  • fn is_err_and(self : Result<T, E>, p : Closure1<E, bool>) -> bool
    Did it fail, and does p accept the failure?
  • fn and<U>(self : Result<T, E>, other : Result<U, E>) -> Result<U, E>
    The other one if this succeeded, otherwise this failure. The success types differ, which is why this takes a type parameter where Option::and does not. Bound first and returned once: a match whose arms each return an enum is declined by the flat path, and other is a starting value that needs no default of its own.
  • fn or<F>(self : Result<T, E>, other : Result<T, F>) -> Result<T, F>
    This success if there is one, otherwise the other result. The failure types differ.
  • fn map_or<U>(self : Result<T, E>, default : U, f : Closure1<T, U>) -> U
    f applied to the value, or the given answer when there is none.
  • fn map_or_else<U>(self : Result<T, E>, d : Closure1<E, U>, f : Closure1<T, U>) -> U
    f over the value, or d over the failure. Both answer with the same type.

Option<T> methods

  • fn ok_or<E>(self : Option<T>, err : E) -> Result<T, E>
    The value as a success, or the given failure.
  • fn ok_or_else<E>(self : Option<T>, f : Closure0<E>) -> Result<T, E>
    The value as a success, or the failure f gives. Nothing is computed when there is a value.

core::sync::atomic

Types

  • enum AtomicOrdering
    How an atomic operation is ordered with the memory operations around it.
  • struct AtomicBool
    A bool that can be read and changed atomically. It is held as an AtomicU8 that is always 0 or 1, since LLVM has no atomic operation on a single bit.
  • enum FenceScope
    Which threads a scoped_fence orders memory for. A smaller scope is a cheaper fence.

Functions

  • fn core_atomic_load_order_ok<D : Topology>(o : AtomicOrdering) -> void
    Whether a load may use o. Rust panics on Release and AcqRel. It runs where its caller runs, so device code can call it.
  • fn core_atomic_store_order_ok<D : Topology>(o : AtomicOrdering) -> void
    Whether a store may use o. Rust panics on Acquire and AcqRel. It runs where its caller runs.
  • fn core_atomic_fence_order_ok<D : Topology>(o : AtomicOrdering) -> void
    Whether a fence may use o. Rust panics on Relaxed. It runs where its caller runs.
  • fn core_atomic_with_acquire(o : AtomicOrdering) -> AtomicOrdering
    o, made strong enough to include Acquire.
  • fn core_atomic_cas_order(success : AtomicOrdering, failure : AtomicOrdering) -> AtomicOrdering
    The one ordering compare_exchange uses: success made strong enough to also cover failure. A failure of Release or AcqRel fails the assert, as Rust panics.
  • fn core_atomic_bool_byte(b : bool) -> u8
    1 for true, 0 for false.
  • fn fence(order : AtomicOrdering) -> void
    Orders the memory operations before it with those after it, for every thread. order cannot be Relaxed.
  • fn scoped_fence<D : Topology>(order : AtomicOrdering, scope : FenceScope) -> void
    fence, for the threads of scope only. It runs where its caller runs, and it writes no memory, so the passes of an across loop may call it. order cannot be Relaxed. On a CPU, LLVM gives every scope the fence for the whole system, which is stronger.
  • fn compiler_fence(order : AtomicOrdering) -> void
    fence, for the current thread only: it stops the compiler from moving memory operations across it and emits no instruction. order cannot be Relaxed.

AtomicBool methods

  • fn new(v : bool) -> AtomicBool
    A new atomic holding v.
  • fn into_inner(self : AtomicBool) -> bool
    The value, which is no longer atomic.
  • fn load(self : &AtomicBool, order : AtomicOrdering) -> bool
    Reads the value. order cannot be Release or AcqRel.
  • fn store(self : &AtomicBool, val : bool, order : AtomicOrdering) -> void
    Writes val. order cannot be Acquire or AcqRel.
  • fn swap(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
    Writes val and answers the old value.
  • fn compare_exchange(self : &AtomicBool, current : bool, new : bool, success : AtomicOrdering, failure : AtomicOrdering) -> Result<bool, bool>
    Stores new if the value is current. Answers Ok with the old value when it stored, Err with the value it found when it did not.
  • fn compare_exchange_weak(self : &AtomicBool, current : bool, new : bool, success : AtomicOrdering, failure : AtomicOrdering) -> Result<bool, bool>
    compare_exchange, which never fails spuriously.
  • fn fetch_and(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
    Logical and with val; answers the old value.
  • fn fetch_or(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
    Logical or with val; answers the old value.
  • fn fetch_xor(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
    Logical exclusive or with val; answers the old value.
  • fn fetch_nand(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
    Sets the value to !(old && val); answers the old value. A byte nand would leave 254, so this is an exclusive or with true when val is true, and a store of true when not.
  • fn fetch_not(self : &AtomicBool, order : AtomicOrdering) -> bool
    Flips the value; answers the old one.

Default for AtomicBool methods

  • fn default() -> AtomicBool
    An atomic holding false.

From<bool> for AtomicBool methods

  • fn from(value : bool) -> AtomicBool
    new.

T methods, stamped for 25 instantiations

  • fn W(self : &T, val : U, order : AtomicOrdering) -> U

(T, U, W) = $name → $t → swap, $name → $t → fetch_add, $name → $t → fetch_sub, $name → $t → fetch_and, $name → $t → fetch_nand, $name → $t → fetch_or, $name → $t → fetch_xor, AtomicI8 → i8 → fetch_max, AtomicI8 → i8 → fetch_min, AtomicI16 → i16 → fetch_max, AtomicI16 → i16 → fetch_min, AtomicI32 → i32 → fetch_max, AtomicI32 → i32 → fetch_min, AtomicI64 → i64 → fetch_max, AtomicI64 → i64 → fetch_min, AtomicU8 → u8 → fetch_max, AtomicU8 → u8 → fetch_min, AtomicU16 → u16 → fetch_max, AtomicU16 → u16 → fetch_min, AtomicU32 → u32 → fetch_max, AtomicU32 → u32 → fetch_min, AtomicU64 → u64 → fetch_max, AtomicU64 → u64 → fetch_min, AtomicUsize → usize → fetch_max, AtomicUsize → usize → fetch_min

T methods, stamped for 9 instantiations

  • fn new(v : U) -> T
    A new atomic holding v.
  • fn into_inner(self : T) -> U
    The value, which is no longer atomic.
  • fn get_mut(self : &mut T) -> &mut U
    The value, through a &mut, which rules out any other access at the same time.
  • fn as_ptr(self : &T) -> *mut U
    A pointer to the value.
  • fn load(self : &T, order : AtomicOrdering) -> U
    Reads the value. order cannot be Release or AcqRel.
  • fn store(self : &T, val : U, order : AtomicOrdering) -> void
    Writes val. order cannot be Acquire or AcqRel.
  • fn compare_exchange(self : &T, current : U, new : U, success : AtomicOrdering, failure : AtomicOrdering) -> Result<U, U>
    Stores new if the value is current. Answers Ok with the old value when it stored, Err with the value it found when it did not.
  • fn compare_exchange_weak(self : &T, current : U, new : U, success : AtomicOrdering, failure : AtomicOrdering) -> Result<U, U>
    compare_exchange. Rust allows this one to fail even when the value matches; this one never does.
  • fn fetch_update(self : &T, set_order : AtomicOrdering, fetch_order : AtomicOrdering, f : Closure1<U, Option<U>>) -> Result<U, U>
    Replaces the value with f(value) until no other write gets in between, and answers Ok with the old value. If f answers None, stops and answers Err with the value.
  • fn core_atomic_cmpxchg(self : &T, current : U, new : U, order : AtomicOrdering) -> U
    One llvm.cmpxchg at order, answering the value it found. The failure ordering is the strongest one order allows.

(T, U) = AtomicI8 → i8, AtomicI16 → i16, AtomicI32 → i32, AtomicI64 → i64, AtomicU8 → u8, AtomicU16 → u16, AtomicU32 → u32, AtomicU64 → u64, AtomicUsize → usize

Default for T methods, stamped for 9 instantiations

  • fn default() -> T
    An atomic holding zero.

(T, U) = AtomicI8 → i8, AtomicI16 → i16, AtomicI32 → i32, AtomicI64 → i64, AtomicU8 → u8, AtomicU16 → u16, AtomicU32 → u32, AtomicU64 → u64, AtomicUsize → usize

From<U> for T methods, stamped for 9 instantiations

  • fn from(value : U) -> T
    new.

(T, U) = AtomicI8 → i8, AtomicI16 → i16, AtomicI32 → i32, AtomicI64 → i64, AtomicU8 → u8, AtomicU16 → u16, AtomicU32 → u32, AtomicU64 → u64, AtomicUsize → usize

Functions, stamped for 6 instantiations

  • fn W<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>) -> void
    The number of elements of t. It reads no element.
  • fn X<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>, i : i32, order : AtomicOrdering) -> void
    Reads t[i]. order cannot be Release or AcqRel.
  • fn Y<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>, i : i32, val : T, order : AtomicOrdering) -> void
    Writes val to t[i]. order cannot be Acquire or AcqRel.

(T, W, X, Y) = i32 → core_atomic_len_i32 → atomic_load_i32 → atomic_store_i32, u32 → core_atomic_len_u32 → atomic_load_u32 → atomic_store_u32, i64 → core_atomic_len_i64 → atomic_load_i64 → atomic_store_i64, u64 → core_atomic_len_u64 → atomic_load_u64 → atomic_store_u64, f32 → core_atomic_len_f32 → atomic_load_f32 → atomic_store_f32, f64 → core_atomic_len_f64 → atomic_load_f64 → atomic_store_f64

Functions, stamped for 4 instantiations

  • fn W<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>, i : i32, current : T, new : T, order : AtomicOrdering) -> void
    Writes new to t[i] if it holds current, and answers the value it found. So it wrote when the answer is current. The failure ordering is the strongest one order allows.

(T, W) = i32 → atomic_compare_and_swap_i32, u32 → atomic_compare_and_swap_u32, i64 → atomic_compare_and_swap_i64, u64 → atomic_compare_and_swap_u64

Functions, stamped for 31 instantiations

  • fn T<M : Memory, D : Topology>(t : &Tensor<U, [?], Memory : : M>, i : i32, val : U, order : AtomicOrdering) -> void

(T, U) = atomic_swap_i32 → i32, atomic_fetch_add_i32 → i32, atomic_fetch_sub_i32 → i32, atomic_fetch_max_i32 → i32, atomic_fetch_min_i32 → i32, atomic_swap_u32 → u32, atomic_fetch_add_u32 → u32, atomic_fetch_sub_u32 → u32, atomic_fetch_max_u32 → u32, atomic_fetch_min_u32 → u32, atomic_swap_i64 → i64, atomic_fetch_add_i64 → i64, atomic_fetch_sub_i64 → i64, atomic_fetch_max_i64 → i64, atomic_fetch_min_i64 → i64, atomic_swap_u64 → u64, atomic_fetch_add_u64 → u64, atomic_fetch_sub_u64 → u64, atomic_fetch_max_u64 → u64, atomic_fetch_min_u64 → u64, atomic_swap_f32 → f32, atomic_fetch_add_f32 → f32, atomic_fetch_sub_f32 → f32, atomic_fetch_max_f32 → f32, atomic_fetch_min_f32 → f32, atomic_swap_f64 → f64, atomic_fetch_add_f64 → f64, atomic_fetch_sub_f64 → f64, atomic_fetch_max_f64 → f64, atomic_fetch_min_f64 → f64, atomic_fetch_inc_wrap_u32 → u32

core::time

Clocks and durations.

Types

  • struct Duration
    A span of time: whole seconds and the nanoseconds past them, always below one second.

Duration methods

  • fn new(secs : u64, nanos : u32) -> Duration
    secs seconds and nanos nanoseconds. Nanoseconds of a second or more carry into the seconds; refused when the seconds then overflow.
  • fn from_secs(secs : u64) -> Duration
  • fn from_millis(millis : u64) -> Duration
  • fn from_micros(micros : u64) -> Duration
  • fn from_nanos(nanos : u64) -> Duration
  • fn zero() -> Duration
    No time at all.
  • fn max_value() -> Duration
    The longest duration: the largest u64 of seconds and 999,999,999 nanoseconds.
  • fn is_zero(self : &Duration) -> bool
  • fn as_secs(self : &Duration) -> u64
    The whole seconds, without the fraction.
  • fn subsec_millis(self : &Duration) -> u32
    The fraction of a second, in whole milliseconds.
  • fn subsec_micros(self : &Duration) -> u32
    The fraction of a second, in whole microseconds.
  • fn subsec_nanos(self : &Duration) -> u32
    The fraction of a second, in nanoseconds.
  • fn as_millis(self : &Duration) -> u64
    The whole duration in milliseconds, rounded down.
  • fn as_micros(self : &Duration) -> u64
    The whole duration in microseconds, rounded down.
  • fn as_nanos(self : &Duration) -> u64
    The whole duration in nanoseconds.
  • fn as_secs_f64(self : &Duration) -> f64
  • fn as_secs_f32(self : &Duration) -> f32
  • fn from_secs_f64(secs : f64) -> Duration
    secs seconds, to the nearest nanosecond. Refused when secs is negative, NaN or too large for a Duration.
  • fn from_secs_f32(secs : f32) -> Duration
    As from_secs_f64, from an f32.
  • fn mul_f64(self : &Duration, rhs : f64) -> Duration
    This duration times rhs, through f64 seconds.
  • fn mul_f32(self : &Duration, rhs : f32) -> Duration
  • fn div_f64(self : &Duration, rhs : f64) -> Duration
    This duration divided by rhs, through f64 seconds.
  • fn div_f32(self : &Duration, rhs : f32) -> Duration
  • fn div_duration_f64(self : &Duration, other : &Duration) -> f64
    How many times other fits in this duration, as an f64.
  • fn div_duration_f32(self : &Duration, other : &Duration) -> f32
  • fn checked_add(self : &Duration, other : &Duration) -> Option<Duration>
    The sum, or None when it would overflow.
  • fn checked_sub(self : &Duration, other : &Duration) -> Option<Duration>
    The difference, or None when other is longer than this duration.
  • fn checked_mul(self : &Duration, rhs : u32) -> Option<Duration>
    This duration times rhs, or None when it would overflow.
  • fn checked_div(self : &Duration, rhs : u32) -> Option<Duration>
    This duration divided by rhs, rounded down to a nanosecond, or None when rhs is 0.
  • fn saturating_add(self : &Duration, other : &Duration) -> Duration
    The sum, or the longest duration when it would overflow.
  • fn saturating_sub(self : &Duration, other : &Duration) -> Duration
    The difference, or no time when other is longer.
  • fn saturating_mul(self : &Duration, rhs : u32) -> Duration
    This duration times rhs, or the longest duration when it would overflow.
  • fn abs_diff(self : &Duration, other : &Duration) -> Duration
    How far apart the two durations are.

Add for Duration methods

  • fn add(self : Duration, rhs : Duration) -> Duration

Sub for Duration methods

  • fn sub(self : Duration, rhs : Duration) -> Duration

PartialEq for Duration methods

  • fn eq(self : &Duration, other : &Duration) -> bool

Ord for Duration methods

  • fn cmp(self : &Duration, other : &Duration) -> Ordering

PartialOrd for Duration methods

  • fn partial_cmp(self : &Duration, other : &Duration) -> Option<Ordering>

Hash for Duration methods

  • fn hash<H : Hasher>(self : &Duration, state : &mut H) -> i32

core::tuple

The structs tuple syntax stands for, Tuple2 to Tuple6; imported by any module that writes a tuple.

Types

  • struct Tuple2<A, B>
    A pair.
  • struct Tuple3<A, B, C>
    Three values.
  • struct Tuple4<A, B, C, D>
    Four values.
  • struct Tuple5<A, B, C, D, E>
    Five values.
  • struct Tuple6<A, B, C, D, E, F>
    Six values.

Functions

  • fn has_2_elements<A, B>(_t : &Tuple2<A, B>) -> bool
    true, for a tuple of exactly two elements; any other tuple is a type error. A tuple let or match calls one of these, so a pattern with the wrong number of elements is rejected.
  • fn has_3_elements<A, B, C>(_t : &Tuple3<A, B, C>) -> bool
  • fn has_4_elements<A, B, C, D>(_t : &Tuple4<A, B, C, D>) -> bool
  • fn has_5_elements<A, B, C, D, E>(_t : &Tuple5<A, B, C, D, E>) -> bool
  • fn has_6_elements<A, B, C, D, E, F>(_t : &Tuple6<A, B, C, D, E, F>) -> bool

std::alloc

Raw allocation and deallocation.

Functions

  • fn alloc(bytes : i64, align : i64) -> *mut i8
    bytes of uninitialised host memory at align, a power of two, or null when there is none.
  • fn grow(p : *mut i8, old_bytes : i64, new_bytes : i64, align : i64) -> *mut i8
    The block at p, of old_bytes, resized to new_bytes, with its contents and its alignment. Null when there is no memory, and then p is still valid. A null p makes a new block.
  • fn dealloc(p : *mut i8, align : i64) -> void
    Gives back a block from alloc or grow, with the alignment it was made with. Null is a no-op.

std::box

Box<T>, a single-owner heap allocation. Required for recursive types.

Types

  • struct Box<T>
    A single-owner heap allocation, which is what makes a recursive type possible. Dropping it drops the value, then frees the allocation.

Box<T> methods

  • fn new(val : T) -> Box<T>
    Move a value to the heap. Stops the program when there is no memory.

Drop for Box<T> methods

  • fn drop(self : &mut Box<T>) -> void

std::fs

Files and directories.

Types

  • enum OpenMode
    The three file-opening behaviors supported by the current host bridge.
  • struct File
    An open file, held by the Rust core behind an opaque pointer. Closed when it is dropped.

File methods

  • unsafe fn open(path : *const i8, mode : OpenMode) -> File
    Open path with a named mode. Writing creates the file and truncates it; read-and-write creates it and does not truncate. Reading does not create it. A failed open cannot be detected. The returned File holds a null pointer, and Vx cannot compare a raw pointer against null or cast one to an integer (Vx#714) – so a missing file is indistinguishable from an empty one until that closes. Every later call on it answers 0 or -1 rather than doing anything. Unsafe because nothing checks that path points at a NUL-terminated string.
  • unsafe fn read(self : *mut File, buffer : *mut u8, len : u64) -> u64
    Read up to len bytes into buffer and answer how many arrived. Zero means end of file, a null file or buffer, or a read error – the four are not told apart. A short read is normal and is not an error. Unsafe because buffer must have room for len bytes; nothing here checks.
  • unsafe fn write(self : *mut File, buffer : *const u8, len : u64) -> u64
    Write up to len bytes from buffer and answer how many were taken. Zero means a null file or buffer, or a write error, with the same lack of distinction read has. A short write is not an error and the remainder is not retried here.
  • fn seek(self : *mut File, offset : i64, whence : i32) -> i64
    Move the read and write position, answering where it ended up. whence is 0 from the start, 1 from the current position, 2 from the end, as libc’s SEEK_SET, SEEK_CUR and SEEK_END. Answers -1 for a null file, an unknown whence, or a seek the operating system refuses.

Drop for File methods

  • fn drop(self : &mut File) -> void
    Close the file and release it.

C bindings (the native functions this module is built on)

  • fn vx_file_open(c_path : *const i8, mode : i32) -> *mut i8
    The Rust core behind File::open. Null when the open fails.
  • fn vx_file_read(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
    The Rust core behind File::read.
  • fn vx_file_write(ptr : *mut i8, buffer : *const u8, len : u64) -> u64
    The Rust core behind File::write.
  • fn vx_file_seek(ptr : *mut i8, offset : i64, whence : i32) -> i64
    The Rust core behind File::seek.
  • fn vx_file_drop(ptr : *mut i8) -> void
    The Rust core behind dropping a File.
  • fn fopen(path : *const i8, mode : *const i8) -> *mut i8
    C’s fopen, for code that wants a FILE* rather than the Rust-backed File.
  • fn fread(ptr : *mut u8, size : i64, nmemb : i64, stream : *mut i8) -> i64
    C’s fread: the number of whole items read, not the number of bytes.
  • fn fclose(stream : *mut i8) -> i32
    C’s fclose.
  • fn fileno(f : *mut i8) -> i32
    C’s fileno: the descriptor behind a FILE*, for handing to mmap.
  • fn fseek(f : *mut i8, offset : i64, whence : i32) -> i32
    C’s fseek.
  • fn ftell(f : *mut i8) -> i64
    C’s ftell: the current position, which is how the size is found after a seek to the end.
  • fn mmap(addr : *mut i8, len : i64, prot : i32, flags : i32, fd : i32, offset : i64) -> *mut i8
    C’s mmap, declared here so a file can be mapped without importing std::mmap.
  • fn munmap(addr : *mut i8, len : i64) -> i32
    C’s munmap.

std::googletest

Assertions for tests written in Vx.

Types

  • trait GoogletestEq

trait GoogletestEq methods

  • fn expect_eq(self : Self, expected : Self) -> i32
    Answers 0 when this value equals expected. Otherwise prints both and stops the program.

GoogletestEq for f32 methods

  • fn expect_eq(self : f32, expected : f32) -> i32
    Compared exactly, so two values a rounding step apart are reported as different.

GoogletestEq for i32 methods

  • fn expect_eq(self : i32, expected : i32) -> i32
    Compared exactly.

Functions

  • fn expect_eq<T : GoogletestEq>(actual : T, expected : T) -> i32
    actual.expect_eq(expected) written the way a test reads: the value under test first.

std::hash_map

HashMap<K, V>.

Functions (bound directly to C)

  • fn vx_hash_map_new_i32_i32() -> *mut i8
    An empty map from i32 to i32, owned by the Rust core.
  • fn vx_hash_map_insert_i32_i32(ptr : *mut i8, key : i32, val : i32) -> i32
    Insert a value under a key, replacing whatever was there.
  • fn vx_hash_map_get_i32_i32(ptr : *mut i8, key : i32) -> *mut i8
    A pointer to the value under a key, or null when the key is absent. Vx cannot test a pointer against null (Vx#714), so contains_key is the way to ask whether the key is there.
  • fn vx_hash_map_contains_key_i32_i32(ptr : *mut i8, key : i32) -> Bool
    Is the key present?
  • fn vx_hash_map_len_i32_i32(ptr : *mut i8) -> i32
    How many entries the map holds.
  • fn vx_hash_map_drop_i32_i32(ptr : *mut i8) -> i32
    Release the map.
  • fn vx_hash_map_new_i32_f32() -> *mut i8
    An empty map from i32 to f32, owned by the Rust core.
  • fn vx_hash_map_insert_i32_f32(ptr : *mut i8, key : i32, val : f32) -> i32
    Insert a value under a key, replacing whatever was there.
  • fn vx_hash_map_get_i32_f32(ptr : *mut i8, key : i32) -> *mut i8
    A pointer to the value under a key, or null when the key is absent. Vx cannot test a pointer against null (Vx#714), so contains_key is the way to ask whether the key is there.
  • fn vx_hash_map_contains_key_i32_f32(ptr : *mut i8, key : i32) -> Bool
    Is the key present?
  • fn vx_hash_map_len_i32_f32(ptr : *mut i8) -> i32
    How many entries the map holds.
  • fn vx_hash_map_drop_i32_f32(ptr : *mut i8) -> i32
    Release the map.

std::hash_set

HashSet<T>.

Functions (bound directly to C)

  • fn vx_hash_set_new_i32() -> *mut i8
    An empty set of i32, owned by the Rust core.
  • fn vx_hash_set_insert_i32(ptr : *mut i8, val : i32) -> i32
    Add a value. Adding one already present changes nothing.
  • fn vx_hash_set_contains_i32(ptr : *mut i8, val : i32) -> Bool
    Is the value in the set?
  • fn vx_hash_set_len_i32(ptr : *mut i8) -> i32
    How many distinct values the set holds.
  • fn vx_hash_set_drop_i32(ptr : *mut i8) -> i32
    Release the set.

std::io

Standard input, output and error.

Functions

  • unsafe fn stdout_write(buffer : *const u8, len : u64) -> u64
    Write len bytes to standard output and answer how many were taken. A short write is possible and is not retried here. Unsafe because buffer must have len bytes to read.
  • unsafe fn stderr_write(buffer : *const u8, len : u64) -> u64
    Write len bytes to standard error, with the same caveats as stdout_write.
  • unsafe fn stdin_read(buffer : *mut u8, len : u64) -> u64
    Read up to len bytes from standard input and answer how many arrived. Zero means end of input or an error, which are not told apart. Unsafe because buffer must have room for len bytes.

C bindings (the native functions this module is built on)

  • fn vx_stdout_write(buffer : *const u8, len : u64) -> u64
    The Rust core behind stdout_write.
  • fn vx_stderr_write(buffer : *const u8, len : u64) -> u64
    The Rust core behind stderr_write.
  • fn vx_stdin_read(buffer : *mut u8, len : u64) -> u64
    The Rust core behind stdin_read.

std::libc

Direct bindings to the C library.

Functions (bound directly to C)

  • fn open(path : *const i8, flags : i32) -> i32
    C’s open: a file descriptor, or -1 on failure. flags is the platform’s, not Vx’s.
  • fn close(fd : i32) -> i32
    C’s close: 0, or -1 on failure.
  • fn lseek(fd : i32, offset : i64, whence : i32) -> i64
    C’s lseek: the new offset, or -1. whence is 0 from the start, 1 from the current position, 2 from the end.
  • fn malloc(size : usize) -> *mut i8
    C’s malloc: size bytes of uninitialised memory, or null.
  • fn calloc(count : usize, size : usize) -> *mut i8
    C’s calloc: count * size bytes set to zero, or null.
  • fn realloc(ptr : *mut i8, size : usize) -> *mut i8
    C’s realloc: the block resized, and moved if need be. Null when there is no memory, and then ptr is still valid. The result has malloc’s alignment, not the one that aligned_alloc gave.
  • fn free(ptr : *mut i8) -> void
    C’s free: gives back a block from any of these, aligned_alloc included. Null is a no-op.
  • fn aligned_alloc(alignment : usize, size : usize) -> *mut i8
    C11’s aligned_alloc: size bytes at alignment, or null. size must be a multiple of alignment. Microsoft’s C runtime does not have it.

std::llama

Helpers used by the Llama 2 example.

Types

  • struct LlamaConfig
  • struct TransformerWeightOffsets
  • struct Tokenizer

LlamaConfig methods

  • fn load(filepath : *const i8) -> LlamaConfig
    Read a checkpoint’s header. Nothing validates the file: a path that is not a checkpoint gives a config of whatever the first seven words happen to be, and the sizes computed from it are then wrong.

TransformerWeightOffsets methods

  • fn calculate(c : &LlamaConfig) -> TransformerWeightOffsets
    Where each weight matrix begins, as a float offset into one flat buffer. The order is llama2.c’s, and the arithmetic assumes the checkpoint was written by it.
  • fn load_all_weights(filepath : *const i8, c : &LlamaConfig) -> Tensor<f32, [?, ?]>
    Every weight as one 1-by-N tensor, copied out of the mapped checkpoint. The length is computed from c, so a config that does not match the file reads past its end. A copy rather than a view, so the whole model is resident twice while this runs.

Tokenizer methods

  • fn load(filepath : *const i8, vocab_size : i32) -> Tokenizer
    Read a tokenizer file. vocab_size must match the checkpoint’s.
  • fn decode(self : &Tokenizer, prev_token : i32, token : i32) -> String
    The text for one token, as an owned String. prev_token decides whether a leading space is stripped, which is why decoding a token in isolation can differ from decoding it in sequence.

C bindings (the native functions this module is built on)

  • fn vx_load_config(filepath : *const i8) -> *mut i32
    Read the seven header fields of a llama2.c checkpoint into an array of i32.
  • fn vx_load_weights(filepath : *const i8) -> *mut f32
    Map a checkpoint’s weights and hand back a pointer to the first float.
  • fn vx_build_tokenizer(filepath : *const i8, vocab_size : i32) -> *mut i8
    Read a tokenizer file, answering an opaque handle the Rust core owns.
  • fn vx_decode_token(tokenizer_ptr : *mut i8, prev_token : i32, token : i32) -> *const i8
    The text for one token, as a NUL-terminated string. prev_token is needed because llama2’s tokenizer strips a leading space after the beginning-of-sequence token and not otherwise.
  • fn vx_encode_prompt(tokenizer_ptr : *mut i8, text_ptr : *const i8) -> *mut i32
    Encode a prompt, answering an array of token ids with its length in the first slot.
  • fn vx_read_prompt_file(filepath : *const i8) -> *const i8
    Read a whole file as a NUL-terminated string.
  • fn vx_get_llama_config() -> *mut i32
    The configuration of the checkpoint most recently loaded.

std::mmap

Memory-mapped files.

Functions (bound directly to C)

  • fn mmap(addr : *mut i8, length : i64, prot : i32, flags : i32, fd : i32, offset : i64) -> *mut i8
    C’s mmap: map length bytes of fd into memory. Answers MAP_FAILED rather than null on failure, which is -1 cast to a pointer and which Vx cannot test for (Vx#714). prot and flags are the platform’s.
  • fn munmap(addr : *mut i8, length : i64) -> i32
    C’s munmap: unmap a region previously mapped. 0, or -1 on failure.

std::net

TCP and UDP sockets.

Types

  • struct TcpStream
    A connected TCP socket, held by the Rust core behind an opaque pointer. Closed when it is dropped.
  • struct UdpSocket
    A bound UDP socket, held by the Rust core behind an opaque pointer.
  • struct TcpListener
    A listening TCP socket, held by the Rust core behind an opaque pointer.

TcpStream methods

  • unsafe fn connect(addr : *const i8) -> TcpStream
    Connect to addr, written as host:port. /// A failure cannot be detected. The value handed back holds a null pointer, and Vx cannot compare a raw pointer against null (Vx#714), so a refused connection looks like a working one until every later call answers 0. Unsafe because nothing checks that addr points at a NUL-terminated string.
  • unsafe fn read(self : *mut TcpStream, buffer : *mut u8, len : u64) -> u64
    Read up to len bytes into buffer and answer how many arrived. Zero means the peer closed, a null socket, or a read error. A short read is normal: TCP is a stream and a message may arrive in pieces, so a caller wanting a whole one loops. Unsafe because buffer must have room for len bytes.
  • unsafe fn write(self : *mut TcpStream, buffer : *const u8, len : u64) -> u64
    Write up to len bytes from buffer and answer how many were taken. A short write is normal and the remainder is not retried here.

Drop for TcpStream methods

  • fn drop(self : &mut TcpStream) -> void
    Close the connection and release it.

UdpSocket methods

  • unsafe fn bind(addr : *const i8) -> UdpSocket
    Bind to addr, written as host:port. /// A failure cannot be detected. The value handed back holds a null pointer, and Vx cannot compare a raw pointer against null (Vx#714), so a refused connection looks like a working one until every later call answers 0.
  • unsafe fn recv(self : *mut UdpSocket, buffer : *mut u8, len : u64) -> u64
    Receive one datagram into buffer and answer its length. A datagram longer than len is truncated and the rest is lost, which is UDP’s behaviour and not an error here. The sender’s address is not reported.
  • unsafe fn send_to(self : *mut UdpSocket, buffer : *const u8, len : u64, addr : *const i8) -> u64
    Send one datagram of len bytes to addr, answering how many were sent. Nothing guarantees it arrives, or arrives once, or arrives in order.

Drop for UdpSocket methods

  • fn drop(self : &mut UdpSocket) -> void
    Close the socket and release it.

TcpListener methods

  • unsafe fn bind(addr : *const i8) -> TcpListener
    Listen on addr, written as host:port. A failure cannot be detected, for the reason TcpStream::connect gives (Vx#714).
  • fn accept(self : *mut TcpListener) -> TcpStream
    Wait for a connection and answer with it. Blocks until one arrives. The stream handed back holds a null pointer when the accept failed, which cannot be told from a working one either.

Drop for TcpListener methods

  • fn drop(self : &mut TcpListener) -> void
    Stop listening and release the socket.

C bindings (the native functions this module is built on)

  • fn vx_tcp_stream_connect(c_addr : *const i8) -> *mut i8
    The Rust core behind TcpStream::connect. Null when the connection fails.
  • fn vx_tcp_stream_read(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
    The Rust core behind TcpStream::read.
  • fn vx_tcp_stream_write(ptr : *mut i8, buffer : *const u8, len : u64) -> u64
    The Rust core behind TcpStream::write.
  • fn vx_tcp_stream_drop(ptr : *mut i8) -> i32
    The Rust core behind dropping a TcpStream.
  • fn vx_udp_socket_bind(c_addr : *const i8) -> *mut i8
    The Rust core behind UdpSocket::bind. Null when the bind fails.
  • fn vx_udp_socket_recv(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
    The Rust core behind UdpSocket::recv.
  • fn vx_udp_socket_send_to(ptr : *mut i8, buffer : *const u8, len : u64, c_addr : *const i8) -> u64
    The Rust core behind UdpSocket::send_to.
  • fn vx_udp_socket_drop(ptr : *mut i8) -> i32
    The Rust core behind dropping a UdpSocket.
  • fn vx_tcp_listener_bind(c_addr : *const i8) -> *mut i8
    The Rust core behind TcpListener::bind. Null when the bind fails.
  • fn vx_tcp_listener_accept(ptr : *mut i8) -> *mut i8
    The Rust core behind TcpListener::accept. Blocks until a connection arrives.
  • fn vx_tcp_listener_drop(ptr : *mut i8) -> i32
    The Rust core behind dropping a TcpListener.

std::rand

Seeded pseudo-random numbers, one stream per Rng.

Types

  • struct SplitMix64
    SplitMix64: a counter, scrambled. One multiply-xor-shift chain, no rejection, no loop. Its job here is to turn one seed word into the four Rng needs, which is what it was written for. It is a usable generator on its own where 64 bits of state are enough.
  • struct Rng
    A stream of pseudo-random numbers. Seed it, then draw from it. spare holds the second of the pair normal produces, since the polar method makes two normals at once and handing one back would throw half the work away.

Functions

  • fn sqrt_f64(x : f64) -> f64
    The square root, over the math dialect so this module needs no libm. core::num has the same method; this is kept because normal runs before an import of it would settle, and a local one keeps the dependency to core::num alone.

SplitMix64 methods

  • fn seeded(seed : u64) -> SplitMix64
    A generator started at seed. Every seed is valid, including zero.
  • fn next_u64(self : &mut SplitMix64) -> u64
    Advance the counter by the golden-ratio constant, then scramble the value taken. The three constants are 0x9E3779B97F4A7C15, 0xBF58476D1CE4E5B9 and 0x94D049BB133111EB, spelled in decimal because Vx has no hex literal.

Rng methods

  • fn seeded(seed : u64) -> Rng
    A stream from one seed word. Every seed is allowed, zero included.
  • fn next_u64(self : &mut Rng) -> u64
    The next draw, uniform over the whole 64-bit range. Every other method is built on it. The value handed back is computed from the state before the state advances, which is what xoshiro256** specifies; returning the new state instead is a different and worse generator.
  • fn next_u32(self : &mut Rng) -> u32
    The narrower widths take the high bits of a draw. The low bits of a xoshiro draw are the weakest ones, and a % 256 would hand back exactly those.
  • fn next_u16(self : &mut Rng) -> u16
    The next 16 bits.
  • fn next_u8(self : &mut Rng) -> u8
    The next 8 bits.
  • fn next_i64(self : &mut Rng) -> i64
    Uniform over the signed range, negatives included: the bits are reinterpreted, not clamped, so half the draws are below zero.
  • fn next_i32(self : &mut Rng) -> i32
    The next 32 bits read as signed, so negative half the time.
  • fn next_i16(self : &mut Rng) -> i16
    The next 16 bits read as signed.
  • fn next_i8(self : &mut Rng) -> i8
    The next 8 bits read as signed.
  • fn next_f64(self : &mut Rng) -> f64
    Uniform in [0, 1), built from the top 53 bits because that is f64’s mantissa. Taking more would round, and rounding up at the top of the range returns exactly 1.0 – which a caller scaling into a half-open range does not expect.
  • fn next_f32(self : &mut Rng) -> f32
    Uniform in [0, 1), on the same terms with f32’s 24 bits.
  • fn next_f16(self : &mut Rng) -> f16
    Uniform in [0, 1) over an evenly spaced grid: 2048 points at f16 and 256 at bf16, each of them exact at that width. The draw is built from that many bits rather than narrowed from an f32, so every point is equally likely.
  • fn next_bf16(self : &mut Rng) -> bf16
    A bf16 uniform in [0, 1). Eight bits of mantissa, so the draws are coarse.
  • fn next_bool(self : &mut Rng) -> bool
    One bit, from the top of a draw.
  • fn chance(self : &mut Rng, p : f64) -> bool
    True with probability p. Outside [0, 1] it is always false or always true.
  • fn below(self : &mut Rng, bound : u64) -> u64
    Uniform in [0, bound), with no bias. A plain next_u64() % bound is biased whenever bound does not divide 2^64: the first 2^64 % bound values come up once more often than the rest. The draws that would land in that overhang are rejected and taken again. floor is where the overhang ends, and 0 - bound is 2^64 - bound – the subtraction wraps, which is what makes 2^64 expressible in a 64-bit word at all.
  • fn range_i64(self : &mut Rng, lo : i64, hi : i64) -> i64
    Uniform in [lo, hi), lo included and hi not. The span is measured in u64 so that a range spanning zero, or the whole of i64, is still one subtraction: hi - lo in i64 would overflow for the widest of them.
  • fn range_f64(self : &mut Rng, lo : f64, hi : f64) -> f64
    Uniform in [lo, hi). With lo > hi the range runs backwards and the result is in (hi, lo], which is the same arithmetic and rarely what a caller meant.
  • fn range_f32(self : &mut Rng, lo : f32, hi : f32) -> f32
    Uniform in [lo, hi), with the caveat range_f64 gives for a backwards range.
  • fn normal(self : &mut Rng) -> f64
    One draw from the standard normal distribution: mean 0, standard deviation 1. Marsaglia’s polar method. A point is drawn from the square until it lands inside the unit circle (about four tries in five), and that point yields two normals. The second is kept in the struct for the next call, so the loop runs once per two draws. This is the method to reach for when filling something that stands in for model data. Weights, activations and KV entries are roughly Gaussian, and a uniform fill exercises a range of magnitudes real data never has – which matters most in f16, where the interesting failures are overflow and underflow at the tails.
  • fn normal_around(self : &mut Rng, mean : f64, stddev : f64) -> f64
    A normal draw moved and stretched: mean mean, standard deviation stddev.
  • fn fill_f32(self : &mut Rng, out : *mut f32, count : i32) -> i32
    Fill a buffer in place. One call per buffer rather than one per element, which is what makes filling a real tensor practical – a 256x8192 one is two million values. count elements are written, so the buffer must hold that many.
  • fn fill_range_f32(self : &mut Rng, out : *mut f32, count : i32, lo : f32, hi : f32) -> i32
    Fill count elements with draws uniform in [lo, hi). out must have room for count of them; nothing here checks.
  • fn fill_normal_f32(self : &mut Rng, out : *mut f32, count : i32, mean : f32, stddev : f32) -> i32
    Fill count elements with normal draws of the given mean and standard deviation. out must have room for count of them. A negative stddev mirrors the distribution rather than being refused.
  • fn fill_f16(self : &mut Rng, out : *mut f16, count : i32) -> i32
    Fill count elements with f16 draws uniform in [0, 1).
  • fn fill_normal_f16(self : &mut Rng, out : *mut f16, count : i32, mean : f64, stddev : f64) -> i32
    The normal fill at half precision. The draw and the scaling happen in f64 and narrow once at the store, so a tail value is rounded rather than computed twice.

std::simd

SIMD vector types and operations.

Functions

  • unsafe fn simd_add_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The elementwise sum of two four-lane f32 vectors, written to out. All three pointers must address four f32s; nothing here checks. out may alias a or b.
  • unsafe fn simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The elementwise difference of two four-lane f32 vectors, written to out. All three pointers must address four f32s; nothing here checks. out may alias a or b.
  • unsafe fn simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The elementwise product of two four-lane f32 vectors, written to out. All three pointers must address four f32s; nothing here checks. out may alias a or b.
  • unsafe fn simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The elementwise quotient of two four-lane f32 vectors, written to out. All three pointers must address four f32s; nothing here checks. out may alias a or b.
  • unsafe fn simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32
    a * b + c elementwise over four lanes, written to out. Fused, so the product is not rounded before the addition: the answer can differ from a separate multiply and add in the last bit, and is the more accurate of the two.

C bindings (the native functions this module is built on)

  • fn vx_simd_add_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The Rust core behind simd_add_f32x4.
  • fn vx_simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The Rust core behind simd_sub_f32x4.
  • fn vx_simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The Rust core behind simd_mul_f32x4.
  • fn vx_simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
    The Rust core behind simd_div_f32x4.
  • fn vx_simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32
    The Rust core behind simd_fma_f32x4.

std::string

String and text manipulation.

Types

  • struct String
    A growable, owned string, held by the Rust core behind an opaque pointer. Released when it is dropped.

String methods

  • fn new() -> String
    An empty string.
  • unsafe fn from_c_str(c_str : *const i8) -> String
    A copy of a NUL-terminated C string. Unsafe because nothing here checks that c_str is a valid pointer to a NUL-terminated run of bytes. The copy is owned by the new String; the argument is not taken.
  • unsafe fn push_c_str(self : *mut String, c_str : *const i8) -> i32
    Append a NUL-terminated C string, with the same requirement from_c_str has.
  • fn len(self : *mut String) -> i32
    The length in bytes, not in characters: this counts UTF-8 code units.
  • fn as_c_str(self : *mut String) -> *const i8
    A NUL-terminated copy of the contents. This allocates and leaks. The Rust core builds a fresh CString and hands out its raw pointer, so every call costs a copy that is never released: the matching vx_string_free_c_str is declared in the extern block and is not wrapped as a method here. Calling this in a loop grows the process without bound.

PartialEq for String methods

  • fn eq(self : &String, other : &String) -> bool

Drop for String methods

  • fn drop(self : &mut String) -> void

Functions

  • unsafe fn string_length(s : *const i8) -> i32
    The number of bytes before the NUL, found by scanning. Unsafe because it walks until it finds one: a pointer to bytes with no NUL runs off the end. core::str replaces this once a string literal carries its length (Vx#532).
  • unsafe fn string_compare(s1 : *const i8, s2 : *const i8) -> i32
    C’s strcmp: negative when s1 sorts first, positive when s2 does, zero when equal. The magnitude is the difference between the first bytes that differ, which callers should not read anything into beyond its sign. Unsafe for the reason string_length is.
  • unsafe fn parse_int(s : *const i8) -> i32
    The leading run of decimal digits as an i32. Stops at the first byte that is not a digit and answers what it has, so "12abc" is 12 and "abc" is 0 – there is no way to tell that second case from a genuine zero. A leading - is not a sign, it is a stop. Nothing checks for overflow: a run longer than i32 holds wraps. core::str’s parse replaces it (Vx#532).

T methods, stamped for 8 instantiations

  • fn to_string(self : T) -> String
    This number in decimal, as an owned String the caller must drop.

T = i8, i16, i32, i64, u8, u16, u32, u64

C bindings (the native functions this module is built on)

  • fn vx_string_new() -> *mut i8
    The Rust core behind String::new.
  • fn vx_string_from_c_str(ptr : *const i8) -> *mut i8
    The Rust core behind String::from_c_str.
  • fn vx_string_push_c_str(ptr : *mut i8, c_str : *const i8) -> i32
    The Rust core behind String::push_c_str.
  • fn vx_string_len(ptr : *mut i8) -> i32
    The Rust core behind String::len.
  • fn vx_string_eq(a : *mut i8, b : *mut i8) -> i32
    The Rust core behind == on two strings: 1 when they hold the same bytes.
  • fn vx_string_as_c_str(ptr : *mut i8) -> *const i8
    Allocate a NUL-terminated copy and hand out its raw pointer. The caller owns it and releases it with vx_string_free_c_str; String::as_c_str does not, and leaks.
  • fn vx_string_free_c_str(ptr : *const i8) -> i32
    Release what vx_string_as_c_str returned. Nothing in Vx calls this yet.
  • fn vx_string_drop(ptr : *mut i8) -> i32
    The Rust core behind String::drop.
  • fn vx_string_with_len(len : i64) -> *mut i8
    A string of len NUL bytes, for to_string to overwrite with digits.
  • fn vx_string_as_mut_ptr(ptr : *mut i8) -> *mut i8
    The bytes of a string made by vx_string_with_len, writable.

std::tensor

Operations on Tensor, including shape queries and elementwise maths.

Types

  • enum PadMode
    The second argument of t.reshape(shape, mode), for a new shape with a different number of elements. Trim keeps the first elements of t in row order, as a view of t. Pad makes a new tensor that holds all of t’s elements in row order, followed by zeros.
  • trait Float
    A floating-point element type, the only kind these tensor methods are written for.

Tensor<T, [?, ?]> methods

  • fn from_ptr_1d(ptr : *mut T, d1 : i32) -> Tensor<T, [?, ?]>
    A 1-by-d1 tensor holding a copy of d1 elements read from ptr. The elements are copied, so the tensor does not alias the buffer and outlives it. ptr must address d1 elements; nothing here checks.
  • fn from_ptr_2d(ptr : *mut T, d1 : i32, d2 : i32) -> Tensor<T, [?, ?]>
    A d1-by-d2 tensor holding a copy of d1 * d2 elements read from ptr in row-major order. Copied, as from_ptr_1d is.
  • fn slice_2d(self : &Tensor<T, [?, ?]>, row : i32, d1 : i32, d2 : i32) -> Tensor<T, [?, ?]>
    A d1-by-d2 tensor read from one row of this one, taken row-major from its start. A copy, not a view. Every slice_ method here allocates and copies, so writing to the result does not touch the original and the cost is the elements moved, not constant.
  • fn slice_2d_from_1d(self : &Tensor<T, [?, ?]>, start : i32, d1 : i32, d2 : i32) -> Tensor<T, [?, ?]>
    A d1-by-d2 tensor read from row 0 beginning at start, reshaped row-major. A copy.
  • fn slice_1d(self : &Tensor<T, [?, ?]>, start : i32, d1 : i32) -> Tensor<T, [?, ?]>
    A 1-by-d1 tensor read from row 0 beginning at start. A copy.
  • fn fill(self : &mut Tensor<T, [?, ?]>, val : T) -> void
    Set every element to val. Lowered through linalg, with a separate path chosen at compile time for AVX-512.
  • fn copy(self : &mut Tensor<T, [?, ?]>, src : &Tensor<T, [?, ?]>) -> void
    Overwrite this tensor’s elements with src’s. Both must have the same shape; nothing here checks, and a mismatch reads or writes past an end.
  • fn assign(self : &mut Tensor<T, [?, ?]>, val : T) -> void
    Set every element to val, as fill does. The two differ in the linalg form they emit, not in what they mean.
  • fn compare(self : &Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> bool
    Are the two tensors equal element by element? Exact equality, reduced over every element, so two tensors a rounding step apart answer false. A NaN anywhere makes the answer false, including against itself.

Tensor<T, [N, M]> methods

  • fn fill_static(self : &mut Tensor<T, [ N, M ]>, val : T) -> void
    Set every element of a statically shaped tensor to val. The shape is known at compile time here, so the emitted loop has constant bounds.

Tensor<f32, [?]> methods

  • fn softmax_inplace(self : &mut Tensor<f32, [?]>) -> i32
    Replaces each element x with exp(x) / (sum of exp over all elements), so the elements are positive and add up to 1. The largest element is subtracted before exp, which gives the same answer without overflowing. Vector loops, with the exp of tensor_exp_shifted_loop: within 1 ulp of f32::exp.

Tensor<T, [?]> methods, stamped for 4 instantiations

  • fn add_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
    Adds other to this tensor, element by element. Refused for different lengths.
  • fn sub_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
    Subtracts other from this tensor, element by element.
  • fn mul_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
    Multiplies this tensor by other, element by element.
  • fn div_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
    Divides this tensor by other, element by element.
  • fn add_scalar(self : &mut Tensor<T, [?]>, a : T) -> void
    Adds a to every element.
  • fn scale(self : &mut Tensor<T, [?]>, a : T) -> void
    Multiplies every element by a.
  • fn scaled_add(self : &mut Tensor<T, [?]>, a : T, x : &Tensor<T, [?]>) -> void
    Adds a times x to this tensor: BLAS’s axpy. Refused for different lengths.
  • fn map_inplace(self : &mut Tensor<T, [?]>, f : Closure1<T, T>) -> void
    Replaces every element by f of it. A call per element, so the named operations below are faster where one fits.
  • fn abs_inplace(self : &mut Tensor<T, [?]>) -> void
    Replaces every element by its absolute value.
  • fn sqrt_inplace(self : &mut Tensor<T, [?]>) -> void
    Replaces every element by its square root.
  • fn clamp_inplace(self : &mut Tensor<T, [?]>, lo : T, hi : T) -> void
    Holds every element between lo and hi, as clamp on a number does.

T = f32, f64, f16, bf16

Tensor<T, [?]> methods, stamped for 2 instantiations

  • fn sum(self : &Tensor<T, [?]>) -> T
    The sum of the elements. 0 for an empty tensor.
  • fn dot(self : &Tensor<T, [?]>, other : &Tensor<T, [?]>) -> T
    The sum of the products of matching elements. Refused for tensors of different lengths.
  • fn mean(self : &Tensor<T, [?]>) -> T
    The average of the elements. NaN for an empty tensor. The loop of sum, written out rather than calling self.sum(), which the legacy code generator cannot compile.
  • fn norm(self : &Tensor<T, [?]>) -> T
    The Euclidean length: the square root of the sum of the squares. The loop of dot with the tensor on both sides, written out rather than calling self.dot(self), which the legacy code generator cannot compile.

T = f32, f64

Tensor<T, [?]> methods, stamped for 2 instantiations

  • fn sum(self : &Tensor<T, [?]>) -> f32
    The sum of the elements. 0 for an empty tensor.
  • fn dot(self : &Tensor<T, [?]>, other : &Tensor<T, [?]>) -> f32
    The sum of the products of matching elements. Refused for tensors of different lengths.
  • fn mean(self : &Tensor<T, [?]>) -> f32
    The average of the elements. NaN for an empty tensor. The loop of sum, written out rather than calling self.sum(), which the legacy code generator cannot compile.
  • fn norm(self : &Tensor<T, [?]>) -> f32
    The Euclidean length: the square root of the sum of the squares. The loop of dot with the tensor on both sides, written out rather than calling self.dot(self), which the legacy code generator cannot compile.

T = f16, bf16

Tensor<T, [?]> methods, stamped for 4 instantiations

  • fn max(self : &Tensor<T, [?]>) -> T
    The largest element. A NaN is ignored, as max on a number does; negative infinity for an empty tensor.
  • fn min(self : &Tensor<T, [?]>) -> T
    The smallest element. A NaN is ignored; positive infinity for an empty tensor.
  • fn argmax(self : &Tensor<T, [?]>) -> Option<i32>
    The index of the largest element, the first one if several are equal. A NaN is ignored, as max does; None for an empty tensor or one holding only NaN. Two passes: the vector loop of max, then a search for the first element equal to it.
  • fn argmin(self : &Tensor<T, [?]>) -> Option<i32>
    The index of the smallest element, the first one if several are equal. A NaN is ignored; None for an empty tensor or one holding only NaN.

T = f32, f64, f16, bf16

Tensor<T, [?]> methods, stamped for 3 instantiations

  • fn softmax_inplace(self : &mut Tensor<T, [?]>) -> i32
    Replaces each element x with exp(x) / (sum of exp over all elements), so the elements are positive and add up to 1. The largest element is subtracted before exp, which gives the same answer without overflowing.

T = f64, f16, bf16

Tensor<T, [?]> methods, stamped for 2 instantiations

  • fn variance(self : &Tensor<T, [?]>) -> T
    The average squared distance from the mean, dividing by the number of elements n (numpy’s var; divide by n - 1 for the sample variance). It finds the mean first, which is more accurate than the one-pass formula. NaN for an empty tensor.
  • fn std_dev(self : &Tensor<T, [?]>) -> T
    The standard deviation: the square root of variance.

T = f32, f64

Tensor<T, [?]> methods, stamped for 2 instantiations

  • fn variance(self : &Tensor<T, [?]>) -> f32
    As variance on f32, computed in f32.
  • fn std_dev(self : &Tensor<T, [?]>) -> f32
    As std_dev on f32, computed in f32.

T = f16, bf16

Tensor<T, [?, ?]> methods, stamped for 4 instantiations

  • fn add_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
    Adds other to this tensor, element by element. Refused for different shapes.
  • fn sub_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
    Subtracts other from this tensor, element by element.
  • fn mul_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
    Multiplies this tensor by other, element by element (not a matrix product).
  • fn div_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
    Divides this tensor by other, element by element.
  • fn add_scalar(self : &mut Tensor<T, [?, ?]>, a : T) -> void
    Adds a to every element.
  • fn scale(self : &mut Tensor<T, [?, ?]>, a : T) -> void
    Multiplies every element by a.
  • fn abs_inplace(self : &mut Tensor<T, [?, ?]>) -> void
    Replaces every element by its absolute value.
  • fn sqrt_inplace(self : &mut Tensor<T, [?, ?]>) -> void
    Replaces every element by its square root.
  • fn clamp_inplace(self : &mut Tensor<T, [?, ?]>, lo : T, hi : T) -> void
    Holds every element between lo and hi, as clamp on a number does.

T = f32, f64, f16, bf16

Tensor<T, [?, ?]> methods, stamped for 2 instantiations

  • fn sum(self : &Tensor<T, [?, ?]>) -> T
    The sum of every element. 0 for an empty tensor.
  • fn mean(self : &Tensor<T, [?, ?]>) -> T
    The average of every element. NaN for an empty tensor.
  • fn max(self : &Tensor<T, [?, ?]>) -> T
    The largest element. A NaN is ignored; negative infinity for an empty tensor.
  • fn min(self : &Tensor<T, [?, ?]>) -> T
    The smallest element. A NaN is ignored; positive infinity for an empty tensor.
  • fn sum_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The sum along axis: of each column for axis 0, of each row for axis 1.
  • fn mean_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The average along axis: of each column for axis 0, of each row for axis 1.
  • fn max_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The largest element along axis: of each column for axis 0, of each row for axis 1. A NaN is ignored.
  • fn min_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The smallest element along axis: of each column for axis 0, of each row for axis 1. A NaN is ignored.

T = f32, f64

Tensor<T, [?, ?]> methods, stamped for 2 instantiations

  • fn sum(self : &Tensor<T, [?, ?]>) -> f32
    The sum of every element. 0 for an empty tensor.
  • fn mean(self : &Tensor<T, [?, ?]>) -> f32
    The average of every element. NaN for an empty tensor.
  • fn max(self : &Tensor<T, [?, ?]>) -> T
    The largest element. A NaN is ignored; negative infinity for an empty tensor.
  • fn min(self : &Tensor<T, [?, ?]>) -> T
    The smallest element. A NaN is ignored; positive infinity for an empty tensor.
  • fn sum_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<f32, [?]>
    The sum along axis: of each column for axis 0, of each row for axis 1.
  • fn mean_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<f32, [?]>
    The average along axis: of each column for axis 0, of each row for axis 1.
  • fn max_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The largest element along axis: of each column for axis 0, of each row for axis 1. A NaN is ignored.
  • fn min_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
    The smallest element along axis: of each column for axis 0, of each row for axis 1. A NaN is ignored.

T = f16, bf16

Tensor<T, [?, ?]> methods, stamped for 2 instantiations

  • fn matmul(self : &Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> Tensor<T, [?, ?]>
    The matrix product: a new tensor with as many rows as this one and as many columns as other, element [i][j] the sum over k of self[i][k] * other[k][j]. Refused when this tensor’s column count differs from other’s row count.

T = f32, f64

std::time

Clocks and durations.

Functions

  • fn now() -> f32
    Seconds from some fixed point, for measuring how long something took. Only differences between two calls mean anything: the origin is unspecified. An f32 holds about seven digits, so a long-running process loses resolution as the value grows – unix_timestamp is the f64 one.
  • fn sleep(seconds : f32) -> i32
    Pause this thread for at least seconds. It may be longer; it is never shorter.
  • fn unix_timestamp() -> f64
    Seconds since the Unix epoch, as an f64. Wall-clock time, so it can move backwards when the system clock is adjusted; now is the one to measure a duration with.
  • unsafe fn bench_report(name : *const i8, unit : *const i8, value : f32) -> i32
    Report a benchmark measurement in the form the harness collects. Unsafe because name and unit must point at NUL-terminated strings.

C bindings (the native functions this module is built on)

  • fn vx_get_time() -> f32
    The Rust core behind now.
  • fn vx_sleep(seconds : f32) -> i32
    The Rust core behind sleep.
  • fn vx_unix_timestamp() -> f64
    The Rust core behind unix_timestamp.
  • fn vx_bench_report(name : *const i8, unit : *const i8, value : f32) -> i32
    The Rust core behind bench_report.

std::vec

Vec<T>, a growable array.

Types

  • struct Vec<T>
    A growable array of T. It holds a buffer from std::alloc and does its own element loads and stores. The buffer is released when the vector is dropped.
  • struct VecIter<T>
    An iterator over references to a Vec<T>’s elements. It borrows the vector it walks, so the vector cannot be changed while the iterator is used.
  • struct VecIntoIter<T>
    An iterator that owns a Vec<T> and hands out its elements by value. Built by into_iter, which is what for x in v calls. It holds the vector itself rather than a reference to it, as VecIter does: the vector was moved into into_iter, so a reference to it would point at a variable of a function that has returned.
  • struct VecIterMut<T>
    An iterator over mutable references to a Vec<T>’s elements. Built by iter_mut. It borrows the vector mutably, so the vector cannot be used another way while the iterator is used.
  • struct VecMap<T, NewItem>
    The iterator VecIter::map builds: the inner walk plus the function applied to each item.

Functions

  • fn vec_bytes(cap : i64, elem_size : i64) -> i64
    The size in bytes of cap elements of elem_size bytes. Stops the program when the product does not fit in an i64.
  • fn vec_align() -> i64
    The alignment of a buffer: 16 bytes, the most any Vx scalar type needs, until Vx has alignof<T>().
  • fn vec_alloc(elem_size : i64, cap : i64) -> *mut i8
    A buffer for cap elements of elem_size bytes, or null when that is zero bytes.
  • fn vec_grow(ptr : *mut i8, old_cap : i64, new_cap : i64, elem_size : i64) -> *mut i8
    The buffer of old_cap elements resized to new_cap, keeping its contents. A null ptr makes a new buffer. ptr is invalid afterwards.
  • fn vec_check_index(index : i64, len : i64) -> i32
    Stops the program when index is outside 0..len, and prints both to standard error first.

Vec<T> methods

  • fn new() -> Vec<T>
    An empty vector with room for two elements.
  • fn with_capacity(capacity : i32) -> Vec<T>
    An empty vector with room for capacity elements before it has to grow.
  • fn as_mut_ptr(self : &Vec<T>) -> *mut T
    A raw pointer to the first element. Invalidated by anything that grows the vector.
  • fn as_mut_slice(self : &mut Vec<T>) -> &mut T
    A mutable reference to the first element. Named for what it will return once slices exist (Vx#534); today it hands back the first element rather than a (pointer, length) pair, so the length has to be carried separately by whoever reads it.
  • fn leak(self : Vec<T>) -> &mut T
    Gives up the vector without freeing its buffer, and returns its first element as as_mut_slice does. Nothing frees the buffer, so the reference stays valid.
  • fn as_slice(self : &Vec<T>) -> &T
    A reference to the first element, with the caveat as_mut_slice describes.
  • fn push(self : &mut Vec<T>, val : T) -> i32
    Append a value, growing the buffer when it is full. Capacity doubles, from four upwards, so appending n values reallocates about log2(n) times. Any raw pointer or reference taken from this vector is invalidated by a growth.
  • fn get_ref(self : &Vec<T>, index : i32) -> &T
    A reference to the element at index. v[i] reads through this when the element type is not Copy.

    Panics

    When index is negative or not below len. It prints the index and the length, and stops the program.
  • fn set(self : &mut Vec<T>, index : i32, val : T) -> i32
    Overwrite the element at index, dropping the one that was there.

    Panics

    As get_ref does, and for the same reason. Setting past the end does not extend the vector; push is what grows it.
  • fn len(self : &Vec<T>) -> i32
    How many elements are in the vector, which is not its capacity.
  • fn is_empty(self : &Vec<T>) -> bool
    Whether the vector has no elements.
  • fn pop(self : &mut Vec<T>) -> Option<T>
    Removes the last element and returns it, or None when the vector is empty. The capacity stays, so a later push reuses the slot.
  • fn iter(self : &Vec<T>) -> VecIter<T>
    An iterator over references to the elements, borrowing the vector rather than consuming it. .copied() turns it into one over copies.
  • fn iter_mut(self : &mut Vec<T>) -> VecIterMut<T>
    An iterator over mutable references to the elements, through which each can be changed in place.
  • fn get(self : &Vec<T>, index : i32) -> T
    A copy of the element at index. For an element type that is not Copy, get_ref gives a reference instead.

    Panics

    As get_ref does.

Drop for Vec<T> methods

  • fn drop(self : &mut Vec<T>) -> void
    Drop each element, then release the buffer.

Extend<T> for Vec<T> methods

  • fn extend<I : Iterator>(self : &mut Vec<T>, iter : I) -> i32
    Push every item iter has left, in order.
  • fn extend_one(self : &mut Vec<T>, item : T) -> i32
    Push item.

IntoIterator for Vec<T> methods

  • fn into_iter(self : Vec<T>) -> VecIntoIter<T>
    An iterator that takes the vector over and hands out its elements by value.

Default for Vec<T> methods

  • fn default() -> Vec<T>
    An empty Vec, as new makes.

FromIterator<T> for Vec<T> methods

  • fn from_iter<I : Iterator>(iter : I) -> Vec<T>
    A new Vec holding every item iter has left, which the caller owns.

Drop for VecIntoIter<T> methods

  • fn drop(self : &mut VecIntoIter<T>) -> void
    Drop the elements not handed out yet. The vector it holds is dropped next, and its length is set to 0 so that it frees the buffer without dropping any element again.

Iterator for VecIntoIter<T> methods

  • fn next(self : &mut VecIntoIter<T>) -> Option<T>
    The next element, or nothing once the end is reached.

DoubleEndedIterator for VecIntoIter<T> methods

  • fn next_back(self : &mut VecIntoIter<T>) -> Option<T>
    The last element not yet handed out from either end.

ExactSizeIterator for VecIntoIter<T> methods

  • fn len(self : &VecIntoIter<T>) -> i64
    How many elements are left between the two ends.

Iterator for VecIter<T> methods

  • fn next(self : &mut VecIter<T>) -> Option<&T>
    A reference to the next element, or nothing once the end is reached.

DoubleEndedIterator for VecIter<T> methods

  • fn next_back(self : &mut VecIter<T>) -> Option<&T>
    A reference to the last element not yet handed out from either end.

ExactSizeIterator for VecIter<T> methods

  • fn len(self : &VecIter<T>) -> i64
    How many elements are left between the two ends.

Iterator for VecIterMut<T> methods

  • fn next(self : &mut VecIterMut<T>) -> Option<&mut T>
    A mutable reference to the next element, or nothing once the end is reached.

DoubleEndedIterator for VecIterMut<T> methods

  • fn next_back(self : &mut VecIterMut<T>) -> Option<&mut T>
    A mutable reference to the last element not yet handed out from either end.

ExactSizeIterator for VecIterMut<T> methods

  • fn len(self : &VecIterMut<T>) -> i64
    How many elements are left between the two ends.

VecIter<T> methods

  • fn map<NewItem>(self : VecIter<T>, f : Closure1<&T, NewItem>) -> VecMap<T, NewItem>
    An iterator over these elements with f applied to a reference to each.

Iterator for VecMap<T, NewItem> methods

  • fn next(self : &mut VecMap<T, NewItem>) -> Option<NewItem>
    The next element of the inner iterator with f applied.

ExactSizeIterator for VecMap<T, NewItem> methods

  • fn len(self : &VecMap<T, NewItem>) -> i64
    As many as the inner iterator has.

VecMap<T, NewItem> methods

  • fn collect(self : &mut VecMap<T, NewItem>) -> Vec<NewItem>
    Drain the iterator into a fresh Vec, which the caller owns.

2437 functions across 42 modules.

Diagnostic index

Every diagnostic the Vx compiler can emit, with the code it reports and what it means.

Codes are grouped by the compilation stage that raises them, and the group is readable off the number: E1xxx is the parser, E3xxx the type checker, E6xxx the placement and capacity rules, and so on. A W prefix is a warning rather than an error.

This file is generated from src/diagnostic.rs by scripts/tools/gen_error_index.py. Edit the doc comments on the codes there, not this file.

Every code below links to its own page, which carries the message the compiler emits and, where the test suite has one, a program that triggers it.

Contents

Warnings

Reported without stopping the compile. A warning means the program is accepted but something in it is probably not what was intended.

CodeMeaning
W1001Unused variable binding
W1002A function in the program’s own file that nothing calls, directly or through other functions, starting from main.
W1003Unreachable code after return, break, or continue
W1004let mut x where x never changes: it is never assigned, never borrowed &mut, and no method that changes it is called on it. The mut can go.
W1005A let that reuses a name already declared in the same block, so the first value can no longer be named.
W1006&&x, a borrow of a borrow, where &x was probably meant.
W1007An as cast that widens, such as x as i64 when x is an i32. It cannot lose a value, and i64::from(x) says so.
W1008A match arm whose body is empty, such as 1 => {}.
W1009Unused function parameter
W1010An unsafe block with nothing inside it that needs unsafe.
W1013An as cast to the type the value already has, such as x as i32 when x is an i32.
W1014An as cast of a number known while compiling that does not fit the target type, such as 100000 as i8, which gives -96.
W1020*&x, a borrow that is dereferenced at once. It is the same as x.
W1022A transfer into the memory the value is already in. It only makes a copy.
W1023spawn on(Topology::Current): the region runs where the code around it already runs.
W1024Implicit cross-topology transfer inserted via a Relocatable impl (a real data movement happens silently at the use site; write the transfer explicitly to silence). Relocatable answers “may this value move implicitly?” and is keyed on a user type. That is a different question from “what code moves bytes across this hardware edge?”, which is impl Transfer<Memory::A, Memory::B> for Topology::X. Both were called Transfer before Vx#353.
W1025Use of a user-defined topology with no registered descriptor (not declared via Topology <Name> { ... } and not registered by a plugin). Often a typo of a built-in; defaults to host-like placement.
W1026A user-defined topology’s memory is unreachable from the host (no transfer path), so data can never be moved to it. See the topology coherence check.
W1027A declared relaxed transfer edge does not preserve visibility (the seam engine shows a consumer may read stale data). See the topology coherence check.
W1028The working set of a memory space exceeds capacity, but the space is declared overcommit, so the cumulative-budget errors (E6010, and the cross-call E6027/E6028) are downgraded to this warning.
W1029A tensor placed in a memory space that declares a capacity has a dynamic (non- literal) shape, so the capacity check (E6009/E6010) could not run — the placement is unverified. Silence by making the shape static, or bounding it. Emitted only when the destination space actually declares a capacity.
W1030A topology’s device index is not a compile-time constant (GPU[i] for a runtime i), so it cannot be resolved to a device instance and falls back to index 0. Every such spawn therefore targets the same device. Vx models one representative device per declared kind (#284), so a fleet program should index with constants or const generics.
W1031A proof obligation could not be discharged because no SMT solver was available, so the property is unverified rather than proved. Distinct from W1027, which means the solver ran and found a violation. Emitted only under VX_ALLOW_UNVERIFIED; without it a missing solver is an error, because silence used to be indistinguishable from success (Vx#374).
W1033The loop of a spawn region runs on one thread although each pass writes only memory of its own, so its passes could run at the same time. The warning points at what stops it: a call the compiler cannot show writes nothing, inline MLIR, a read of a row another pass writes, bounds not known when compiling, and the like. A region that calls barrier() asks for blocks and threads, so it is warned about whenever it runs on one thread, with the first thing that stopped the split: two threads that may write the same element, a break out of a loop that holds a barrier(), a write to block-shared memory with no barrier() after it, and the like.
W1034The program writes a variable that a closure copied, and uses the closure after the write. The closure keeps the value that it copied, so it does not see the write. A later release makes this an error (K4).

Parser Errors

Raised while turning source text into an AST. The program is not syntactically valid Vx.

CodeMeaning
E1001Unexpected token
E1002Unexpected end of file
E1003Expected identifier
E1004Expected type
E1005Expected expression
E1006Unclosed delimiter (paren/brace/bracket)
E1007Missing semicolon
E1008Missing comma
E1009Invalid operator
E1010Unknown topology variant
E1011Unknown memory space
E1012Unknown element type
E1013Invalid macro invocation syntax

Name Resolution Errors

Raised when a name cannot be resolved to a declaration, or resolves to something of the wrong kind.

CodeMeaning
E2001Undefined variable
E2002Undefined function
E2003Unknown enum
E2004Unknown enum variant
E2005Unknown struct field
E2006Module does not export function
E2007Method not found on type

Type Errors

Raised by the type checker. Vx performs no implicit numeric conversion, so many of these are mismatches that a language with coercion would have silently accepted.

CodeMeaning
E3001Type mismatch in variable declaration
E3002Type mismatch in return
E3003Type mismatch in function argument
E3004Type mismatch in binary operation
E3005Type mismatch in relational operation
E3006Type mismatch in logical operation
E3007If branch type mismatch
E3008Enum payload type mismatch
E3009Enum payload arity mismatch
E3010Function argument count mismatch
E3011Unsupported cast
E3012Type mismatch in struct field initialization
E3013Missing struct field in initialization
E3014Range type mismatch
E3015Trait not implemented
E3016A generic call that leaves one of the callee’s type parameters unbound: no argument fixes it, and no type declared for the result does either. Left alone, the parameter travelled into the instance’s symbol name as its bare letter and sizeof<T>() folded to 8, so a buffer was sized for an element type that was never chosen. Spell the type argument out, as Vec<i32>::new(), or give the result a type, as let v : Vec<i32> = Vec::new();.
E3017Closure argument count or type mismatch
E3018An array literal whose elements are not scalars, are not all of one type, or which is empty. A number literal takes the type of the other elements; a typed value needs as. An array literal lowers to tensor.from_elements, whose element type must be a scalar, so [a, b] for tensors – placed or not – has nothing to lower to, and an empty literal has no element type to give it. Both used to be accepted by the checker (the element type silently stayed at its f32 default) and then crash codegen with an internal error rather than a diagnostic. See Vx#354.
E3019A match arm whose integer literal cannot be represented in the scrutinee’s type. The arm can never be selected, so the program does not mean what it says. Codegen used to parse the literal with a zero fallback, which turned an unrepresentable arm into a comparison against 0 – so the arm fired for scrutinee 0, the most common value there is, with no diagnostic.
E3036A static call to a method several traits supply, where no impl takes the argument types written. Reported instead of an ambiguity (E3035), because ambiguity is not what went wrong: the call named one thing and the arguments ruled every candidate out. The message lists the impls that do exist, which is the edit.
E3020A match that no arm is guaranteed to match. A match over an enum must name every variant or carry a wildcard arm, wherever it sits. The uncovered value falls through, and when every written arm returns, the function falls off its end and hands back whatever was in the return slot. A scrutinee that is not an enum cannot be enumerated, so it is only asked for a wildcard in value position, where the fall-through edge would otherwise have no value to carry – codegen used to paper over that by evaluating the whole match to a constant zero.
E3021An enum variant whose payload is a tensor. A payload is stored into the variant’s tagged-union slot with llvm.insertvalue, which takes primitive operands, and a tensor is a memref descriptor. The AST path dropped such a payload silently: the construction emitted the tag and nothing else, so a program carrying a tensor through an enum compiled, ran, and lost it with no diagnostic. A struct field holding a tensor is the same representational gap in another position.
E3022An extern function whose signature mentions a tensor. A tensor is a memref, and lowering expands a memref parameter into the seven scalars of its descriptor – allocated pointer, aligned pointer, offset, and a size and stride per rank. So fn c_take(t : Tensor<f32, [?, ?]>) -> i32 declares a C symbol taking seven arguments, which is not a signature anyone writes on the C side; the call links by name and passes something the callee never agreed to. Take a raw pointer and build the tensor in Vx (Tensor<f32, [?, ?]>::from_ptr_2d), which is what the corpus already does.
E3023A shaped tensor initialized from a scalar. let a : Tensor<f32, [128, 64]> = 1.0 allocated and filled a whole buffer from something that reads as an assignment, and the two codegen paths disagreed about it: the AST path emitted the allocation and a linalg.fill, the flat path kept the bare constant and handed an f32 to a call expecting a memref. Tensor<T, [..]>::fill(v) is the spelling. The rank-0 wrap (Tensor<f32, []> = 1.0) is a different thing and stays legal.
E3024.shape[i] on a tensor. It answered on any value, not only a tensor, and typed its answer as a rank-0 tensor. extent(i) is the read of a run-time extent.
E3025extent(i) with an index that is not a literal below the tensor’s rank. Rank is static, so the index is checked here rather than read past the descriptor at run time.
E3026A placement query (.topology()) the checker cannot decide. Placement is a fact of the receiver’s type, compared with Some(Topology::..) or None; it has no run-time value.
E3028A function with a non-void return type whose body can complete without returning. Reported here rather than left to codegen, where it surfaced as an MLIR verifier message naming an operation, with no source location.
E3029A type name in a signature that names no declaration. An unknown name in type position parses as a user nominal, so without this a typo – or a type constructor removed from the language – compiled silently and did nothing.
E3030An operator applied to operand types it is not defined on – % on a shaped tensor or on a bool. Refused here because the alternative is worse in both directions: the flat emitter would decline and fall back to a path that cannot lower it either, and an i1 operand would reach arith.remsi and verify.
E3031impl Copy for X where one of X’s fields is a type that moves. Copy promises a value survives being assigned elsewhere, and a field that does not survive it breaks that promise for the whole type – which would be a way to duplicate a tensor, or any other placed value, without saying so.
E3032A chain of generic instantiations that does not end – f<N - 1>() whose base case is never reached. Reported here rather than left to run out of stack, which gave no file, no line and no message.
E3033A comptime block the evaluator could not finish. The block runs during compilation and leaves nothing behind, so one that cannot be run has no meaning – and used to be emitted as ordinary run-time code, which hid the fact entirely.
E3034A comptime block inside another one. The outer block already runs at compile time, so the inner one asks for nothing extra, and nesting them is what made a block’s value depend on evaluating a closure defined inside another block.
E3035A method name that more than one impl block defines for the same type. The impls are kept in a hash map, so which body a call reached used to change from one run of the compiler to the next; refusing the call is the only answer that is the same twice. Naming the trait, as in Display::fmt(&x, f), chooses one impl.
E3037An impl of a trait that declares an associated type does not bind it. The trait’s signatures are written against Self::Item, so with no binding there is nothing to put in their place, and the method’s type becomes whatever the impl happened to write.
E3038type Item = .. in an impl whose trait declares no Item. Usually a misspelling: nothing reads the binding, so it would go on meaning nothing, in silence.
E3039I::Item disagrees with the impl for what I is: an argument bound it to one type and that impl binds Item to another.
E3040I::Item where no impl for what I is binds an associated type named Item: I has no such bound, or the name is misspelled.
E3041A struct field that names I::Item. Fields are laid out from the struct’s parameters alone, so the projection is made a parameter instead, as Map<I, F> does with its closure.
E3042A bound on a type parameter, T : Name, where no trait called Name is declared or imported. Usually a misspelling or a missing import.
E3043A for loop over a reference to a type with no method to iterate it by. The loop calls iter() for for x in &c and iter_mut() for for x in &mut c, as Rust’s collections do, so the type needs the one the loop asks for.
E3044A top-level const whose value is not known while compiling, or does not fit its declared type. A const holds a number or a bool: a literal, or arithmetic on literals and other consts. A const table holds a list of number literals, and is read one number at a time, as TABLE[i].
E3045A number literal that does not fit usize, which holds 0 up to the largest i64.
E3046A for loop over a tensor that is not a variable or a flat array literal, such as one a function call returns, or a nested array literal. The loop reads the tensor by index, so it needs a name: bind the tensor with let first.
E3047A reshape call the compiler cannot check: the new shape has a different number of elements than the tensor (without PadMode::Pad or PadMode::Trim), a size is not known while compiling, or the arguments are not a shape such as [4, 4] and an optional PadMode.
E3048An if with no else whose value is used, as in let u = if c { .. };. When the condition is false there is no branch to give a value. Add an else, or use the if as a statement.
E3049Two impls of the same trait for the same type: impl Describe for Point written twice, in one module or in two. A type has one impl of a trait, so a call could not choose between them. Remove one.
E3050A pattern inside a variant’s payload that is not a name or _, such as the 3 in Val(3) or the Inner::A(v) in Wrap(Inner::A(v)). The compiler does not test such a pattern when it chooses an arm, so the arm would be taken for any payload. Bind the payload to a name and test it in the arm, with an if or another match.
E3051A match pattern that names a variant of another enum than the value matched, such as Other::Red in a match on a Color. Write the variant with the matched value’s enum.
E3052Arithmetic on fp8 or fp4 values (f8e4m3, f8e5m2 or f4e2m1) with no written result type, as in print(a * b). The compiler computes in f32 and rounds once to the written type, so the program must write it: let c : f32 = a * b;. Also += and the other compound operators on these values, and any operator on tensors of them.
E3053An impl of Send or Sync with no unsafe, as in impl Send for Handle {}. The compiler derives both from the fields, and a raw pointer is neither. An impl claims one for a type that holds a raw pointer, and the compiler cannot check the claim. Write unsafe impl Send for Handle {}.
E3054unsafe impl of a trait other than Send or Sync, or of no trait at all. Only those two traits make a claim that the compiler cannot check. Remove unsafe.
E3055A task takes a value that is not Send, or reads a variable whose type is not Sync. A task that moves a value, copies it or writes it needs Send. A task that reads a variable in place shares it with the code around the task, so it needs Sync. The error names the field that makes the type fail.
E3056spawn { .. } outside a tasks { .. } block. The end of the tasks block waits for each task, so a task cannot outlive the variables it uses. A spawn in a closure, or directly in another task, is outside the block too. Put the spawn in a tasks block of its own.
E3057A task handle used in a way other than h.join(), or a spawn that is not the value of a let or a statement of its own. A task must not leave its tasks block, so the program cannot return a handle, store it, or pass it to a function. Write let h = spawn { .. }; and later h.join().
E3058A return, break or continue that leaves a task, or a task that joins another task. The last expression of a task body is its result. A task cannot join another task yet. Join both tasks in the tasks block.
E3059An extern declaration of malloc, calloc, realloc, aligned_alloc or free whose type is not C’s. The compiler calls these functions too, and it can share only a declaration of C’s type. A size is a 64-bit integer: usize, i64 or u64. A pointer is a raw pointer to host memory. free returns nothing.

Borrow/Ownership Errors

Raised by the borrow checker and the linear-type rules. These rule out use-after-move, aliasing violations and lifetimes that outlive what they point at.

CodeMeaning
E4001Use of moved or consumed linear variable
E4002Cannot access mutably borrowed variable
E4003Cannot borrow as mutable (already immutably borrowed)
E4004Cannot borrow (already mutably borrowed)
E4005A value that points into this function’s stack frame would outlive the function: a reference to a local, or a closure that uses one, returned (on its own or inside a struct), stored through a reference, or passed to a call that could store it there. What it points at is gone once the function returns. The same holds inside a function for a block: a variable declared outside a block cannot be given a value that points at a variable declared inside it. Nor can a variable keep a reference into a value with no name, such as make_vec() in let r = &make_vec();, when that value is dropped at the end of its statement.
E4006An assignment through a shared reference: *r = v, r.f = v or r[i] = v where r is a &T. A shared reference only reads; writing needs a &mut T.
E4007A value moved while a borrow of it is still used afterwards: let r = &a; let b = a; followed by a use of r. The borrow would point at a value that has moved away.
E4008A value moved out from behind a reference: let b = *r; where r is a reference to a type that is not Copy. The reference does not own the value, so it cannot give it away; copy or clone it instead.
E4009An assignment to a variable, or a field or element of it, while a & borrow of it is still used afterwards: let r = &x; x = 2; and then a use of r. The borrow would see the value change under it.
E4010A change to a variable declared without mut: assigning it, or a field or element of it, borrowing it &mut, or calling a method that takes &mut self on it. Declare it let mut x, or for a parameter mut x : T.
E4011A view of a tensor (a row q[i] or a field h.t) used where a tensor of its own is held: stored in a variable that already exists (t = q[i]) or a struct field (h.t = q[i], Holder { t : q[i] }), or passed to a function that takes the tensor by value (f(q[i])). A view shares its owner’s memory and is not a copy. Bind it to a new variable with let, pass q[i].clone(), or take the parameter by reference.
E4012A call to the drop method of a Drop impl by hand: x.drop(). drop runs when the value is dropped, so calling it as well would run it twice. Write drop(x), from core::mem, to drop a value early.
E4013A field moved out of a value that must keep it: let x = p.a where p’s type implements Drop, whose drop needs every field, or let x = make().a, where the value has no name and drops all its fields at the end of the statement. Borrow the field (&p.a), clone it, or bind the value to a variable first.
E4014A task that can still run when a variable that it uses ends or changes. A task in a loop that the same pass does not join runs at the same time as the next pass, so it must not write a variable from outside the loop, or read one that the loop writes. A task must not use a variable that ends before its tasks block, such as one of the loop body. Join the task in the same block: let h = spawn { .. }; and then h.join();.

Safety Errors

Raised where an operation needs an unsafe context and does not have one.

CodeMeaning
E5001Unsafe function call outside unsafe block. Tensor<..>::uninit() is one: its elements hold whatever the memory held, so the program says with unsafe that it writes each one before reading it.
E5002Unsafe memory operation outside unsafe block

Topology/Hardware Errors

Raised by the placement and capacity rules — the checks that make Vx different from a single-address-space language. A value in the wrong memory space, a region on the wrong device, a working set that does not fit, or a transfer with no declared route.

CodeMeaning
E6001Topology mismatch in function call
E6002Cannot transfer between memory spaces (no hardware path)
E6003A value is used from a topology that cannot see the memory space it lives in. The diagnostic names the value’s space, the visible set of the topology reading it, and the cost of the transfer that would fix it – so a misplaced handoff (an un-transferred KV cache in a disaggregated prefill/decode split, say) is a compile error that carries its own remedy. A managed: cached space the topology can reach across a declared seam is coherent in hardware and is not reported here.
E6004Transfer violates the boundary contract at a seam (per-seam local-completeness / soundness obligation is sat; a stale read can violate the contract).
E6005A user-defined topology declaration is incoherent: it cannot see its own default memory space (default_space ∉ visibility). See the topology coherence check.
E6006A Memory declaration’s within: hierarchy forms a cycle (a space contains itself).
E6007A Memory sub-space’s capacity exceeds its parent’s capacity (a child cannot be larger than what contains it).
E6008A Memory declaration has a non-positive capacity, bandwidth, or granule.
E6009A statically-shaped tensor placed in a memory space exceeds that space’s capacity.
E6010The working set placed in a memory space (the sum of its tiles) exceeds capacity. Downgraded to W1028 when the space is declared overcommit.
E6011A sub-space’s scope is broader than its parent’s (locality must narrow down within:).
E6012The same Memory or Topology name is declared by two compilation inputs (e.g. a --machine file and the program). Declarations are name-keyed, so one would silently shadow the other and the machine model in force would depend on load order (#281).
E6013A declared transfer edge carries an explicit cost and has one derivable from its endpoints’ bandwidth: figures. An edge gets exactly one cost source, because two answers to “what does this hop cost” is not a model: the compiler routed by the declared number and reported the derived one, and nothing detected the disagreement.
E6014A program stages through host memory while a machine model is in force, and no host was declared. --machine describes an accelerator and says nothing about the machine it hangs off, so the host end of that seam was being reasoned about without anything describing it. --host <file> names one; --host default names the machine compiling the program. A host declares no capacity – host memory is virtual, and a hard limit would reject programs that page rather than fail – so this is about the host being stated rather than assumed, not about a budget.
E6015A structurally invalid transfer lowering (impl transfer A -> B { ... }): the same edge implemented twice in one compilation (which one is in force would be load order), or a lowering with no functions (an empty body cannot move anything, and accepting it would make impl transfer an inert annotation rather than code).
E6016A Topology or Memory declaration whose identity cannot be relied on. Two forms: the declared name shadows a built-in topology (every use of Topology::<Name> resolves to the built-in, so the declaration is silently ignored – including its arch:); or two declared names collide on one dispatch id (custom ids are derived from the name by hashing), in which case which declaration is in force would be hash-iteration order – observed as the same program getting a device image on some runs and not others for a topology, and as a transfer carrying the other space’s capacity and granule for a memory space.
E6017A misuse of the raw:: transfer-lowering primitives (Vx#353): a raw:: call outside an impl transfer body, an unknown primitive name, a tile argument that is not a bare parameter name (the primitives are indexed, not addressed), a store into a tile not held by &mut, or a wrongly typed index/value.
E6018A raw:: bounds obligation (0 <= index < extent) that could not be proven. Prove it with a loop bound or invariant the SMT prover can see, or assert it in an unsafe block – which records the obligation as asserted-not-proven, the same standing an unverified spec: figure has.
E6019raw::barrier() anywhere but a top-level statement of the lowering body. The barrier’s contract requires every lane to reach it; under a conditional or a loop that cannot be guaranteed syntactically, so it is rejected outright (conservative by design – restructure the body so the barrier is unconditional).
E6020raw::async_copy in a lowering for an edge no declared topology equips with a copy engine. The capability lives in the machine file (transfer A -> B copy_engine); using an absent primitive is a compile error, not a fallback.
E6021A violation of the async/synchronization discipline in a lowering body: a destination read while an async_copy into it is still outstanding, a body that ends with copies no async_wait covers, or a lowering for a synchronizing edge whose body does not end with raw::barrier() (the seam obligation of hir/seam.rs: a relaxed publication makes a stale read reachable).
E6022An impl transfer lowering whose edge endpoints are not visible to a topology that declares the edge – the lowering would execute on a part that cannot address the spaces it moves bytes between, which the transfer contract’s space-visibility constraint forbids.
E6023An impl transfer lowering whose declared tile shape is not the shape the transfer at hand actually moves. A lowering is selected by edge, so nothing else relates the two, and the raw:: primitives take their extents from the declaration: a smaller declaration copies part of the tile and leaves the rest uninitialised, a larger one stores past the end (observed as a SIGSEGV).
E6024A proof obligation could not be discharged because no SMT solver was available. Fails the compilation by default: an undischarged obligation is not a proved one, and treating the two alike is what let a missing z3 certify every seam in silence (Vx#374). Set VX_ALLOW_UNVERIFIED=1 to downgrade this to W1031 and compile anyway.
E6025A placement naming a location the machine does not have: a memory space no declared topology holds, written either as the space or as the device that would hold it. The derivation between the two spellings has a like-named fallback, so an undeclared name resolves to a space that exists only in the placement that mentions it.
E6026A tensor whose element type the target hardware cannot represent, placed on it anyway. The machine model states what a device has (dtypes: [f32, f16, ...]); this is the check that a placement stays inside it. An H100 has no fp4, so an fp4 tensor placed on one asks for silicon that is not there – and the placement is in the type, so the question is answerable here rather than at a kernel launch on the machine that lacks the type. Only fires against a topology that declares dtypes:. An undeclared machine constrains nothing, which is what keeps every machine file written before the field kept working.
E6027A working set that overflows a space only across call boundaries: the peak along some call path – what each caller still holds when it calls, plus the deepest callee’s own peak – exceeds the space’s declared capacity, while every function on the path fits by itself (that case is E6010’s). Computed by folding per-function capacity summaries over the call graph, after the per-function checks. Downgraded to W1028 when the space is declared overcommit.
E6028A recursive cycle that places tiles in a space with a declared capacity. The recursion depth is not known at compile time, so the true peak is unbounded and the placement is refused conservatively. Downgraded to W1028 when the space is declared overcommit.
E6029A pointer argument into one memory passed to a parameter that wants a pointer into another: a host *mut f32 where *mut f32 in Memory::GPU_HBM is declared, or the reverse. The two are different types, and the C library or kernel behind the parameter reads the memory the declaration names, so the spaces must match exactly.
E6030A spawn region on a device hands out a tensor it makes, which the host could not free. The compiler makes such a tensor before the region instead, in the device’s memory, when the region hands it out as a variable created with Tensor<..>(..), ::new() or unsafe { ..::uninit() }, with no type annotation, a size that uses nothing the region computes, and a name nothing outside the region already uses.
E6031A tensor shape that folds to a negative extent, or whose size is more than 2^63 - 1 bytes, the most an allocation can ask for. The size is known, and it is not one the generated code can allocate: it would wrap.
E6032An across loop that the topology or its place cannot have: a topology with no levels: or one whose arch cannot launch them (without -fauto-schedule-levels), a level the topology does not name, a loop outside a spawn on region or in the wrong order, a size that is not known when the kernel is compiled or is over the limit, or a jump out of the loop. Also a lane reduction (lanes::sum and the others) outside the code of a loop of blocks or subgroups, or with a tensor that is too large.
E6033An index into a tensor in a scope: thread space that is not known when compiling. Such a tensor stays in registers, and a register has no address. So each index must be a constant, or made of constants and the variables of for loops with constant bounds, at most 1024 passes each. The code generator unrolls those loops.
E6034An aligned(N) in a tensor type that the compiler cannot give: the space is not one whose storage the compiler lays out (scope: sm, cta or thread), or N is less than the alignment: of the space, which every tensor there has.

Tensor/Math Errors

Raised on tensor shapes and numeric operations, including shape mismatches that are decided at compile time.

CodeMeaning
E7001Matmul dimension mismatch
E7002Matmul element type mismatch
E7003Reshape arithmetic mismatch
E7004Non-differentiable return type (autodiff)

Contract/Verification Errors

Raised when a requires, ensures or invariant clause cannot be discharged, or when a seam obligation is left unproven.

CodeMeaning
E8001Cannot prove postcondition
E8002Comptime assert failed
E8003Compile-time index out of range
E8004Compile-time evaluation exceeded the call-depth limit
E8005Compile-time evaluation ran more loop iterations than the budget allows. A loop whose end condition is never reached is the usual cause; without this it hung the compiler.
E8006A call does not meet the called function’s precondition (requires)
E8007An access clause that does not hold: an atomic(t) whose body uses t without an atomic operation, or a writes_only() whose body may write memory

164 diagnostics.

The compiler

Actions

vxc runs one action per invocation. The default is run-jit.

FlagWhat it does
--runCompile and execute immediately, propagating the program’s exit code
-cCompile to an object file
--emit-mlirEmit the MLIR representation
--emit-llvmEmit LLVM IR
--print-astParse and typecheck, then print the AST
--parse-onlyLex and parse only
--emit-interfaceSerialize this module’s import interface to a .vxlib
vxc --run program.vx
vxc -c program.vx -o program.o
vxc --emit-mlir program.vx

Optimization is -O0 through -O3, defaulting to -O0.

Hardware and machine models

FlagWhat it does
--machine <FILE>Compile against a declared machine — see machine files
--host <FILE|default>Declare the host the program runs on
--diagnostics-json [PATH]Write the admission verdict as one structured JSON record
--verify-seamsDischarge asynchronous-visibility obligations with z3

--diagnostics-json is the one to reach for in a build script or a CI job. The schema is versioned, and a capacity rejection carries the space, the requirement, the availability and the margin as fields rather than as prose you would have to parse out of a message.

Note that with --verify-seams and no solver on PATH, the compile fails rather than certifying seams it could not check. VX_ALLOW_UNVERIFIED=1 downgrades that to a warning.

Where imports come from

An import that starts with a package name reads only that package (see below). vxc looks for any other import in two places, in this order:

  1. The standard library. An installed toolchain keeps it in stdlib/ beside bin/, and a development build uses the stdlib/ of the source tree it was built from. --std-path DIR replaces it. Give the flag once for each directory.
  2. The directory of the file you compile, so import helper; reads the helper.vx beside it.

vxc never searches the current directory, so a command gives the same result wherever you run it.

Packages

--package name=dir makes a library in another directory importable by name. With --package textkit=../textkit, import textkit::runs reads ../textkit/runs.vx. Imports that start with textkit are read from that directory only, and the stdlib paths stay as they are. Give the flag once for each package.

vxc --package textkit=../textkit main.vx -o main

Separate compilation

--emit-interface writes a .vxlib: the module’s frozen registry and portable flat-HIR bodies. A downstream compile consumes it with --link-interface and resolves calls into that module without parsing its source.

vxc --emit-interface lib.vx -o lib.vxlib
vxc --link-interface lib.vxlib main.vx -o main

The .vxlib format carries a version tag, and a compiler rejects artifacts written by a different one. Regenerate them when you upgrade the toolchain rather than keeping them in a cache.

Version numbers

vx-api diff compares the interfaces of two versions of a library and prints the smallest safe version bump. An added item needs a minor release. A removed item, or a change that breaks a caller, needs a major one. Anything else is a patch.

vx-api diff old/lib.vxlib new/lib.vxlib

Every item in a .vxlib counts as public, because a program can call any of them. The tool cannot see traits, data-carrying enums, constants or the bounds of generic parameters, because a .vxlib does not hold them.

The other tools

ToolPurpose
vx-formatThe canonical source formatter
vx-optMLIR pass driver for the Vx dialect
vx-analyzerLanguage server
vx-apiCompares the interfaces of two versions of a library and reports the version bump
cargo vx-benchRuns the programs in benchmarks/manifest.txt and collects the times they report; cargo vx-bench compare runs the Vx-against-C++ suite in benchmarks/stdlib

vx-format has no options worth learning: there is one canonical style, and it applies it.

vx-format src/*.vx

The standard library

21 modules, imported as std::<name>. Every type and function is listed in the standard library reference, generated from the sources.

Coreoption, result, box, alloc, closure, iter
Collectionsvec, hash_map, hash_set, string
Numericsmath, simd, tensor
Systemio, fs, net, mmap, time, libc
Testinggoogletest

Beyond std the toolchain ships graph, imported as graph::traversal and friends.

The repository also carries examples/llama.vx, a Llama 2 inference port, and an early packages/vx_linalg. Several other package directories exist under packages/ but are still empty placeholders — do not plan around them yet.

Environment variables

VariableEffect
VX_RUNTIME_LIB_DIRWhere to find the Vx runtime library
LLVM_CONFIG_PATH, MLIR_TRANSLATE_PATH, OPT_PATH, LLC_PATH, CLANG_PATHAbsolute paths to the LLVM tools, if they are not on PATH
VX_DISPATCH_LIBThe accelerator dispatch backend to load
ENZYME_LIBThe Enzyme plugin, for autodiff
VX_ALLOW_UNVERIFIEDDowngrade an undischarged seam obligation to a warning

An installed toolchain sets the first several of these for you through a wrapper script, so you normally need none of them.

Editor support

VS Code

The extension lives in vscode-vx/ in the repository. It provides syntax highlighting and connects to the language server.

Until it is published to the marketplace, install it from source:

cd vscode-vx
npm install
npm run package
code --install-extension vx-*.vsix

The language server

vx-analyzer speaks the Language Server Protocol, so any LSP-capable editor can use it. It ships with the toolchain, at ~/.vx/bin/vx-analyzer for a standard install.

Point your editor’s LSP client at that binary for files with the .vx extension. In Neovim with nvim-lspconfig, for example, that is a cmd of { "vx-analyzer" } and a filetypes of { "vx" }.

Formatting

vx-format is the canonical formatter. There is one style and no configuration:

vx-format path/to/file.vx

It rewrites files in place. Wire it to format-on-save in your editor, and run it over a directory before committing — the project’s CI checks that tracked .vx files are formatted.

Contributing

Vx is early. The most valuable contribution right now is a clear bug report: a program that should compile and does not, or one that compiles and does the wrong thing.

Reporting a bug

Open an issue at github.com/vx-lang/Vx/issues with:

  • the smallest .vx file that reproduces it,
  • the exact command you ran,
  • what you expected and what happened,
  • vxc --version and your platform.

If the compiler produced MLIR, --emit-mlir output is often the fastest way to show what went wrong.

Your first change

Issues labelled good first issue are self-contained and do not assume you know the compiler. Each one states the problem, shows the current behaviour, and says what the fix should look like.

A sample of what is open:

IssueWhat it is
Vx#494The unused-variable warning fires on a variable used only through method calls
Vx#445Twelve warning codes are declared but never emitted
Vx#423Issue numbers in code comments, against a rule that forbids them

If one of these is unclear, say so on the issue. A first issue that cannot be picked up cold is a bug in the issue, not in you.

help wanted holds larger pieces that are still well specified.

Signing the CLA

Your first pull request needs a signed Contributor License Agreement. It grants the project permission to keep distributing your work under Apache 2.0 with LLVM Exceptions. You keep the copyright to everything you write.

You do not have to do anything in advance. Open the pull request, and a bot will comment with the one sentence to reply with. It asks once; later pull requests from the same account go straight through.

A person is the author of every commit

Every commit in a pull request must have a person as its author, using an email address linked to that person’s GitHub account. That person signs the CLA and is responsible for the whole change.

You may use AI tools. Credit one with a Co-authored-by: line at the end of the commit message, not as the commit’s author. Some tools, such as Cursor’s agent, make commits under their own account; the CLA check then fails, because that account cannot sign. To make yourself the author of your last commit, run:

git commit --amend --reset-author --no-edit
git push --force-with-lease

For every commit on your branch, run this instead of the first line:

git rebase --exec "git commit --amend --reset-author --no-edit" origin/main

Working on the compiler

Start with building from source. Once cargo test passes you have a working development setup.

For how the compiler is put together — the phase pipeline, the two code generators, how to add a language feature, and how the test tiers work — read the developer guide.

The repository layout:

src/                the vxc compiler (Rust)
  lexer, parser/    source to AST
  syntax/           AST, types, topologies, declarations
  hir/              lowering, type checking, borrow checking
    check/          transfer, calls, access, autodiff, region traffic
    memory.rs       the memory algebra: containment, capacity, derived cost
    seam.rs         asynchronous-visibility contracts
    flatten.rs      the flat, AST-annihilated path
  codegen/          MLIR emission
  dialect/          the Vx dialect and its lowering (C++)
  plugin/           vendor MLIR pass plugins
fleet/              machine files
runtime/            dispatch and the distributed fleet runtime (C++)
stdlib/             the standard library
vx-analyzer/        language server
tests/              integration suites and unit tests

Conventions worth knowing

No locks in the compiler. A compilation has to be isolated, and CI rejects Mutex, RwLock, OnceLock and friends anywhere under src/. Process-global atomics are banned for the same reason: a static that a worker thread can write is shared mutable state whether or not it takes a lock to do it, and a cached answer survives across compilations in a long-lived process such as the language server.

Determinism is gated, not hoped for. Several tests assert that the pipeline emits byte-identical MLIR at one thread, at four, and with the thread pool off the path entirely. If your change makes output depend on iteration order, those will catch it.

Format before you commit. cargo fmt for Rust, vx-format for .vx files, clang-format for C++. CI checks all three, and clippy runs with -D warnings.

Tests come with the change. New backend behaviour needs a fixture under tests/; the check lines are generated by utils/update_mlir_test_checks.rs rather than written by hand.

Extending the compiler for new hardware

Vendors are not expected to patch the compiler. A new accelerator arrives as:

  1. a machine file declaring its memory hierarchy and interconnect, and
  2. an MLIR pass plugin under src/plugin/ that lowers the Vx dialect for that target.

docs/adding_a_topology.md walks through the process.