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:
| Check | What it rules out |
|---|---|
| Address-space typing | Dereferencing a device pointer from the host |
| Capacity admission | A placement whose working set cannot fit the space it targets |
| Seam contracts | Reading a buffer whose asynchronous transfer has not been made visible |
| Linear types | Use-after-move of a consumed buffer |
| Borrow checking | Aliasing and lifetime errors, with region tracking |
| Topology reachability | A 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
| Platform | Status |
|---|---|
| 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 macOS | Not supported — the runtime assumes Apple Silicon. |
| Linux arm64, Windows | No 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
- Your first program — write something real.
- A tour of Vx — the language in one sitting.
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
| Requirement | Why |
|---|---|
| LLVM/MLIR 22 | Pinned 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 binary | Seam verification executes it. The library alone is not enough. |
| libffi | The 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
- Ownership and borrowing — the memory model.
- Generics and traits — abstraction without runtime cost.
- Topologies and memory — the reason Vx exists.
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, notuse.use core::option;isimport core::option;. An import brings in everything the module declares.pubexports a symbol. Every top-level item is visible to a module that imports it, with or withoutpub. Apub fnis 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)isfn log(x : i32) -> void, andmainreturnsi32:fn main() -> i32 { .. return 0; }. selfhas a type.fn get(&self)isfn get(self : &Self),&mut selfisself : &mut Self, andselfisself : Self. The type’s own name works too:self : &Point.- Closure parameters have types.
|x| x + 1is| x : i64 | x + 1. - No
impl Traitparameters.fn show(g : &impl Area)isfn show<T : Area>(g : &T).
Types and values
- No turbofish.
Vec::<i32>::new()isVec<i32>::new(). - An enum variant is written with its type.
Some(3)isOption<i64>::Some(3), andNoneisOption<i64>::None().Optioncomes fromimport core::option;. - An integer variable with no written type is
i32. Rust readslet mut total = 0;as ani64wheni64s are added to it later. Vx does not, so writelet mut total : i64 = 0;. - Arrays are tensors.
[i32; 4]isTensor<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.MaybeUninithas no equivalent. Writeunsafe { Tensor<f32, [4]>::uninit() }and set every element before reading it, or useTensor<f32, [4]>::new(), which is zeroed.
Control flow
- A
matcharm’s body is a block.0 => 7,is0 => { 7 },. - No
if letorwhile let. Usematch. - No
?operator. Usematchand 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!andeprintln!do the same on standard error.- There is no function called
println:println(x)is an error. Writeprintln!("{}", 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.unsafeallows raw pointers and calls tounsafe 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 — who owns a value, and who may look at it
- Contracts and verification —
requires,ensures,assert - Generics and traits — writing code once for many types
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 anifor amatchborrows every tensor it may come from, and a view taken through a reference (rq[1]withrq = &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
mutis a&mutborrow, and its owner must bemut(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
mutview, 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 withletinstead. 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 oft’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:
| Case | Issue |
|---|---|
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
Vecof numbers, is freed after its last use. - A
Dropimplementation: a value whose type implementsDropwaits for the end of its block, and values in one block are dropped in reverse order of declaration. Itsdropruns 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
- Generics and traits — the rest of the generic system
- Contracts and verification — other things checked before the program runs
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 givesx == e(alet mutgives nothing, sincexmay change);assert(c, ..), which givescafter theassert;- the condition of an
if: thethenbranch knows it is true, theelsebranch knows it is false; - a loop’s
invariant, inside the loop, anda <= i && i < binsidefor 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
matcharm 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 alet mutgives no fact, aninvariantabout 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
- Control flow — where
invariantattaches - Ownership and borrowing — the other thing checked at compile time
- Unsafe and FFI — what the checks do not cover
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:
| Type | Meaning |
|---|---|
*const T | A raw pointer you may read through. |
*mut T | A 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
- Ownership and borrowing — the checks
unsafesteps around - Topologies and memory — the checks it does not
- Standard library reference — which
stdfunctions areunsafe, and why
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 have | Use |
|---|---|
| Many inputs, one output (a loss) | vjp |
| One input, many outputs | jvp |
| A scalar function of a scalar | grad |
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
- Compile-time evaluation — the other thing that happens before the program runs
- Topologies and memory — running the result on an accelerator
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
| Type | Bits | Values | Infinity | NaN | Status |
|---|---|---|---|---|---|
f16 | 16 | up to 65504, about 3 decimal digits | yes | yes | works |
bf16 | 16 | the range of f32, about 2 decimal digits | yes | yes | works |
f8e4m3 | 8 | up to 448 | no | yes | declaration only (#1874) |
f8e5m2 | 8 | up to 57344 | yes | yes | declaration only (#1874) |
f4e2m1 | 4 | 0, 0.5, 1, 1.5, 2, 3, 4, 6, and their negatives | no | no | declaration only (#1874) |
f8e8m0 | 8 | powers of two, 2⁻¹²⁷ to 2¹²⁷ | no | yes | planned (#1874), block scales only |
i4 | 4 | -8 to 7 | works | ||
u4 | 4 | 0 to 15 | works |
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.
| Format | Values per block | Scale per block | Scale per tensor |
|---|---|---|---|
| NVFP4 | 16 | f8e4m3 | one f32 |
| MXFP4 | 32 | f8e8m0, a power of two | none |
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 onis a statement. The design intends it to become an expression yielding aFuture, so a host thread could fan work across several accelerators and join later. There is no future type and noawaitin 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:
GB | 10⁹ bytes |
GiB | 2³⁰ bytes |
TB/s | 10¹² 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:
- 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.
- 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 thembindsyscall. 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:
| Placement | Bandwidth | vs interleaved |
|---|---|---|
| First-touch (naive default) | 96.8 GB/s | 0.54× |
numactl --interleave=all | 180.5 GB/s | 1.00× |
| Placed per node | 242.1 GB/s | 1.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
- Machine files — the full field reference for
MemoryandTopology - Carrying facts across boundaries — the other six places a fact crosses a boundary rather than being re-derived
- Topologies and memory — how placement and reachability are checked
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() -> i32is 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 returnsvoid.struct Point { x : i64, y : i64 }, built withPoint { x : 1, y : 2 }.enum Shape { Square(i64), Rect(i64, i64) }, built withShape::Square(3).trait Describe { fn size(self : &Self) -> i64; }. A trait method can have a default body.impl Point { .. }andimpl Describe for Shape { .. }. Each method writes its receiver’s type:self : &Self,self : &mut Selforself : Self. Inside an impl,Selfis 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 asname!(..).extern "C" { fn abs(x : i32) -> i32; }declares a C function.
Types
- Integers
i4i8i16i32i64,u4u8u16u32u64; floatsf16bf16f32f64;bool. The fp8 and fp4 types are in Low-precision numbers. Nothing converts by itself: writex as i64. An integer variable with no written type isi32. - Tuples
(i64, f32), read ast.0or taken apart withlet (a, b) = t;. - References
&Tand&mut T, checked by the borrow checker; raw pointers*const Tand*mut T, used insideunsafe. Tensor<f32, [2, 3]>: the shape is part of the type.?is a size known only at run time,Tensor<f32, [?, ?]>, read witht.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>andResult<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
-> !, likepanic("..").
Statements and expressions
let x = 1;andlet mut y : i64 = 2;. Variables are read-only unlessmut.if c { .. } else { .. }, which is also an expression:if c { 1 } else { 2 }.for i in 0..n { .. }(nnot included),while c { .. },loop { .. }, withbreakandcontinue.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== != < <= > >=,&&||!onbool, and+=,-=,*=and the like. - Closures:
| x : i64 | x * 2. Parameter types are required. unsafe { .. }allows raw pointer use, calls tounsafe 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!andeprintln!do the same on standard error.panic("message")stops the program with a message on standard error, andabort()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 methodsmap,filter,zip,enumerate,chain,take,skip,rev,fold,sum,count,min,max,any,all,find,nth,collectand 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/*.vxandstdlib/std/*.vxbyscripts/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—core::cell—core::clone—Clone, an explicit duplicate of a value.core::cmp— Ordering and equality:PartialEq,Ord,PartialOrdandOrdering.core::convert—From, the conversions that cannot fail and lose nothing.core::default—Default, the value a type starts from.core::fmt— Writing integers as text, with the width, fill and flags of a Rust format spec.core::hash—core::hint—core::iter::adapters— The iteratorsIterator’s adaptor methods build:Map,Filter,Chainand the rest.core::iter::traits— TheIteratortrait: one requirednext, and the methods written over it.core::iter—Rangeandrange; importing it brings the trait and the adaptors too.core::libm—core::marker— The traits that say something about a type without giving it a method.core::mem— Moving values around without looking at what they are.core::num::float_to_decimal—core::num— The integer and float methods, stamped over every width.core::ops— The callable types a closure literal lowers into.core::option—Option<T>, for a value that may be absent.core::panic—core::ptr— Raw pointers: making one, and reading or writing through it.core::result—Result<T, E>, for an operation that may fail.core::sync::atomic—core::time— Clocks and durations.core::tuple— The structs tuple syntax stands for,Tuple2toTuple6; imported by any module that writes a tuple.std::alloc— Raw allocation and deallocation.std::box—Box<T>, a single-owner heap allocation. Required for recursive types.std::fs— Files and directories.std::googletest— Assertions for tests written in Vx.std::hash_map—HashMap<K, V>.std::hash_set—HashSet<T>.std::io— Standard input, output and error.std::libc— Direct bindings to the C library.std::llama— Helpers used by the Llama 2 example.std::mmap— Memory-mapped files.std::net— TCP and UDP sockets.std::rand— Seeded pseudo-random numbers, one stream perRng.std::simd— SIMD vector types and operations.std::string—Stringand text manipulation.std::tensor— Operations onTensor, including shape queries and elementwise maths.std::time— Clocks and durations.std::vec—Vec<T>, a growable array.
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 thisAtoZ(65 to 90)?fn is_ascii_lowercase(self : u8) -> bool
Is thisatoz(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 this0to9(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, orAtoF(65 to 70), oratof(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>
ATthat a shared reference can write through.
SharedCell<T> methods
fn new(v : T) -> SharedCell<T>
A new cell holdingv.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 needsunsafe.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 overT : Clonehas to be able to call it.fn clone_from(self : &mut Self, source : &Self) -> void
Replace this value with a duplicate ofsource. A default written overclone. Override it for a type that can overwrite itself more cheaply than it can build a fresh copy; nothing incoreneeds 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 Orderingtrait PartialEqtrait Eq
Equality that is reflexive: every value equals itself. A marker, since Vx has no supertraits yet to tie it toPartialEq. The floats do not implement it, because a NaN is not equal to itself.trait Ordtrait 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, andf32implements this and notOrdfor that reason.fn ne(self : &Self, other : &Self) -> bool
Are the two values different? The negation ofeq, 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, andPartialOrdis 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:otherwhen they compare equal. A method rather than a free function, because the compiler reads the bare namesmaxandminas the tensor reductions (Vx#223).fn min(self : Self, other : Self) -> Self
The lesser of the two:selfwhen they compare equal.fn clamp(self : Self, lo : Self, hi : Self) -> Self
This value brought inside the range, solobelow it andhiabove it.Panics
Whenlois greater thanhi, 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 answersSome, and the integer widths implement it that way so a body written overPartialOrdworks for every number.
Functions
fn partial_lt<T : PartialOrd>(a : &T, b : &T) -> bool
a < bfor aPartialOrdtype: false when the two do not compare.fn partial_le<T : PartialOrd>(a : &T, b : &T) -> bool
a <= bfor aPartialOrdtype: false when the two do not compare.fn partial_gt<T : PartialOrd>(a : &T, b : &T) -> bool
a > bfor aPartialOrdtype: false when the two do not compare.fn partial_ge<T : PartialOrd>(a : &T, b : &T) -> bool
a >= bfor aPartialOrdtype: 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 byf, answeringbwhen they compare equal. A free function because it takes the comparison rather than reading it off the type. The plainmaxandminareOrdmethods 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 byf, answeringawhen 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 keyfgives each, answeringbon 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 keyfgives each, answeringaon 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 aT, losing nothing. A static method, called through the target:i32::from(x). Only implemented where every value ofTfits, so there is no failure to report; the narrowing direction isTryFrom’s, which Vx does not have yet.
From<T> for Option<T> methods
fn from(v : T) -> Option<T>
The value wrapped inSome.
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(), notx.default(). A struct gets one by writing the impl; the language does not derive it.fn default() -> Option<T>
None, whateverTis: 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. Onlywrite_u8is 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 withFormatSpec::new()and the setters below.struct ByteBuf
A writer over caller-owned memory:ptrholdscapbytes. The bytes written so far are always followed by a NUL, soas_c_strcan be printed; that leaves room forcap - 1. A byte that does not fit is refused withError, 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 markedunsafeyet 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 leastnbytes.fn fill(self : FormatSpec, b : u8) -> FormatSpec
The*of Rust’s{:*>8}: pad with the bytebinstead 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’s0.fn upper(self : FormatSpec) -> FormatSpec
Rust’sXin place ofx.
Functions
fn write_repeated<W : Write>(w : &mut W, b : u8, n : i64) -> Result<i32, Error>
Writebtowntimes.fn digit_count(x : u64, radix : u64) -> i64
How many digitsxhas in baseradix.fn write_digits<W : Write>(w : &mut W, x : u64, radix : u64, upper : bool) -> Result<i32, Error>
Write the digits ofxin baseradix, most significant first.pstarts at the largest power ofradixthat is at mostx, found by multiplying up while the next power still fits underx, 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 the0b/0o/0xprefix, if asked for.fn alignment_code(a : Alignment) -> u8
The numberFormatSpec::alignstores fora.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 baseradix(2 to 36), padded asspecasks. This is Rust’sFormatter::pad_integral, with the digits made here.
ByteBuf methods
unsafe fn new(ptr : *mut i8, cap : i64) -> ByteBuf
Unsafe because nothing checks thatptrholdscapbytes.fn len(self : &ByteBuf) -> i64
How many bytes have been written.fn byte(self : &ByteBuf, i : i64) -> u8
The byte ati, which must be belowlen.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}whenspecasks 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}whenspecasks for upper case.
T = u8, u16, u32, u64, usize
core::hash
Types
trait Hashertrait Hashstruct FxHasher
rustc’s hasher: fast, not resistant to a chosen-key attack.struct SipStatestruct SipHasher13
SipHash-1-3: one round per block, three to finish. Bytes are gathered intobufand 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) -> i32fn finish(self : &Self) -> u64fn write_u16(self : &mut Self, v : u16) -> i32fn write_u32(self : &mut Self, v : u32) -> i32fn write_u64(self : &mut Self, v : u64) -> i32fn write_i8(self : &mut Self, v : i8) -> i32fn write_i16(self : &mut Self, v : i16) -> i32fn write_i32(self : &mut Self, v : i32) -> i32fn write_i64(self : &mut Self, v : i64) -> i32fn write_usize(self : &mut Self, v : usize) -> i32
Written as au64, so ausizehashes the same as au64holding 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) -> i32fn 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) -> SipHasher13fn new() -> SipHasher13
Hasher for SipHasher13 methods
fn write_u8(self : &mut SipHasher13, b : u8) -> i32fn 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 thatcondis true, so it may drop checks that follow from it. Nothing checks it: ifcondis false, the program’s behaviour is undefined. A module that uses this is skipped by the pass that frees heap buffers, because thellvm.intr.assumeit becomes does not declare its memory effects.fn must_use<T>(value : T) -> T
Returnsvalueunchanged, marking a value that should not be ignored.fn select_unpredictable<T>(condition : bool, true_val : T, false_val : T) -> T
true_valifconditionholds, otherwisefalse_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 withfapplied to each. Built bymap.struct Filter<I, P>struct Take<I>
An iterator over at mostleftitems of another. Built bytake.struct Skip<I>
An iterator over another’s items with the firstdropof them discarded. Built byskip.struct StepBy<I>
An iterator over everystepth item of another, starting with its first. Built bystep_by.struct Chain<A, B>
An iterator over the items of one iterator, then those of another. Built bychain.struct TakeWhile<I, P>
An iterator over another’s items for as long askeepaccepts them. Built bytake_while.struct SkipWhile<I, P>
An iterator over another’s items from the first oneskiprefuses. Built byskip_while.struct MapWhile<I, F>
An iterator over whatfanswers for another’s items, until it answers nothing. Built bymap_while.struct Inspect<I, F>
An iterator that hands each of another’s items tofon its way past. Built byinspect.struct Scan<I, St, F>
An iterator over whatfanswers for another’s items while it carries a state between them, ending whereffirst answers nothing. Built byscan.struct Copied<I>
An iterator that answers nothing forever once another has answered nothing once. Built byfuse. An iterator over copies of the values another iterator’s references point at. Built bycopied.struct Cloned<I>
An iterator over clones of the values another iterator’s references point at. Built bycloned.struct Fuse<I>struct Peekable<I, T>
An iterator whose next item can be looked at without taking it. Built bypeekable.Tis alwaysI::Item: a struct field cannot nameI::Item, so the item it holds on to is typed by a parameter of its own, whichpeekablefills in.struct FlatMap<I, F, U>
An iterator over the items of the iteratorsfanswers for another’s items, one after another. Built byflat_map.Uis whatfanswers, held while its items are handed out, and a parameter of its own for the reasonPeekable’sTis.struct Flatten<I, U>
An iterator over the items of each of another’s items, which are iterators themselves. Built byflatten.struct Rev<I>
An iterator over another’s items from the back. Built byrev.struct Zip<A, B>
An iterator over pairs of two others’ items, taken in step, ending when either does. Built byzip.struct Enumerate<I>
An iterator over another’s items, each paired with how far along it is, counting from zero. Built byenumerate.struct Cycle<I>
An iterator over another one’s items, again and again. Built bycycle.origis kept untouched. Each pass runs over a clone of it held incur, made when the pass starts. The clone is taken here rather than incycle, because a default method ofIteratoris checked for every iterator type, and most of them are notClone; 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 withfapplied.
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 withfapplied.
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 thatkeepaccepts.
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 thatkeepaccepts.
Iterator for Take<I> methods
fn next(self : &mut Take<I>) -> Option<I : : Item>
The inner iterator’s next item, untilleftof them have been handed out.
ExactSizeIterator for Take<I> methods
fn len(self : &Take<I>) -> i64
The inner iterator’s count, up toleft.
DoubleEndedIterator for Take<I> methods
fn next_back(self : &mut Take<I>) -> Option<I : : Item>
The last of the itemsnextwould 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 onestepplaces 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 firstkeeprefuses; 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 onesskipaccepts 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>
fof the inner iterator’s next item, which is nothing oncefsays 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, afterfhas 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, afterfhas 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>
fof 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 itempeeklooked 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 itemnextwould 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 ifaccepttakes 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 onepeekholds.
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 Iteratortrait DoubleEndedIterator
An iterator that can also answer from its far end, which is whatrevneeds. Not a subtrait ofIterator, because Vx has no subtraits;Self::Itemhere is the one the type’sIteratorimpl binds.trait ExactSizeIterator
An iterator that knows how many items it has left, which is whatrevneeds fromtake,skipandstep_by. Not a subtrait ofIterator, because Vx has no subtraits.trait IntoIterator
A value that can be turned into an iterator, which is what aforloop does with what it is given when that is not an iterator already:for x in vover aVec.trait FromIterator<A>
A collection that can be built from an iterator’s items, which is whatcollectbuilds.trait Extend<A>
A collection that grows by an iterator’s items, which is whatpartitionandunzipfill.trait Sum<A>
A type whose values can be added up from an iterator’s items, which is whatsumdoes.trait Product<A>
A type whose values can be multiplied together from an iterator’s items, which is whatproductdoes.
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 itemnplaces 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
Doesfaccept any item? Stops at the first it does, leaving the rest unconsumed.fn all(self : &mut Self, f : Closure1<Self : : Item, bool>) -> bool
Doesfaccept 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 itemfaccepts, 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 itemfaccepts is, counting the next one as zero.fn for_each(self : &mut Self, f : Closure1<Self : : Item, i32>) -> i32
Hand every item tof, consuming the sequence.fanswers ani32rather 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
fover every item, carrying a value from one to the next:initgoes in with the first item, and whatfanswers goes in with the next. The last answer is the result, orinitfor a sequence that is already finished.fn try_fold<Acc, R>(self : &mut Self, init : Acc, f : Closure2<Acc, Self : : Item, R>) -> R
foldthat can stop early.fanswers anOptionor aResult:SomeorOkcarries its value on to the next item, and the firstNoneorErris 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, orinitwrapped for a sequence that is already finished.fn try_for_each<R>(self : &mut Self, f : Closure1<Self : : Item, R>) -> R
for_eachthat can stop early: the firstNoneorErrthatfanswers is returned at once, and the rest of the sequence is left unconsumed. Otherwise the answer isSome(0)orOk(0).fanswers anOption<i32>orResult<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 implementingSum, 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 implementingProduct.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 ascompareorders them,compare(a, b)saying whereasits againstb. 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 ascompareorders 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 whosekeyis 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 whosekeyis 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 withfapplied to each.fn filter(self : Self, keep : Closure1<&Self : : Item, bool>) -> Filter<Self, Closure1<&Self : : Item, bool>>
An iterator over the itemskeepaccepts.fn take(self : Self, n : i64) -> Take<Self>
An iterator over at most the firstnitems.fn skip(self : Self, n : i64) -> Skip<Self>
An iterator over the items after the firstn.fn step_by(self : Self, step : i64) -> StepBy<Self>
An iterator over the first item and everystepth one after it.Panics
Whenstepis 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 thenother’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 itemskeepaccepts, 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 oneskiprefuses onward.fn map_while<U>(self : Self, f : Closure1<Self : : Item, Option<U>>) -> MapWhile<Self, Closure1<Self : : Item, Option<U>>>
An iterator over whatfanswers for each item, ending whereffirst 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 tofon its way past.fanswers ani32, 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 whatfanswers for each item, withfgiveninitialto keep and change from one item to the next. Ends whereffirst 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 whencyclewas called, so the type must implementClone. 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 aCopytype, asVec::iterdoes.fn cloned(self : Self) -> Cloned<Self>
An iterator over clones of what these items point at, for an iterator that hands out references to aClonetype.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, bypeek, 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 iteratorfanswers, in order.fanswers 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 andother’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 ani64.fn reduce(self : &mut Self, f : Closure2<Self : : Item, Self : : Item, Self : : Item>) -> Option<Self : : Item>
foldwith 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 implementingFromIterator.fn partition<B>(self : &mut Self, f : Closure1<&Self : : Item, bool>) -> (B, B)
Every item, split in two byf: the ones it accepts first, the rest second. Each half is a collection of whatever type the result is assigned to, which starts from itsDefaultand grows throughExtend.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 againstother’s, in step, by theirOrd: 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>
cmpby 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 toother’s, one for one and as many of them?fn ne<Other>(self : &mut Self, other : Other) -> bool
Do these items differ fromother’s anywhere, or in how many there are?fn lt<Other>(self : &mut Self, other : Other) -> bool
Are these items less thanother’s, in the orderpartial_cmpgives? False when some pair does not compare.fn le<Other>(self : &mut Self, other : Other) -> bool
Less than or equal, bypartial_cmp. False when some pair does not compare.fn gt<Other>(self : &mut Self, other : Other) -> bool
Greater than, bypartial_cmp. False when some pair does not compare.fn ge<Other>(self : &mut Self, other : Other) -> bool
Greater than or equal, bypartial_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 itemfaccepts, searching from the back.fn nth_back(self : &mut Self, n : i64) -> Option<Self : : Item>
The itemnplaces 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 itemiterhas left, in order.
trait Extend<A> methods
fn extend<I : Iterator>(self : &mut Self, iter : I) -> i32
Add every itemiterhas 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 itemiterhas left, added up. Zero when it has none.
trait Product<A> methods
fn product<I : Iterator>(iter : I) -> Self
Every itemiterhas 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 fromatup to but not includingend. A half-open span ofi64, fromatup to but not includingend.
Iterator for Range methods
fn next(self : &mut Range) -> Option<i64>
The next value in the span, or nothing onceendis 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 whatcyclerestarts from.
Functions
fn range(at : i64, end : i64) -> Range
The numbersat .. end.
core::libm
Types
struct LibmDyadic128
(-1)^negative * 2^exponent * mantissa, where the mantissa is the 128-bit integerhi * 2^64 + lo. Normalized when the mantissa is zero or its top bit is set.struct LibmTrigReduced
The reduced argument forsinf,cosfandtanf:k = round(x * 32 / pi)andy = x * 32 / pi - k, fromrange_reduction_fma.h.32 / piis held in five doubles; up to 2^45 two of them are enough, and beyond thatsmall_range_reductionwould lose the integer bits, solarge_range_reductionfirst 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)andcos(y * pi / 32) - 1for the reduced argument:sincosf_evalfromsincosf_utils.h.struct LibmDoubleDouble
A double-double:hi + lo, with|lo|at most half a unit in the last place ofhi. LLVM libc’sDoubleDouble, fromFPUtil/double_double.h.struct LibmI128
hi * 2^64 + loread as a signed 128-bit integer.struct LibmLogReduced
The result oflibm_log_range_reduction: the sum of the table values it added, and the reduced argument.struct LibmZivResult
The result oflibm_exp_ziv_denorm:okis false when the error bound does not decide the rounding.struct LibmExp2Reduced
The reductionexp2and its subnormal path share:k = round(x * 2^12), and the fast pass’sexp_midandlo.struct LibmTrig64Reduced
The result of reducingxby multiples of pi/128:x = k pi / 128 + u, with|u|at most about pi/256. For a largex, the pieces the 128-bit pass needs to redo the reduction.struct LibmSinCos
sin(u)andcos(u)as double-doubles, and the bound on their error:sincos_evalfromsincos_eval.h, Taylor polynomials inu.hiwithu.loadded to first order.struct LibmAsinEval
asin(x) / xas a double-double foru = x^2(or the reduced argument of the larger range), from rowround(32 u.hi)ofLIBM_ASIN_COEFFS:asin_evalfromasin_utils.h.erris 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 / 64withidx = round(64 x^2), fromx^2as a double-double. Itsidxcomes back ink.struct LibmAsinLarge
For1/2 <= |x| < 1,asin(|x|) = pi/2 - 2 asin(sqrt(u))withu = (1 - |x|) / 2. This holds the pieces bothasinandacosneed:v = sqrt(u)asvh / 2 + vl / 2, and the double-double2 sqrt(u) * asin(sqrt(u)) / sqrt(u)with its error bound.struct LibmExp10Reduced
The reductionexp10and its subnormal path share:k = round(x * log2(10) * 2^12), and the fast pass’sexp_midandlo.struct LibmU256
A 256-bit unsigned integer as four 64-bit words,w0the lowest: LLVM libc’sUInt<256>.struct LibmU256Carry
The sum ofaandband whether it carried out of the top word.struct LibmMulAcc
The running two-word sum ofquick_mul_hi, with the carries out of it counted.struct LibmDyadic256
(-1)^negative * 2^exponent * m, withma 256-bit integer: LLVM libc’sDyadicFloat<256>, which the last pass ofpowuses.struct LibmMaybeF64
Whatpowanswers for the inputscheck_special_inputsofpow_utils.hsettles: x or y with mostly zero mantissa bits, such as zeros, ones, powers of two and NaNs.hasis false when the general path must answer.struct LibmPow64State
The statepowcarries out ofcheck_exceptional_casesinpow_utils.h: the answer when it has one, elsexandy(made positive, or moved in range),x’s exponent and mantissa (a subnormal normalized), and the result’s sign.struct LibmPow64Exact
The exact value ofx^y, asm * 2^e, when it has one with at most 54 bits inm:is_exact_rounding_boundaryfrompow_utils.h. Such a value can lie exactly halfway between two doubles, which an error bound can never decide.struct LibmExpParts
e^aas2^hi * m, withma double-double between about 0.7 and 1.5, for|a|below about 745: the reduction and double-double pass oflibm_exp, without scaling by2^hi, so that a value past the largest double can still be divided.
Functions
fn libm_f64_to_bits(x : f64) -> u64fn libm_f64_from_bits(v : u64) -> f64fn libm_f32_to_bits(x : f32) -> u32fn libm_f32_from_bits(v : u32) -> f32fn libm_multiply_add(x : f64, y : f64, z : f64) -> f64
x * y + zwith one rounding:fputil::multiply_addwith 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, normalx, accurate enough for the single-precision functions:log_evalfromacoshf_utils.h, accurate version. Withx = 2^e * m, it ise * log(2) + log(1 / r) + log(1 + dx), whererfrom the table makesdx = r * m - 1small 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:asinhffromasinhf.h.fn libm_acoshf(x : f32) -> f32
The inverse hyperbolic cosine, correctly rounded; NaN below 1:acoshffromacoshf.h.fn libm_atanhf(x : f32) -> f32
The inverse hyperbolic tangent, correctly rounded; infinite at +-1 and NaN beyond:atanhffromatanhf.h.fn libm_multiply_add_f32(x : f32, y : f32, z : f32) -> f32
fmaf(x, y, z):x * y + zin 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:logffromlogf.h. Withx = 2^m * 1.mantandfthe top 8 bits of1.mant, it ism * 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:log2ffromlog2f.h, the same reduction aslibm_logfwith a degree-5 polynomial forlog2(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:log10ffromlog10f.h. Powers of ten up to10^10are exact.fn libm_log1pf(x : f32) -> f32
log(1 + x), correctly rounded:log1pffromlog1pf.h. For|x| <= 2^-6a degree-8 polynomial (Sollya:fpminimax(log(1 + x)/x, 7, [|D...|], [-2^-6; 2^-6])); otherwise1 + xis exact in double precision andlibm_log_evaltakes 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
Whatln,log2andlog10of 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 oflogf16,log2f16andlog10f16.fn libm_logf16(x : f16) -> f16
The natural logarithm of an f16, correctly rounded:logf16fromlogf16.h. Withx = 2^m * 1.mantandfthe top 6 bits of1.mant, it ism * 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:log2f16fromlog2f16.h, the same reduction aslibm_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:log10f16fromlog10f16.h, the same reduction aslibm_logf16.fn libm_log1pf16(x : f16) -> f16
log(1 + x)of an f16, correctly rounded. LLVM libc has nolog1pf16: this is itslog1pfon the input widened to f32, rounded to f16. Rounding twice lands one step off for two inputs, found by checking all 65,536 inputs againstln(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 f32is exact too, but LLVM then narrows a comparison such as(x as f32) == 0.0back to bf16, and on a CPU with AVX512-BF16 it evaluates that throughvcvtneps2bf16, 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 bf16cannot be used: on a CPU with AVX512-BF16, LLVM converts withvcvtneps2bf16, 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 oflibm_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 throughvcvtneps2bf16, which flushes a subnormal to zero.fn libm_log_bf16(x : bf16) -> bf16
The natural logarithm of a bf16, correctly rounded:log_bf16fromlog_bf16.h. Withx = 2^e * (1 + m), it ise * log(2) + log(1 + m), the last read from a table indexed by the 7 bits ofm.fn libm_log_bf16_bits(x_bits : u16) -> u16
The bits oflibm_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 nolog2for bf16: this is itslog2fon 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 nolog10for bf16: this is itslog10fon 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 nolog1pfor bf16: this is itslog1pfon 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 producta * 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
dwith its mantissa shifted left bysbits and the exponent unchanged; zero oncesreaches 128.fn libm_dyadic_shr(d : LibmDyadic128, s : u64) -> LibmDyadic128
dwith its mantissa shifted right bysbits and the exponent unchanged; zero oncesreaches 128. The bits shifted out are lost.fn libm_dyadic_normalize(d : LibmDyadic128) -> LibmDyadic128
dwith its mantissa’s top bit moved to bit 127, and the exponent lowered to match.fn libm_dyadic_from_f64(x : f64) -> LibmDyadic128
xexactly, normalized: theDyadicFloat(double)constructor.fn libm_dyadic_shift_right(d : LibmDyadic128, s : i32) -> LibmDyadic128
dwith its mantissa shifted right bysbits 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 isquick_mul_hiof the two, the high 128 bits of their product without the low halves’ producta.lo * b.loand without the carry out of the bits below.fn libm_dyadic_to_f64(d : LibmDyadic128) -> f64
The nearest double tod, ties to even, for a normalizedd:fast_as<double>. The top 53 bits maked_hi; the round and sticky bits below them, scaled tod_hi’s last place, are added with one FMA, which rounds the sum correctly.fn libm_round_even_f32(x : f32) -> f32
xrounded to the nearest integer, ties to even:fputil::nearest_integer.fn libm_expf(x : f32) -> f32
exp(x), correctly rounded:expffromexpf_double_eval.h. Withx = hi + mid + lo,hian integer andmida multiple of 1/128, it isexp(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:exp2ffromexp2f_double_eval.h. Near zero a degree-6 polynomial; otherwise2^x = 2^hi * 2^mid * 2^lowithmida multiple of 1/32,2^hiadded straight into the exponent of2^midfrom a table, and2^loa degree-5 polynomial.fn libm_expm1f(x : f32) -> f32
exp(x) - 1, correctly rounded:expm1ffromexpm1f.h. Near zero a polynomial; otherwiseexpf’s reduction with the final stepexp(hi + mid) * exp(lo) - 1as one FMA.fn libm_expf16(x : f16) -> f16fn libm_exp2f16(x : f16) -> f16fn libm_expm1f16(x : f16) -> f16fn libm_exp_bf16(x : bf16) -> bf16fn libm_exp2_bf16(x : bf16) -> bf16fn libm_expm1_bf16(x : bf16) -> bf16fn libm_exp_pm_eval(x : f32, is_sinh : bool) -> f64
sinh(x)(is_sinh) orcosh(x)of an f32, before rounding:exp_pm_evalfromsinhfcoshf_utils.h. Withx = (hi + mid) * log(2) + lo,mida multiple of 1/32,e^xande^-xshare one reduction:2^(hi + mid)and2^-(hi + mid)fromEXP_2_MID, and the even and odd halves of a degree-5 polynomial fore^loande^-lo.fn libm_round_even_f64(x : f64) -> f64
xrounded to the nearest integer, ties to even, in double precision.fn libm_sinhf(x : f32) -> f32
The hyperbolic sine, correctly rounded:sinhffromsinhf.h. Near zero a polynomial; otherwiselibm_exp_pm_eval.fn libm_coshf(x0 : f32) -> f32
The hyperbolic cosine, correctly rounded:coshffromcoshf.h.fn libm_tanhf(x : f32) -> f32
The hyperbolic tangent, correctly rounded:tanhffromtanhf.h. Near zero a Taylor polynomial; otherwise(e^2x - 1) / (e^2x + 1)written as(r - mh) / (r + mh)withmh = 2^-(hi + mid)andra polynomial fore^2lo.fn libm_sinhf16(x : f16) -> f16fn libm_coshf16(x : f16) -> f16fn libm_tanhf16(x : f16) -> f16fn libm_sinh_bf16(x : bf16) -> bf16fn libm_cosh_bf16(x : bf16) -> bf16fn libm_tanh_bf16(x : bf16) -> bf16fn libm_trig_reduce(xd : f64, x_abs : u32) -> LibmTrigReducedfn libm_sincosf_eval(xd : f64, x_abs : u32) -> LibmSinCosPartsfn libm_sinf(x : f32) -> f32
The sine, correctly rounded:sinffromsinf_double_eval.h. Near zero a polynomial; otherwisesin(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:cosffromcosf_double_eval.h.fn libm_tanf(x : f32) -> f32
The tangent, correctly rounded:tanffromtanf.h. Near zero a polynomial; otherwise the sine over the cosine, from one reduction.fn libm_sinf16(x : f16) -> f16fn libm_cosf16(x : f16) -> f16fn libm_tanf16(x : f16) -> f16fn libm_sin_bf16(x : bf16) -> bf16fn libm_cos_bf16(x : bf16) -> bf16fn libm_tan_bf16(x : bf16) -> bf16fn libm_asin_eval(xsq : f64) -> f64
asin(x)/x - 1in terms ofxsq = x^2, for the single-precision functions:asin_evalfromasin_utils.h.fn libm_asinf(x : f32) -> f32
The arcsine, correctly rounded:asinffromasinf.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:acosffromacosf.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 byx:atan_evalfromatan_utils.h.fn libm_atanf(x : f32) -> f32
The arctangent, correctly rounded:atanffromatanf.h. Near zero and beyond 16 a Taylor polynomial (of-1/xbeyond 16); between,atan(k/16) + atan_evalwithk = round(16 |x|), or of1/|x|above 1.fn libm_dd_exact_add(a : f64, b : f64) -> LibmDoubleDouble
a + bas 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-doublemultiply_add.fn libm_dd_nudge(rr : LibmDoubleDouble, mask : u64) -> f64
rr.hi, moved one unit towardrr.lowhen 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 ofatan2f_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 ofatan2f, for a result too near a rounding boundary to trust the fast one:atan2f_double_doublefromatan2f.h, the same reduction in double-double. It answers the double-double;libm_dd_nudgeturns 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:atan2ffromatan2f.h. Withq = min(|x|, |y|) / max(|x|, |y|)it isatan(q)fromatan_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 aremaskin a double: the body ofatan2f.always_accurateskips the fast path, for a format that the fast path’s check does not cover.fn libm_asinf16(x : f16) -> f16fn libm_acosf16(x : f16) -> f16fn libm_atanf16(x : f16) -> f16fn libm_asin_bf16(x : bf16) -> bf16fn libm_acos_bf16(x : bf16) -> bf16fn libm_atan_bf16(x : bf16) -> bf16fn libm_f32_round_odd(d : f64) -> f32
drounded 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 roundingdonce.fn libm_atan2f16(y : f16, x : f16) -> f16
atan2(y, x)for f16, correctly rounded: the double-double accurate pass ofatan2fon 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 ofatan2fon 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:hypotffromhypotf.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 ofhypoton infinities and NaNs, as the bits of a 16-bit float whose exponent mask isexp_maskand quiet bitquiet, 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 ofhypotf16andhypotbf16. They differ in which NaN they answer:hypotf16the larger in size, without its sign, andhypotbf16(keep_sign)xif it is a NaN, elsey, 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 ofhypotof 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 (seelibm_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:exp10ffromexp10f_double_eval.h, withx = (hi + mid) * log10(2) + loand10^loa 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 bitsx_uis a whole number (or infinite):is_integerfrompowf.h.fn libm_f32_bits_is_odd_integer(x_u : u32) -> bool
Whether the f32 with bitsx_uis an odd whole number:is_odd_integerfrompowf.h.fn libm_dd_quick_mult_d(x : f64, b : LibmDoubleDouble) -> LibmDoubleDouble
x * bas a double-double, for a doublex:quick_mult(double, DoubleDouble).fn libm_larger_exponent(a : f64, b : f64) -> bool
Whether the exponent ofais at least that ofb:larger_exponentfrompowf.h.fn libm_powf_double_double(idx_x : i64, dx : f64, y6 : f64, lo6_hi : f64, exp2_hi_mid : LibmDoubleDouble) -> LibmDoubleDouble
The accurate pass ofpowf, as a double-double:powf_double_doublefrompowf.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^yas a double:powffrompowf.h.x^y = 2^(y log2 x):log2 xfrom theLOG2_Rtables and a polynomial,2^(.)fromEXP2_MID1and a polynomial, both in double; a result too near a rounding boundary is redone in double-double and moved off a halfway point bylibm_dd_nudgewithmask. Special cases:yof 0, 1/2, 1, 2, small wholeycomputed exactly, infinities, NaNs, zeros and negativex. Withnarrowfalse the result rounds to the correct f32, aspowfdoes. Withnarrowtrue, for f16 and bf16 inputs, every result takes the double-double pass, andexp2fandexp10f, which would round twice, are not used forxof 2 and 10.fn libm_powf(x : f32, y : f32) -> f32
x^y, correctly rounded:powffrompowf.h.fn libm_powf16(x : f16, y : f16) -> f16
x^yfor f16, correctly rounded:libm_pow_valueon the inputs widened to f32, always through the double-double pass, rounded once.fn libm_pow_bf16(x : bf16, y : bf16) -> bf16
x^yfor 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
vwidened 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, forsbelow 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
Whetherais negative.fn libm_i128_negate(a : LibmI128) -> LibmI128
-a.fn libm_i128_bits_from_68(a : LibmI128) -> i32
Bits 68 to 99 ofa, 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 byDYADIC: 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 oflog:log_range_reductionfromlog_range_reduction.h, onm_xwith units of 2^-60. It adds-log(r)of each step tosumfromstep_2,step_3andstep_4and answers what is left.fn libm_log_step(table : i32, k : i32, i : i64) -> LibmDyadic128
Entryiof stepk(1 to 4) of a logarithm’s range reduction table:log’s whiletableis 0,log2’s while it is 1 andlog10’s while it is 2.fn libm_log_accurate(e_x : i32, index : i64, m_x : f64) -> f64
The accurate pass oflog:log_accuratefromlog.h, in 128-bit dyadic floats.e_xandindexare the fast pass’s exponent and table index,m_xits reduced argument.fn libm_log(x : f64) -> f64
The natural logarithm of a double, correctly rounded:logfromlog.h.x = 2^e * m, andlog(m) = -log(r) + log(1 + u)withrfromRDandu = r * m - 1exact;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 oflog2:log2_accuratefromlog2.h, in 128-bit dyadic floats.fn libm_log2(x : f64) -> f64
The logarithm to base 2 of a double, correctly rounded:log2fromlog2.h. The same reduction aslibm_log; the natural logarithm of the reduced argument is turned into base 2 by a double-double multiply bylog2(e).fn libm_log10_accurate(e_x : i32, index : i64, m_x : f64) -> f64
The accurate pass oflog10:log10_accuratefromlog10.h, in 128-bit dyadic floats.fn libm_log10(x : f64) -> f64
The logarithm to base 10 of a double, correctly rounded:log10fromlog10.h. The same reduction aslibm_log, turned into base 10 by a double-double multiply bylog10(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
EntryiofLOG_R1,LOG_R2orLOG_R3oflog1p, forkof 1, 2 or 3.fn libm_log1p_accurate(e_x : i32, index : i64, m_hi : f64, m_lo : f64) -> f64
The accurate pass oflog1p:log1p_accuratefromlog1p.h, in 128-bit dyadic floats.m_hi + m_lois the fast pass’s reduced argument.fn libm_log1p(x : f64) -> f64
log(1 + x)of a double, correctly rounded and accurate for a smallx:log1pfromlog1p.h.1 + xis formed exactly as a double-double, reduced byR1, and the result ise * 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 withalways_accuratetakes 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
Coefficientiof the double-double polynomial ofexp(which0),exp2(1) orexpm1(2).fn libm_exp_poly_dd(which : i32, dx : LibmDoubleDouble) -> LibmDoubleDouble
The degree-6 double-double polynomial ofexp(which0),exp2(1) orexpm1(2) atdx, by Horner’s rule:polyeval.fn libm_exp_coeff_128(which : i32, i : i64) -> LibmDyadic128
Coefficientiof the 128-bit polynomial ofexp(which0),exp2(1) orexpm1(2).fn libm_exp_poly_128(which : i32, degree : i64, dx : LibmDyadic128) -> LibmDyadic128
The 128-bit polynomial ofexp(which0),exp2(1) orexpm1(2) atdx, with coefficients 0 todegree, 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 thehiandmidparts 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
xtimes2^hi, by addinghito the exponent field.xand 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, whenerrbounds the error oflowell enough to decide the rounding:ziv_test_denormfromexp_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 thexthe fast path leaves out:|x| <= 2^-53, anxwhose result overflows or rounds to zero, infinities and NaN.set_exceptionalfromexp.h.fn libm_exp_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
The 128-bit pass ofexp:exp_f128fromexp.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 ofexp:exp_double_doublefromexp.h.fn libm_exp(x : f64) -> f64
e raised to a double, correctly rounded:expfromexp.h.x = log(2) * (hi + mid1 + mid2) + lo, withhian integer,mid1a multiple of 1/64 andmid2of 1/4096, soexp(x) = 2^hi * 2^mid1 * 2^mid2 * exp(lo). The two middle factors come from tables andexp(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 ofexp2:exp2_f128fromexp2.h.fn libm_exp2_reduce(x : f64) -> LibmExp2Reduced
x = hi + mid1 + mid2 + dxwithmid1a multiple of 1/64,mid2of 1/4096 and|dx| <= 2^-13, and the fast pass’s estimate2^(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_denormfromexp2.h.fn libm_exp2(x : f64) -> f64
Two raised to a double, correctly rounded:exp2fromexp2.h. The same scheme aslibm_exp, without the multiplication bylog2(e), so the reduction is exact.fn libm_expm1_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
The 128-bit pass ofexpm1:expm1_f128fromexpm1.h.2^(hi + mid) * exp(dx) - 1is computed as(2^mid - 2^-hi) + 2^mid * dx * P(dx), then scaled by2^hi.fn libm_expm1_exceptional(x : f64) -> f64
expm1(x)for thexthe fast path leaves out:|x| <= 2^-53,x <= -37.5, anxwhose result overflows, infinities and NaN.set_exceptionalfromexpm1.h.fn libm_expm1(x : f64) -> f64
e^x - 1for a double, correctly rounded, and accurate for a smallxwhereexp(x) - 1would lose every significant digit:expm1fromexpm1.h. The same reduction aslibm_exp; the- 1is folded into the table value as2^mid - 2^-hi, which is exact.fn libm_dd_two_sum(a : f64, b : f64) -> LibmDoubleDouble
a + bas a double-double, exactly, in either order of size (TwoSum):exact_add<false>.fn libm_dd_exact_mult(a : f64, b : f64) -> LibmDoubleDouble
a * bas a double-double, exactly:exact_multwith FMA.fn libm_dd_div(a : LibmDoubleDouble, b : LibmDoubleDouble) -> LibmDoubleDouble
a / bas a double-double:divfromdouble_double.h, one correction step ona.hi / b.hi.fn libm_trig64_reduce_small(x : f64) -> LibmTrig64Reduced
Reduction for|x| < 2^16:range_reduction_smallfromrange_reduction_double_common.h.k = round(x * 128/pi)andu = x - k pi/128, with pi/128 in three parts whose first two products withkare exact.fn libm_trig64_reduce_none(x : f64) -> LibmTrig64Reduced
xitself asu, withk = 0, for anxtoo small to need reducing.fn libm_trig64_reduce_large(x : f64) -> LibmTrig64Reduced
Reduction for|x| >= 2^16:LargeRangeReduction::fastfromrange_reduction_double_fma.h.xis split as2^(16 (idx - 3)) * x_reduced, andx_reducedis multiplied by the row of 128/pi that starts where the integer part of the product stops mattering. The fractional part, times pi/128, isu.fn libm_trig64_pi_over_128_f128() -> LibmDyadic128
pi/128 in 128 bits.fn libm_trig64_reduce_f128(x : f64, r : LibmTrig64Reduced) -> LibmDyadic128
uin 128 bits for the accurate pass:range_reduction_small_f128for|x| < 2^16, elseLargeRangeReduction::accuratefrom the pieceslibm_trig64_reduce_largekept.fn libm_trig64_sin_k(k : i64) -> LibmDoubleDouble
sin(k pi / 128)as a double-double, for any integerk.fn libm_trig64_sin_k_f128(k : i64) -> LibmDyadic128
sin(k pi / 128)in 128 bits, for any integerk, from the table of the first quarter turn.fn libm_trig64_poly_f128(which : i32, u_sq : LibmDyadic128) -> LibmDyadic128
The 128-bit polynomialwhich(0sin(u) / u, 1cos(u), 2tan(u) / u) atu_sq = u^2, by Horner’s rule:polyeval.fn libm_trig64_coeff_f128(which : i32, i : i64) -> LibmDyadic128
Coefficientiof the 128-bit polynomialwhichoflibm_trig64_poly_f128.fn libm_trig64_sincos_eval(u : LibmDoubleDouble) -> LibmSinCosfn libm_trig64_nan(x : f64) -> f64
Whatsin,cosandtananswer for infinities and NaNs: a NaN, quiet.fn libm_sin_accurate(x : f64, r : LibmTrig64Reduced) -> f64
The 128-bit pass ofsin:sin_accuratefromsin.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:sinfromsin.h.x = k pi/128 + u; the sine and cosine ofk pi/128come from a table and those ofufrom 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 ofcos:cos_accuratefromcos.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:cosfromcos.h, the same scheme aslibm_sin.fn libm_dyadic_div_newton(a : LibmDyadic128, b0 : LibmDyadic128, q : f64) -> LibmDyadic128
a / bin 128 bits, from the first guessqof1 / b: two Newton-Raphson steps,newton_raphson_divfromtan.h.fn libm_tan(x : f64) -> f64
The tangent of a double, correctly rounded:tanfromtan.h.x = k pi/128 + u, andtan(x) = (sin(k pi/128) + cos(k pi/128) tan(u)) / (cos(k pi/128) - sin(k pi/128) tan(u)), withtan(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() -> LibmDyadic128fn libm_asin_eval_dd(u : LibmDoubleDouble, err0 : f64) -> LibmAsinEvalfn libm_asin_coeff_f128(idx : i64, j : i64) -> LibmDyadic128
Coefficientjof rowidxofLIBM_ASIN_COEFFS_F128.fn libm_asin_eval_f128(u : LibmDyadic128, idx : i64) -> LibmDyadic128
asin(x) / xin 128 bits from rowidx, at the reduced argumentu: the 128-bitasin_eval, by Horner’s rule.fn libm_asin_small_f128(x_sq : LibmDoubleDouble) -> LibmAsinSmall128fn libm_asin_large(x_abs : f64) -> LibmAsinLargefn libm_asin_large_f128(s : LibmAsinLarge, negative : bool) -> LibmDyadic128
2 sqrt(u) asin(sqrt(u)) / sqrt(u)in 128 bits, from the pieces oflibm_asin_large, with the sign ofnegative.fn libm_asin_nan(x : f64) -> f64
Whatasinandacosanswer for|x| > 1and NaN: a quiet NaN, the input itself if it is one.fn libm_asin(x : f64) -> f64
The arcsine of a double, correctly rounded:asinfromasin.h. For|x| < 1/2a polynomial inx^2, from one of 9 tables; above thatpi/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:acosfromacos.h.pi/2 - asin(x)for|x| < 1/2; above that2 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-doublex:atan_evalfromatan_utils.h,x - x^3/3 + x^5/5 - x^7/7 + x^9/9withx.loadded 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:atanfromatan.h. Withk = round(64 |x|) / 64,atan(|x|) = atan(k) + atan((|x| - k) / (1 + |x| k)); above 1 the same on1 / |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):atan2fromatan2.h. The smaller of|x|and|y|is divided by the larger, reduced as inlibm_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 constantatan2adds 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
Whatatan2answers when either input is a zero or an infinity, before the sign ofy: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 generichypotfromFPUtil/Hypot.hfordouble, 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
hypotwhen 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,xfirst.fn libm_hypot_denorm(a0 : f64, b0 : f64) -> f64
hypotwhen both inputs are below 2^-1022 in size:hypot_denormfromhypot.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:hypotfromhypot.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_exactdoes it with integers.fn libm_exp10_poly_dd(dx : LibmDoubleDouble) -> LibmDoubleDouble
The degree-6 double-double polynomial ofexp10atdx, by Horner’s rule:polyeval.fn libm_exp10_poly_128(dx : LibmDyadic128) -> LibmDyadic128
The degree-7 128-bit polynomial ofexp10atdx, by Horner’s rule:polyeval.fn libm_exp10_128(x : f64, kd : f64, idx1 : i64, idx2 : i64) -> f64
The 128-bit pass ofexp10:exp10_f128fromexp10.h.fn libm_exp10_dd(x : f64, kd : f64, exp_mid : LibmDoubleDouble) -> LibmDoubleDouble
The double-double pass ofexp10:exp10_double_doublefromexp10.h.fn libm_exp10_reduce(x : f64) -> LibmExp10Reducedfn libm_exp10_denorm(x : f64) -> f64
exp10(x)when the result may be subnormal:exp10_denormfromexp10.h.fn libm_exp10(x : f64) -> f64
Ten raised to a double, correctly rounded:exp10fromexp10.h. The same scheme aslibm_exp, withlog2(10)in place oflog2(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) -> LibmU256fn libm_u256_zero() -> LibmU256fn libm_u256_is_zero(a : LibmU256) -> boolfn libm_u256_word(a : LibmU256, i : i64) -> u64
Wordiofa, 0 forioutside 0 to 3.fn libm_u256_add_carry(a : LibmU256, b : LibmU256) -> LibmU256Carryfn 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
Whethera >= b.fn libm_u256_shl(a : LibmU256, s : u64) -> LibmU256
a << s; zero oncesreaches 256.fn libm_u256_shr(a : LibmU256, s : u64) -> LibmU256
a >> s; zero oncesreaches 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
Bitpofa, false forppast 255.fn libm_u256_any_below(a : LibmU256, p : u64) -> bool
Whether any of the bits ofabelow bitpis set (all of them forppast 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 ofa * 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 ofa * b, without the product of the low words:UInt<128>::quick_mul_hi.fn libm_frac128_at(t : i32, i : i64) -> u128
Entryiof a table of 128-bit fractions, low word first.fn libm_frac256_at(t : i32, i : i64) -> LibmU256
Entryiof a table of 256-bit fractions: 0LOG2_POLY_256, 1EXP2_POLY_256, 2EXP2_MID_FRAC256, 3LOG2_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 float2^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
dwith its mantissa’s top bit moved to bit 255, and the exponent lowered to match.fn libm_dyadic256_from_f64(x : f64) -> LibmDyadic256
xexactly, normalized: theDyadicFloat(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 fromquick_mul_hi.fn libm_dyadic256_to_f64(d : LibmDyadic256) -> f64
The nearest double tod, ties to even:fast_as<double>for 256 bits. The top 53 bits maked_hi; the round and sticky bits below them are added with one FMA.fn libm_pow64_is_odd_integer(x : f64) -> bool
Whetherxis an odd integer:is_odd_integerfrompow_utils.h.fn libm_pow64_is_integer(x : f64) -> bool
Whetherxis an integer:is_integerfrompow_utils.h.fn libm_pow64_overflow(negative : bool) -> f64
Infinity or zero with the signnegative:set_overflowandset_underflowin round-to-nearest.fn libm_pow64_underflow(negative : bool) -> f64fn 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, whenerris small enough to decide the rounding:ziv_test_denormfrompow_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) -> LibmMaybeF64fn libm_none_f64() -> LibmMaybeF64fn 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) -> LibmMaybeF64fn libm_pow64_exceptional(s0 : LibmPow64State) -> LibmPow64Statefn libm_pow64_exact(x : f64, y : f64) -> LibmPow64Exactfn libm_pow64_exact_to_f64(negative : bool, m : u64, e : i32) -> f64
The exact valuem * 2^ewith the signnegative, rounded to a double.fn libm_pow64_log2_f128(x_e : i32, idx_x : i64, dx : f64) -> LibmDyadic128
log2(x)in 128 bits fromx = 2^x_e * (1 + dx) / r, with-log2(r)from rowidx_x:log2_f128frompow_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^zin 128 bits:exp2_f128frompow_accurate_128.h.z = hi + idx/64 + lo;2^(idx/64)comes from a table and2^lo - 1from 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_f256frompow_accurate_256.h, the same scheme aslibm_pow64_log2_f128with three precisions.fn libm_pow64_exp2_f256(z : LibmDyadic256) -> LibmDyadic256
2^zin 256 bits:exp2_f256frompow_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 ofpow, in 256 bits:pow_accurate_256frompow_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 ofpow, in 128 bits:pow_accuratefrompow_accurate_128.h. When the result lands too near a rounding boundary to trust, it checks whetherx^yis exactly a boundary, and otherwise redoes it in 256 bits.fn libm_pow(x0 : f64, y0 : f64) -> f64
xraised toyfor doubles, correctly rounded:powfrompow.h.log2(x)is computed as a double-double from a table and a polynomial, multiplied byy, and2^(y log2(x))comes from a table of2^(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) -> LibmExpPartsfn libm_f64_ldexp(v : f64, e : i32) -> f64
v * 2^efor a normalv, 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) / 2forafrom 2^-6 to 711, from the double-doublee^aoflibm_exp_parts:2^(hi - 1) * (m + sign * 2^(-2 hi) / m), rounded once. When2^(-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 smallx, withc = c_hi + c_loa double-double andx^3kept as a double-double, so that onlyx^5 rest, which is far below the last place, is rounded on its own: the series ofsinhandtanhbelow 1/64.fn libm_sinh(x : f64) -> f64
The hyperbolic sine of a double. Not a port: LLVM libc has no double-precisionsinh. Below 1/64 in size, the Taylor series up tox^11fromlibm_odd_series_small. Above that,(e^|x| - e^-|x|) / 2fromlibm_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-precisioncosh. Below 1/64 in size,1 + x^2 / 2 + x^4 Q(x^2); above that(e^|x| + e^-|x|) / 2fromlibm_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-precisiontanh. Below 1/64 in size, the Taylor series up tox^13fromlibm_odd_series_small. Above that,(E - 1) / (E + 1)withE = 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:asinhf16fromasinhf16.h. Below 2^-2 in size a polynomial in f32; above,log(|x| + sqrt(x^2 + 1))with the doublelog_eval.fn libm_acoshf16(x : f16) -> f16
The inverse hyperbolic cosine of an f16, correctly rounded; NaN below 1:acoshf16fromacoshf16.h. Just above 1,sqrt(2 d)times a polynomial ind = x - 1; above that,log(x + sqrt(x^2 - 1))with the doublelog_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_ffromatanhf16.h. The sign ofxis not read.fn libm_atanhf16(x : f16) -> f16
The inverse hyperbolic tangent of an f16, correctly rounded; infinite at 1 and NaN past it:atanhf16fromatanhf16.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 + bfor 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 oflibm_log:x = 2^e * m,log(m) = -log(r) + log(1 + u)withrfromRDandu = r * m - 1exact.log(1 + u)isu - u^2 / 2withu^2exact, plusu^3 (1/3 + u R(u))withu^3and 1/3 as double-doubles; the low part ofaaddsa.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-precisionasinh. Below 1/64 in size, its Taylor series up tox^11fromlibm_odd_series_small; above,log(|x| + sqrt(x^2 + 1))with every step a double-double, and from 2^40 uplog(|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-precisionacosh.log(x + sqrt(x^2 - 1))with every step a double-double (x^2 - 1is exact before the square root), and from 2^40 uplog(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-precisionatanh. Below 1/64 in size, its Taylor series up tox^13fromlibm_odd_series_small; above,log((1 + |x|) / (1 - |x|)) / 2with 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) -> bf16fn libm_atanh_bf16(x : bf16) -> bf16fn libm_cbrtf(x : f32) -> f32
The cube root of an f32, correctly rounded:cbrtffromcbrtf.h.x = 2^(3e + s) * m, and the cube root ofmcomes from a polynomial for one of 16 slices of [1, 2), times2^(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:cbrtf16fromcbrtf16.h, the scheme oflibm_cbrtfin 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’scbrtbf16,cbrtfof it rounded to bf16.fn libm_cbrt_error(x_3 : LibmDoubleDouble, a_sq : LibmDoubleDouble) -> f64
x0^3 * a^2 - 1from the double-doublesx0^3anda^2:get_errorfromcbrt.hwith FMA.fn libm_cbrt(x : f64) -> f64
The cube root of a double, correctly rounded:cbrtfromcbrt.h.x = 2^(3e + s) * a'; a polynomial givesx0 ~ a^(-1/3), and Newton steps onx^3 a^2 = 1refine it, so thata * 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 withalways_accurateits 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 byout_e:update_exponentfromcbrt.h.fn libm_erf_eval(x : f32) -> f64
erf(x)in double for a finite|x|below 4: the polynomial oferff.hfor the slice|x|is in.fn libm_erff(x : f32) -> f32
The error function of an f32, correctly rounded:erfffromerff.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:erff16fromerff16.h, the polynomial oferffin 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 oferff’s polynomial rounded once to bf16. LLVM libc has no bf16erf.fn libm_rsqrt_special(x_u : u64, sign_bit : u64, inf : u64, quiet : u64) -> LibmZivResult
Whatrsqrtanswers for zero, a negative number, infinity and NaN, as the bits of a float with exponent maskinfand quiet bitquiet, or no answer (okfalse) for a positive finite x.fn libm_rsqrtf(x : f32) -> f32
1 / sqrt(x)for an f32:rsqrtffromrsqrtf.h, the double result rounded to f32.fn libm_rsqrtf16(x : f16) -> f16
1 / sqrt(x)for an f16, correctly rounded:rsqrtf16fromrsqrtf16.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-precisionrsqrt. 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 bf16rsqrt.
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 explicittransfer. Enforced for a struct and for a payload-free enum. A generic enum is not treated as linear at all, soOptionandResultsurvive 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 isSendwhen 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 writesunsafe impl Send for T {}, asVecdoes.trait Sync
A type that two tasks may use through&Tat the same time. The compiler derives it as it derivesSend.SharedCellis neverSync, because code can write its value through&. An atomic holds aSharedCelland opts in withunsafe 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. Itsvaluecan still be read, and moved out.
Functions
fn size_of<T>() -> i64
The size ofTin 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:*acannot 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
srcgoes in, and what was there comes back. Through a raw pointer, asswap.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 aTdoes any work: runs adrop, 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 asdigits * 10^exponent, or a NaN or an infinity. Zero hasdigits == 0.negativeis 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 powerifrom 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 dividesvalue, which must not be zero:pow5Factor.fn ryu_digit_count(v : u64) -> i64
The number of decimal digits inv; 1 for zero.fn ryu_trimmed(negative : bool, digits : u64, exponent : i64) -> FloatDecimal
digits * 10^exponentwith any trailing zeros ofdigitsmoved 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 valuebitsholds 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:mulPow5InvDivPow2over the double tables.fn ryu_mul_pow5_div_pow2(m : u64, i : i64, j : i64) -> u64
(m * 5^i) >> j for f32:mulPow5divPow2over 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 valuebitsholds 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 thendigits ofdigitstobuf[at..], with a.after the firstpointof them when 0 < point < n. Returns how many bytes that took.unsafe fn ryu_write_zeros(count : i64, buf : *mut i8, at : i64) -> i64
Writecountzeros tobuf[at..].unsafe fn ryu_write_special(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
WriteNaN,infor-inftobuf[at..]and return how many bytes that took.fn float_decimal_display_len(d : FloatDecimal) -> i64
How many bytes Rust’s{}takes ford.unsafe fn float_decimal_write_display(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
Write what Rust’s{}prints fordtobuf[at..]and return how many bytes that took: plain decimal notation, never an exponent, and no.0on a whole number. Unsafe because nothing checks that the buffer hasfloat_decimal_display_len(d)bytes.fn float_decimal_scientific_len(d : FloatDecimal) -> i64
How many bytes Rust’s{:e}takes ford.unsafe fn float_decimal_write_scientific(d : FloatDecimal, buf : *mut i8, at : i64) -> i64
Write what Rust’s{:e}prints fordtobuf[at..]and return how many bytes that took: one digit, the rest after a.if there are any, theneand the exponent, as in1.5e-7. Unsafe because nothing checks that the buffer hasfloat_decimal_scientific_len(d)bytes.
core::num
The integer and float methods, stamped over every width.
Types
enum FpCategory
The five kinds of float valueclassifytells apart.
usize methods
fn min_value(self : usize) -> usize
Zero.fn max_value(self : usize) -> usize
The largestusize, which is the largesti64.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 ausize, 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 toexp. 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 toexp, or nothing if the answer does not fit.fn saturating_pow(self : usize, exp : usize) -> usize
This raised toexp, held at the largest value if it does not fit.fn wrapping_pow(self : usize, exp : usize) -> usize
This raised toexp, wrapping at the top of the range.fn ilog(self : usize, base : usize) -> usize
The logarithm tobase, 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 tobase, 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
Doesrhsdivide this exactly? Zero is a multiple only of zero.fn next_multiple_of(self : usize, rhs : usize) -> usize
The smallest multiple ofrhsat or above this. Refused on a zerorhs, and when the answer does not fit.fn checked_next_multiple_of(self : usize, rhs : usize) -> Option<usize>
The smallest multiple ofrhsat or above this, or nothing on a zerorhsor 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 ausizeholds.fn next_power_of_two(self : usize) -> usize
One for zero and one. Refused above 2^62, the largest power of two ausizeholds.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 largestusize.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 largestusize. 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, aschecked_div.fn saturating_add(self : usize, rhs : usize) -> usize
Held at the largestusizeinstead 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 largestusizeinstead of trapping.fn wrapping_add(self : usize, rhs : usize) -> usize
The sum modulo 2^63: past the largestusizeit starts again from zero.fn wrapping_sub(self : usize, rhs : usize) -> usize
The difference modulo 2^63: below zero it starts again from the largestusize.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 largestusize.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 largestusize.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 forkstarts at byte2 * 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-precisionrsqrt). Infinite at zero and NaN below it.fn cbrt(self : f64) -> f64
The cube root, correctly rounded (LLVM libc’scbrt). Defined for negative numbers too.fn asinh(self : f64) -> f64
The inverse hyperbolic sine, almost always correctly rounded, fromcore::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, fromcore::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, fromcore::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’slog).fn log2(self : f64) -> f64
The logarithm to base two, correctly rounded (LLVM libc’slog2).fn log10(self : f64) -> f64
The logarithm to base ten, correctly rounded (LLVM libc’slog10).fn ln_1p(self : f64) -> f64
lnof one plus this, correctly rounded and accurate for a small argument (LLVM libc’slog1p).fn exp(self : f64) -> f64
e raised to this, correctly rounded (LLVM libc’sexp).fn exp2(self : f64) -> f64
Two raised to this, correctly rounded (LLVM libc’sexp2).fn exp_m1(self : f64) -> f64
expminus one, correctly rounded, so a small argument keeps its digits (LLVM libc’sexpm1).fn sin(self : f64) -> f64
The sine of this many radians, correctly rounded (LLVM libc’ssin).fn cos(self : f64) -> f64
The cosine of this many radians, correctly rounded (LLVM libc’scos).fn tan(self : f64) -> f64
The tangent of this many radians, correctly rounded (LLVM libc’stan).fn asin(self : f64) -> f64
The angle in radians whose sine is this, between -pi/2 and pi/2, correctly rounded (LLVM libc’sasin).fn acos(self : f64) -> f64
The angle in radians whose cosine is this, between 0 and pi, correctly rounded (LLVM libc’sacos).fn atan(self : f64) -> f64
The angle in radians whose tangent is this.atan2is the form that keeps the quadrant. LLVM libc’satan, which can be off in the last bit.fn atan2(self : f64, x : f64) -> f64
The angle to the point (x, this), which isatanwith the quadrant kept. LLVM libc’satan2, which can be off in the last bit.fn hypot(self : f64, other : f64) -> f64
The length of the hypotenuse, the square root ofself * self + other * other, correctly rounded and without overflow or underflow in the squares (LLVM libc’shypot).fn powf(self : f64, n : f64) -> f64
This raised ton, correctly rounded (LLVM libc’spow).fn sinh(self : f64) -> f64
The hyperbolic sine, fromcore::libm’sexp(LLVM libc has no double-precisionsinh): almost always correctly rounded, and within one unit in the last place.fn cosh(self : f64) -> f64
The hyperbolic cosine, fromcore::libm’sexp(LLVM libc has no double-precisioncosh): almost always correctly rounded, and within one unit in the last place.fn tanh(self : f64) -> f64
The hyperbolic tangent, fromcore::libm’sexp(LLVM libc has no double-precisiontanh): 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’serff).fn rsqrt(self : f32) -> f32
One over the square root (LLVM libc’srsqrtf, 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’scbrtf). Defined for negative numbers too.fn asinh(self : f32) -> f32
The inverse hyperbolic sine, correctly rounded (LLVM libc’sasinhf).fn acosh(self : f32) -> f32
The inverse hyperbolic cosine, correctly rounded; NaN below one (LLVM libc’sacoshf).fn atanh(self : f32) -> f32
The inverse hyperbolic tangent, correctly rounded; infinite at plus or minus one and NaN beyond (LLVM libc’satanhf).fn ln(self : f32) -> f32
The natural logarithm, correctly rounded (LLVM libc’slogf).fn log2(self : f32) -> f32
The logarithm to base two, correctly rounded (LLVM libc’slog2f).fn log10(self : f32) -> f32
The logarithm to base ten, correctly rounded (LLVM libc’slog10f).fn ln_1p(self : f32) -> f32
lnof one plus this, correctly rounded (LLVM libc’slog1pf).fn exp(self : f32) -> f32
e raised to this, correctly rounded (LLVM libc’sexpf).fn exp2(self : f32) -> f32
Two raised to this, correctly rounded (LLVM libc’sexp2f).fn exp_m1(self : f32) -> f32
expminus one, correctly rounded, so a small argument keeps its digits (LLVM libc’sexpm1f).fn sinh(self : f32) -> f32
The hyperbolic sine, correctly rounded (LLVM libc’ssinhf).fn cosh(self : f32) -> f32
The hyperbolic cosine, correctly rounded (LLVM libc’scoshf).fn tanh(self : f32) -> f32
The hyperbolic tangent, correctly rounded (LLVM libc’stanhf).fn sin(self : f32) -> f32
The sine of this many radians, correctly rounded (LLVM libc’ssinf).fn cos(self : f32) -> f32
The cosine of this many radians, correctly rounded (LLVM libc’scosf).fn tan(self : f32) -> f32
The tangent of this many radians, correctly rounded (LLVM libc’stanf).fn asin(self : f32) -> f32
The arcsine, between -pi/2 and pi/2, correctly rounded (LLVM libc’sasinf).fn acos(self : f32) -> f32
The arccosine, between 0 and pi, correctly rounded (LLVM libc’sacosf).fn atan(self : f32) -> f32
The arctangent, between -pi/2 and pi/2, correctly rounded (LLVM libc’satanf).fn atan2(self : f32, x : f32) -> f32
The angle to the point (x, this), which isatanwith the quadrant kept; correctly rounded (LLVM libc’satan2f).fn hypot(self : f32, other : f32) -> f32
The length of the hypotenuse, the square root ofself * self + other * other, without overflow or underflow in between; correctly rounded (LLVM libc’shypotf).fn powf(self : f32, n : f32) -> f32
This raised ton, correctly rounded (LLVM libc’spowf).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’serff16).fn rsqrt(self : f16) -> f16
One over the square root, correctly rounded (LLVM libc’srsqrtf16). Infinite at zero and NaN below it.fn cbrt(self : f16) -> f16
The cube root, correctly rounded (LLVM libc’scbrtf16). Defined for negative numbers too.fn asinh(self : f16) -> f16
The inverse hyperbolic sine, correctly rounded (LLVM libc’sasinhf16).fn acosh(self : f16) -> f16
The inverse hyperbolic cosine; NaN below 1, correctly rounded (LLVM libc’sacoshf16).fn atanh(self : f16) -> f16
The inverse hyperbolic tangent; infinite at 1 and NaN past it, correctly rounded (LLVM libc’satanhf16).fn ln(self : f16) -> f16
The natural logarithm, correctly rounded (LLVM libc’slogf16).fn log2(self : f16) -> f16
The logarithm to base two, correctly rounded (LLVM libc’slog2f16).fn log10(self : f16) -> f16
The logarithm to base ten, correctly rounded (LLVM libc’slog10f16).fn ln_1p(self : f16) -> f16
lnof one plus this, correctly rounded (through LLVM libc’slog1pf).fn exp(self : f16) -> f16
e raised to this, correctly rounded (through LLVM libc’sexpf).fn exp2(self : f16) -> f16
Two raised to this, correctly rounded (through LLVM libc’sexp2f).fn exp_m1(self : f16) -> f16
expminus one, correctly rounded, so a small argument keeps its digits (through LLVM libc’sexpm1f).fn sinh(self : f16) -> f16
The hyperbolic sine, correctly rounded (through LLVM libc’ssinhf).fn cosh(self : f16) -> f16
The hyperbolic cosine, correctly rounded (through LLVM libc’scoshf).fn tanh(self : f16) -> f16
The hyperbolic tangent, correctly rounded (through LLVM libc’stanhf).fn sin(self : f16) -> f16
The sine of this many radians, correctly rounded (through LLVM libc’ssinf).fn cos(self : f16) -> f16
The cosine of this many radians, correctly rounded (through LLVM libc’scosf).fn tan(self : f16) -> f16
The tangent of this many radians, correctly rounded (through LLVM libc’stanf).fn asin(self : f16) -> f16
The arcsine, between -pi/2 and pi/2, correctly rounded (through LLVM libc’sasinf).fn acos(self : f16) -> f16
The arccosine, between 0 and pi, correctly rounded (through LLVM libc’sacosf).fn atan(self : f16) -> f16
The arctangent, between -pi/2 and pi/2, correctly rounded (through LLVM libc’satanf).fn atan2(self : f16, x : f16) -> f16
The angle to the point (x, this), which isatanwith the quadrant kept; correctly rounded (through LLVM libc’satan2f).fn hypot(self : f16, other : f16) -> f16
The length of the hypotenuse, the square root ofself * self + other * other, without overflow or underflow in between; correctly rounded (in double, rounded once).fn powf(self : f16, n : f16) -> f16
This raised ton, 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 bf16erf).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’scbrtbf16, 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’slog_bf16).fn log2(self : bf16) -> bf16
The logarithm to base two, correctly rounded (through LLVM libc’slog2f).fn log10(self : bf16) -> bf16
The logarithm to base ten, correctly rounded (through LLVM libc’slog10f).fn ln_1p(self : bf16) -> bf16
lnof one plus this, correctly rounded (through LLVM libc’slog1pf).fn exp(self : bf16) -> bf16
e raised to this, correctly rounded (through LLVM libc’sexpf).fn exp2(self : bf16) -> bf16
Two raised to this, correctly rounded (through LLVM libc’sexp2f).fn exp_m1(self : bf16) -> bf16
expminus one, correctly rounded, so a small argument keeps its digits (through LLVM libc’sexpm1f).fn sinh(self : bf16) -> bf16
The hyperbolic sine, correctly rounded (through LLVM libc’ssinhf).fn cosh(self : bf16) -> bf16
The hyperbolic cosine, correctly rounded (through LLVM libc’scoshf).fn tanh(self : bf16) -> bf16
The hyperbolic tangent, correctly rounded (through LLVM libc’stanhf).fn sin(self : bf16) -> bf16
The sine of this many radians, correctly rounded (through LLVM libc’ssinf).fn cos(self : bf16) -> bf16
The cosine of this many radians, correctly rounded (through LLVM libc’scosf).fn tan(self : bf16) -> bf16
The tangent of this many radians, correctly rounded (through LLVM libc’stanf).fn asin(self : bf16) -> bf16
The arcsine, between -pi/2 and pi/2, correctly rounded (through LLVM libc’sasinf).fn acos(self : bf16) -> bf16
The arccosine, between 0 and pi, correctly rounded (through LLVM libc’sacosf).fn atan(self : bf16) -> bf16
The arctangent, between -pi/2 and pi/2, correctly rounded (through LLVM libc’satanf).fn atan2(self : bf16, x : bf16) -> bf16
The angle to the point (x, this), which isatanwith the quadrant kept; correctly rounded (through LLVM libc’satan2f).fn hypot(self : bf16, other : bf16) -> bf16
The length of the hypotenuse, the square root ofself * self + other * other, without overflow or underflow in between; correctly rounded (in double, rounded once).fn powf(self : bf16, n : bf16) -> bf16
This raised ton, 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 withrem_euclid.fn checked_pow(self : T, exp : T) -> Option<T>
This raised toexp, or nothing if the answer does not fit.fn wrapping_pow(self : T, exp : T) -> T
This raised toexp, wrapping round at the width. The same aspow, under the name that says so.fn ilog(self : T, base : T) -> T
The logarithm tobase, 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 tobase, 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 toexp, 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 ani8; 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 aschecked_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 toexp, or nothing if the answer does not fit.fn wrapping_pow(self : T, exp : T) -> T
This raised toexp, wrapping round at the width. The same aspow, under the name that says so.fn ilog(self : T, base : T) -> T
The logarithm tobase, 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 tobase, 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 toexp, held at the largest value if it overflows.fn is_multiple_of(self : T, rhs : T) -> bool
Doesrhsdivide 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 ofrhsat or above this. Refused on a zerorhs, and when the answer does not fit.fn checked_next_multiple_of(self : T, rhs : T) -> Option<T>
The smallest multiple ofrhsat or above this, or nothing on a zerorhsor 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, aschecked_divis.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 tobuf[at..]and return how many bytes that took. Unsafe because nothing checks that the buffer has room: 20 bytes pastatholds 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 tobuf[at..]and return how many bytes that took. Unsafe for the reasonu64::write_decimalis.
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 tobuf[at..]and return how many bytes that took. Unsafe for the reasonu64::write_decimalis.
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. Au64from 2^63 up cannot be worked out yet: the evaluator’s integers arei64.
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 ownexp: on a GPU, a few fast instructions in the type’s own width. Not correctly rounded, asexpis; aspawnloop calling it still runs in parallel.fn fast_tanh(self : T) -> T
The hyperbolic tangent, by the target’s owntanh, asfast_expis toexp.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 withsign’s sign.fn mul_add(self : T, a : T, b : T) -> T
self * a + bwith 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 withrem_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 betweenloandhi. A NaN stays a NaN. Refused whenlois abovehior 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 tobase, asln(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, asdigits * 10^exponent(Ryu’s algorithm). NaN and the infinities are flagged.fn decimal_len(self : T) -> i64
How many byteswrite_decimalwrites: the length of Rust’sformat!("{}", x).unsafe fn write_decimal(self : T, buf : *mut i8, at : i64) -> i64
Write what Rust’s{}prints tobuf[at..]and return how many bytes that took: the shortest digits in plain notation, as in0.1,-2,1e21as 22 digits,infandNaN. Unsafe because nothing checks that the buffer hasdecimal_len()bytes. No NUL is written.fn scientific_len(self : T) -> i64
How many byteswrite_scientificwrites: the length of Rust’sformat!("{:e}", x).unsafe fn write_scientific(self : T, buf : *mut i8, at : i64) -> i64
Write what Rust’s{:e}prints tobuf[at..]and return how many bytes that took: the shortest digits as1.5e-7,1e0or-0e0. Unsafe for the reasonwrite_decimalis.
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 whatIterator::try_foldreads from its closure’s answers, andOptionandResultimplement it:SomeandOkcarry on,NoneandErrstop. Only what those two need: no residual type and no?.trait Drop
What happens when a value’s owner gives it up. The compiler callsdropwhere the owner is dropped – at the end of its block, before areturn,breakorcontinueleaves 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::dropgives 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 atindex.trait IndexMut
a[i] = x: a mutable reference to the element atindex.
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, holdingoutput.
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 forunwrap_orwhere 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 messagemsg.unwrapwith a message of the caller’s.fn unwrap_or(self : Option<T>, default : T) -> T
The value, or the given one.defaultis evaluated by the caller either way, so keep it cheap. Anunwrap_or_elsethat 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 whileand_thendoes 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 withfapplied, if there is one.fn and_then<U>(self : Option<T>, f : Closure1<T, Option<U>>) -> Option<U>
mapfor anfthat answers with anOptionof 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 andpaccepts it, otherwise nothing.fn map_or<U>(self : Option<T>, default : U, f : Closure1<T, U>) -> U
fapplied to the value, or the given answer when there is none. Both are the same type, which is what separates this frommap.fn unwrap_or_else(self : Option<T>, f : Closure0<T>) -> T
The value, or the answerfgives. Unlikeunwrap_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 doespaccept it?fn is_none_or(self : Option<T>, p : Closure1<T, bool>) -> bool
Is there no value, or doespaccept the one there is? The mirror ofis_some_and.fn or_else(self : Option<T>, f : Closure0<Option<T>>) -> Option<T>
This one if it holds a value, otherwise whatfgives.oris 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
fapplied to the value, or whatdgives when there is none.map_oris 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, leavingvbehind.
Option<Option<T>> methods
fn flatten(self : Option<Option<T>>) -> Option<T>
One level ofOptionremoved:Some(Some(x))isSome(x), anything else isNone.
Option<(T, U)> methods
fn unzip(self : Option<(T, U) -> void
A pair split in two:Some((a, b))gives(Some(a), Some(b)), andNonegives twoNones. The reverse ofzip.
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 pointercountelements further on, or back for a negativecount. Like Rust’swrapping_offset, it only computes the address.fn offset_mut<T>(p : *mut T, count : i64) -> *mut T
offsetfor a mutable pointer.fn add<T>(p : *const T, count : usize) -> *const T
The pointercountelements further on.fn add_mut<T>(p : *mut T, count : usize) -> *mut T
addfor a mutable pointer.fn sub<T>(p : *const T, count : usize) -> *const T
The pointercountelements back.fn sub_mut<T>(p : *mut T, count : usize) -> *mut T
subfor 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_nullfor 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 thisOk?fn into_output(self : Result<T, E>) -> T
TheOkvalue.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 ofis_ok.fn unwrap(self : Result<T, E>) -> T
The value, or a stop.unwrap_oris 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 messagemsg.unwrapwith a message of the caller’s.fn unwrap_or(self : Result<T, E>, default : T) -> T
The value, or the given one on failure.defaultis evaluated by the caller either way, so keep it cheap;unwrap_or_elseis 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 anOption, the mirror ofok.fn map<U>(self : Result<T, E>, f : Closure1<T, U>) -> Result<U, E>
fover the success, the failure untouched.fn map_err<F>(self : Result<T, E>, f : Closure1<E, F>) -> Result<T, F>
fover the failure, the success untouched.fn and_then<U>(self : Result<T, E>, f : Closure1<T, Result<U, E>>) -> Result<U, E>
mapfor anfthat may itself fail, without the nesting.fn unwrap_or_else(self : Result<T, E>, f : Closure1<E, T>) -> T
The value, or whatfmakes of the failure.fn unwrap_err(self : Result<T, E>) -> E
The failure, or a stop. The mirror ofunwrap.fn expect_err(self : Result<T, E>, msg : *const i8) -> E
The failure, or a stop with the messagemsg. The mirror ofexpect.fn is_ok_and(self : Result<T, E>, p : Closure1<T, bool>) -> bool
Did it succeed, and doespaccept the value?fn is_err_and(self : Result<T, E>, p : Closure1<E, bool>) -> bool
Did it fail, and doespaccept 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 whereOption::anddoes not. Bound first and returned once: amatchwhose arms each return an enum is declined by the flat path, andotheris 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
fapplied 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
fover the value, ordover 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 failurefgives. 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
Aboolthat can be read and changed atomically. It is held as anAtomicU8that is always 0 or 1, since LLVM has no atomic operation on a single bit.enum FenceScope
Which threads ascoped_fenceorders 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 useo. Rust panics onReleaseandAcqRel. 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 useo. Rust panics onAcquireandAcqRel. It runs where its caller runs.fn core_atomic_fence_order_ok<D : Topology>(o : AtomicOrdering) -> void
Whether a fence may useo. Rust panics onRelaxed. It runs where its caller runs.fn core_atomic_with_acquire(o : AtomicOrdering) -> AtomicOrdering
o, made strong enough to includeAcquire.fn core_atomic_cas_order(success : AtomicOrdering, failure : AtomicOrdering) -> AtomicOrdering
The one orderingcompare_exchangeuses:successmade strong enough to also coverfailure. AfailureofReleaseorAcqRelfails the assert, as Rust panics.fn core_atomic_bool_byte(b : bool) -> u8
1 fortrue, 0 forfalse.fn fence(order : AtomicOrdering) -> void
Orders the memory operations before it with those after it, for every thread.ordercannot beRelaxed.fn scoped_fence<D : Topology>(order : AtomicOrdering, scope : FenceScope) -> void
fence, for the threads ofscopeonly. It runs where its caller runs, and it writes no memory, so the passes of anacrossloop may call it.ordercannot beRelaxed. 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.ordercannot beRelaxed.
AtomicBool methods
fn new(v : bool) -> AtomicBool
A new atomic holdingv.fn into_inner(self : AtomicBool) -> bool
The value, which is no longer atomic.fn load(self : &AtomicBool, order : AtomicOrdering) -> bool
Reads the value.ordercannot beReleaseorAcqRel.fn store(self : &AtomicBool, val : bool, order : AtomicOrdering) -> void
Writesval.ordercannot beAcquireorAcqRel.fn swap(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
Writesvaland answers the old value.fn compare_exchange(self : &AtomicBool, current : bool, new : bool, success : AtomicOrdering, failure : AtomicOrdering) -> Result<bool, bool>
Storesnewif the value iscurrent. AnswersOkwith the old value when it stored,Errwith 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 withval; answers the old value.fn fetch_or(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
Logical or withval; answers the old value.fn fetch_xor(self : &AtomicBool, val : bool, order : AtomicOrdering) -> bool
Logical exclusive or withval; 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 bytenandwould leave 254, so this is an exclusive or withtruewhenvalis true, and a store oftruewhen 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 holdingfalse.
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 holdingv.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.ordercannot beReleaseorAcqRel.fn store(self : &T, val : U, order : AtomicOrdering) -> void
Writesval.ordercannot beAcquireorAcqRel.fn compare_exchange(self : &T, current : U, new : U, success : AtomicOrdering, failure : AtomicOrdering) -> Result<U, U>
Storesnewif the value iscurrent. AnswersOkwith the old value when it stored,Errwith 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 withf(value)until no other write gets in between, and answersOkwith the old value. IffanswersNone, stops and answersErrwith the value.fn core_atomic_cmpxchg(self : &T, current : U, new : U, order : AtomicOrdering) -> U
Onellvm.cmpxchgatorder, answering the value it found. The failure ordering is the strongest oneorderallows.
(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 oft. It reads no element.fn X<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>, i : i32, order : AtomicOrdering) -> void
Readst[i].ordercannot beReleaseorAcqRel.fn Y<M : Memory, D : Topology>(t : &Tensor<T, [?], Memory : : M>, i : i32, val : T, order : AtomicOrdering) -> void
Writesvaltot[i].ordercannot beAcquireorAcqRel.
(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
Writesnewtot[i]if it holdscurrent, and answers the value it found. So it wrote when the answer iscurrent. The failure ordering is the strongest oneorderallows.
(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
secsseconds andnanosnanoseconds. Nanoseconds of a second or more carry into the seconds; refused when the seconds then overflow.fn from_secs(secs : u64) -> Durationfn from_millis(millis : u64) -> Durationfn from_micros(micros : u64) -> Durationfn from_nanos(nanos : u64) -> Durationfn zero() -> Duration
No time at all.fn max_value() -> Duration
The longest duration: the largestu64of seconds and 999,999,999 nanoseconds.fn is_zero(self : &Duration) -> boolfn 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) -> f64fn as_secs_f32(self : &Duration) -> f32fn from_secs_f64(secs : f64) -> Duration
secsseconds, to the nearest nanosecond. Refused whensecsis negative, NaN or too large for aDuration.fn from_secs_f32(secs : f32) -> Duration
Asfrom_secs_f64, from anf32.fn mul_f64(self : &Duration, rhs : f64) -> Duration
This duration timesrhs, throughf64seconds.fn mul_f32(self : &Duration, rhs : f32) -> Durationfn div_f64(self : &Duration, rhs : f64) -> Duration
This duration divided byrhs, throughf64seconds.fn div_f32(self : &Duration, rhs : f32) -> Durationfn div_duration_f64(self : &Duration, other : &Duration) -> f64
How many timesotherfits in this duration, as anf64.fn div_duration_f32(self : &Duration, other : &Duration) -> f32fn checked_add(self : &Duration, other : &Duration) -> Option<Duration>
The sum, orNonewhen it would overflow.fn checked_sub(self : &Duration, other : &Duration) -> Option<Duration>
The difference, orNonewhenotheris longer than this duration.fn checked_mul(self : &Duration, rhs : u32) -> Option<Duration>
This duration timesrhs, orNonewhen it would overflow.fn checked_div(self : &Duration, rhs : u32) -> Option<Duration>
This duration divided byrhs, rounded down to a nanosecond, orNonewhenrhsis 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 whenotheris longer.fn saturating_mul(self : &Duration, rhs : u32) -> Duration
This duration timesrhs, 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 tupleletormatchcalls 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>) -> boolfn has_4_elements<A, B, C, D>(_t : &Tuple4<A, B, C, D>) -> boolfn has_5_elements<A, B, C, D, E>(_t : &Tuple5<A, B, C, D, E>) -> boolfn 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
bytesof uninitialised host memory atalign, 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 atp, ofold_bytes, resized tonew_bytes, with its contents and its alignment. Null when there is no memory, and thenpis still valid. A nullpmakes a new block.fn dealloc(p : *mut i8, align : i64) -> void
Gives back a block fromallocorgrow, 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
Openpathwith 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 returnedFileholds 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 thatpathpoints at a NUL-terminated string.unsafe fn read(self : *mut File, buffer : *mut u8, len : u64) -> u64
Read up tolenbytes intobufferand 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 becausebuffermust have room forlenbytes; nothing here checks.unsafe fn write(self : *mut File, buffer : *const u8, len : u64) -> u64
Write up tolenbytes frombufferand answer how many were taken. Zero means a null file or buffer, or a write error, with the same lack of distinctionreadhas. 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.whenceis 0 from the start, 1 from the current position, 2 from the end, as libc’sSEEK_SET,SEEK_CURandSEEK_END. Answers -1 for a null file, an unknownwhence, 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 behindFile::open. Null when the open fails.fn vx_file_read(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
The Rust core behindFile::read.fn vx_file_write(ptr : *mut i8, buffer : *const u8, len : u64) -> u64
The Rust core behindFile::write.fn vx_file_seek(ptr : *mut i8, offset : i64, whence : i32) -> i64
The Rust core behindFile::seek.fn vx_file_drop(ptr : *mut i8) -> void
The Rust core behind dropping aFile.fn fopen(path : *const i8, mode : *const i8) -> *mut i8
C’sfopen, for code that wants aFILE*rather than the Rust-backedFile.fn fread(ptr : *mut u8, size : i64, nmemb : i64, stream : *mut i8) -> i64
C’sfread: the number of whole items read, not the number of bytes.fn fclose(stream : *mut i8) -> i32
C’sfclose.fn fileno(f : *mut i8) -> i32
C’sfileno: the descriptor behind aFILE*, for handing tommap.fn fseek(f : *mut i8, offset : i64, whence : i32) -> i32
C’sfseek.fn ftell(f : *mut i8) -> i64
C’sftell: 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’smmap, declared here so a file can be mapped without importingstd::mmap.fn munmap(addr : *mut i8, len : i64) -> i32
C’smunmap.
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 equalsexpected. 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 fromi32toi32, 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), socontains_keyis 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 fromi32tof32, 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), socontains_keyis 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 ofi32, 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
Writelenbytes to standard output and answer how many were taken. A short write is possible and is not retried here. Unsafe becausebuffermust havelenbytes to read.unsafe fn stderr_write(buffer : *const u8, len : u64) -> u64
Writelenbytes to standard error, with the same caveats asstdout_write.unsafe fn stdin_read(buffer : *mut u8, len : u64) -> u64
Read up tolenbytes from standard input and answer how many arrived. Zero means end of input or an error, which are not told apart. Unsafe becausebuffermust have room forlenbytes.
C bindings (the native functions this module is built on)
fn vx_stdout_write(buffer : *const u8, len : u64) -> u64
The Rust core behindstdout_write.fn vx_stderr_write(buffer : *const u8, len : u64) -> u64
The Rust core behindstderr_write.fn vx_stdin_read(buffer : *mut u8, len : u64) -> u64
The Rust core behindstdin_read.
std::libc
Direct bindings to the C library.
Functions (bound directly to C)
fn open(path : *const i8, flags : i32) -> i32
C’sopen: a file descriptor, or -1 on failure.flagsis the platform’s, not Vx’s.fn close(fd : i32) -> i32
C’sclose: 0, or -1 on failure.fn lseek(fd : i32, offset : i64, whence : i32) -> i64
C’slseek: the new offset, or -1.whenceis 0 from the start, 1 from the current position, 2 from the end.fn malloc(size : usize) -> *mut i8
C’smalloc:sizebytes of uninitialised memory, or null.fn calloc(count : usize, size : usize) -> *mut i8
C’scalloc:count * sizebytes set to zero, or null.fn realloc(ptr : *mut i8, size : usize) -> *mut i8
C’srealloc: the block resized, and moved if need be. Null when there is no memory, and thenptris still valid. The result hasmalloc’s alignment, not the one thataligned_allocgave.fn free(ptr : *mut i8) -> void
C’sfree: gives back a block from any of these,aligned_allocincluded. Null is a no-op.fn aligned_alloc(alignment : usize, size : usize) -> *mut i8
C11’saligned_alloc:sizebytes atalignment, or null.sizemust be a multiple ofalignment. Microsoft’s C runtime does not have it.
std::llama
Helpers used by the Llama 2 example.
Types
struct LlamaConfigstruct TransformerWeightOffsetsstruct 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 fromc, 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_sizemust match the checkpoint’s.fn decode(self : &Tokenizer, prev_token : i32, token : i32) -> String
The text for one token, as an ownedString.prev_tokendecides 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 ofi32.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_tokenis 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’smmap: maplengthbytes offdinto memory. AnswersMAP_FAILEDrather than null on failure, which is -1 cast to a pointer and which Vx cannot test for (Vx#714).protandflagsare the platform’s.fn munmap(addr : *mut i8, length : i64) -> i32
C’smunmap: 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 toaddr, written ashost: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 thataddrpoints at a NUL-terminated string.unsafe fn read(self : *mut TcpStream, buffer : *mut u8, len : u64) -> u64
Read up tolenbytes intobufferand 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 becausebuffermust have room forlenbytes.unsafe fn write(self : *mut TcpStream, buffer : *const u8, len : u64) -> u64
Write up tolenbytes frombufferand 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 toaddr, written ashost: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 intobufferand answer its length. A datagram longer thanlenis 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 oflenbytes toaddr, 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 onaddr, written ashost:port. A failure cannot be detected, for the reasonTcpStream::connectgives (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 behindTcpStream::connect. Null when the connection fails.fn vx_tcp_stream_read(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
The Rust core behindTcpStream::read.fn vx_tcp_stream_write(ptr : *mut i8, buffer : *const u8, len : u64) -> u64
The Rust core behindTcpStream::write.fn vx_tcp_stream_drop(ptr : *mut i8) -> i32
The Rust core behind dropping aTcpStream.fn vx_udp_socket_bind(c_addr : *const i8) -> *mut i8
The Rust core behindUdpSocket::bind. Null when the bind fails.fn vx_udp_socket_recv(ptr : *mut i8, buffer : *mut u8, len : u64) -> u64
The Rust core behindUdpSocket::recv.fn vx_udp_socket_send_to(ptr : *mut i8, buffer : *const u8, len : u64, c_addr : *const i8) -> u64
The Rust core behindUdpSocket::send_to.fn vx_udp_socket_drop(ptr : *mut i8) -> i32
The Rust core behind dropping aUdpSocket.fn vx_tcp_listener_bind(c_addr : *const i8) -> *mut i8
The Rust core behindTcpListener::bind. Null when the bind fails.fn vx_tcp_listener_accept(ptr : *mut i8) -> *mut i8
The Rust core behindTcpListener::accept. Blocks until a connection arrives.fn vx_tcp_listener_drop(ptr : *mut i8) -> i32
The Rust core behind dropping aTcpListener.
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 fourRngneeds, 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.spareholds the second of the pairnormalproduces, 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 themathdialect so this module needs no libm.core::numhas the same method; this is kept becausenormalruns before an import of it would settle, and a local one keeps the dependency tocore::numalone.
SplitMix64 methods
fn seeded(seed : u64) -> SplitMix64
A generator started atseed. 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% 256would 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
Abf16uniform 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 probabilityp. 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 plainnext_u64() % boundis biased wheneverbounddoes not divide 2^64: the first2^64 % boundvalues come up once more often than the rest. The draws that would land in that overhang are rejected and taken again.flooris where the overhang ends, and0 - boundis2^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),loincluded andhinot. The span is measured inu64so that a range spanning zero, or the whole ofi64, is still one subtraction:hi - loini64would 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 caveatrange_f64gives 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: meanmean, standard deviationstddev.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.countelements 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
Fillcountelements with draws uniform in [lo, hi).outmust have room forcountof them; nothing here checks.fn fill_normal_f32(self : &mut Rng, out : *mut f32, count : i32, mean : f32, stddev : f32) -> i32
Fillcountelements with normal draws of the given mean and standard deviation.outmust have room forcountof them. A negativestddevmirrors the distribution rather than being refused.fn fill_f16(self : &mut Rng, out : *mut f16, count : i32) -> i32
Fillcountelements withf16draws 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-lanef32vectors, written toout. All three pointers must address fourf32s; nothing here checks.outmay aliasaorb.unsafe fn simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The elementwise difference of two four-lanef32vectors, written toout. All three pointers must address fourf32s; nothing here checks.outmay aliasaorb.unsafe fn simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The elementwise product of two four-lanef32vectors, written toout. All three pointers must address fourf32s; nothing here checks.outmay aliasaorb.unsafe fn simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The elementwise quotient of two four-lanef32vectors, written toout. All three pointers must address fourf32s; nothing here checks.outmay aliasaorb.unsafe fn simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32
a * b + celementwise over four lanes, written toout. 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 behindsimd_add_f32x4.fn vx_simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The Rust core behindsimd_sub_f32x4.fn vx_simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The Rust core behindsimd_mul_f32x4.fn vx_simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32
The Rust core behindsimd_div_f32x4.fn vx_simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32
The Rust core behindsimd_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 thatc_stris a valid pointer to a NUL-terminated run of bytes. The copy is owned by the newString; 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 requirementfrom_c_strhas.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 freshCStringand hands out its raw pointer, so every call costs a copy that is never released: the matchingvx_string_free_c_stris 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::strreplaces this once a string literal carries its length (Vx#532).unsafe fn string_compare(s1 : *const i8, s2 : *const i8) -> i32
C’sstrcmp: negative whens1sorts first, positive whens2does, 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 reasonstring_lengthis.unsafe fn parse_int(s : *const i8) -> i32
The leading run of decimal digits as ani32. 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 thani32holds wraps.core::str’sparsereplaces it (Vx#532).
T methods, stamped for 8 instantiations
fn to_string(self : T) -> String
This number in decimal, as an ownedStringthe caller mustdrop.
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 behindString::new.fn vx_string_from_c_str(ptr : *const i8) -> *mut i8
The Rust core behindString::from_c_str.fn vx_string_push_c_str(ptr : *mut i8, c_str : *const i8) -> i32
The Rust core behindString::push_c_str.fn vx_string_len(ptr : *mut i8) -> i32
The Rust core behindString::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 withvx_string_free_c_str;String::as_c_strdoes not, and leaks.fn vx_string_free_c_str(ptr : *const i8) -> i32
Release whatvx_string_as_c_strreturned. Nothing in Vx calls this yet.fn vx_string_drop(ptr : *mut i8) -> i32
The Rust core behindString::drop.fn vx_string_with_len(len : i64) -> *mut i8
A string oflenNUL bytes, forto_stringto overwrite with digits.fn vx_string_as_mut_ptr(ptr : *mut i8) -> *mut i8
The bytes of a string made byvx_string_with_len, writable.
std::tensor
Operations on Tensor, including shape queries and elementwise maths.
Types
enum PadMode
The second argument oft.reshape(shape, mode), for a new shape with a different number of elements.Trimkeeps the first elements oftin row order, as a view oft.Padmakes a new tensor that holds all oft’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-d1tensor holding a copy ofd1elements read fromptr. The elements are copied, so the tensor does not alias the buffer and outlives it.ptrmust addressd1elements; nothing here checks.fn from_ptr_2d(ptr : *mut T, d1 : i32, d2 : i32) -> Tensor<T, [?, ?]>
Ad1-by-d2tensor holding a copy ofd1 * d2elements read fromptrin row-major order. Copied, asfrom_ptr_1dis.fn slice_2d(self : &Tensor<T, [?, ?]>, row : i32, d1 : i32, d2 : i32) -> Tensor<T, [?, ?]>
Ad1-by-d2tensor read from one row of this one, taken row-major from its start. A copy, not a view. Everyslice_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, [?, ?]>
Ad1-by-d2tensor read from row 0 beginning atstart, reshaped row-major. A copy.fn slice_1d(self : &Tensor<T, [?, ?]>, start : i32, d1 : i32) -> Tensor<T, [?, ?]>
A 1-by-d1tensor read from row 0 beginning atstart. A copy.fn fill(self : &mut Tensor<T, [?, ?]>, val : T) -> void
Set every element toval. Lowered throughlinalg, 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 withsrc’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 toval, asfilldoes. The two differ in thelinalgform 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 toval. 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 elementxwithexp(x) / (sum of exp over all elements), so the elements are positive and add up to 1. The largest element is subtracted beforeexp, which gives the same answer without overflowing. Vector loops, with theexpoftensor_exp_shifted_loop: within 1 ulp off32::exp.
Tensor<T, [?]> methods, stamped for 4 instantiations
fn add_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
Addsotherto this tensor, element by element. Refused for different lengths.fn sub_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
Subtractsotherfrom this tensor, element by element.fn mul_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
Multiplies this tensor byother, element by element.fn div_assign(self : &mut Tensor<T, [?]>, other : &Tensor<T, [?]>) -> void
Divides this tensor byother, element by element.fn add_scalar(self : &mut Tensor<T, [?]>, a : T) -> void
Addsato every element.fn scale(self : &mut Tensor<T, [?]>, a : T) -> void
Multiplies every element bya.fn scaled_add(self : &mut Tensor<T, [?]>, a : T, x : &Tensor<T, [?]>) -> void
Addsatimesxto this tensor: BLAS’saxpy. Refused for different lengths.fn map_inplace(self : &mut Tensor<T, [?]>, f : Closure1<T, T>) -> void
Replaces every element byfof 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 betweenloandhi, asclampon 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 ofsum, written out rather than callingself.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 ofdotwith the tensor on both sides, written out rather than callingself.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 ofsum, written out rather than callingself.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 ofdotwith the tensor on both sides, written out rather than callingself.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, asmaxon 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, asmaxdoes;Nonefor an empty tensor or one holding only NaN. Two passes: the vector loop ofmax, 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;Nonefor 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 elementxwithexp(x) / (sum of exp over all elements), so the elements are positive and add up to 1. The largest element is subtracted beforeexp, 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 elementsn(numpy’svar; divide byn - 1for 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 ofvariance.
T = f32, f64
Tensor<T, [?]> methods, stamped for 2 instantiations
fn variance(self : &Tensor<T, [?]>) -> f32
Asvarianceonf32, computed inf32.fn std_dev(self : &Tensor<T, [?]>) -> f32
Asstd_devonf32, computed inf32.
T = f16, bf16
Tensor<T, [?, ?]> methods, stamped for 4 instantiations
fn add_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
Addsotherto this tensor, element by element. Refused for different shapes.fn sub_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
Subtractsotherfrom this tensor, element by element.fn mul_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
Multiplies this tensor byother, element by element (not a matrix product).fn div_assign(self : &mut Tensor<T, [?, ?]>, other : &Tensor<T, [?, ?]>) -> void
Divides this tensor byother, element by element.fn add_scalar(self : &mut Tensor<T, [?, ?]>, a : T) -> void
Addsato every element.fn scale(self : &mut Tensor<T, [?, ?]>, a : T) -> void
Multiplies every element bya.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 betweenloandhi, asclampon 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 alongaxis: of each column for axis 0, of each row for axis 1.fn mean_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<T, [?]>
The average alongaxis: 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 alongaxis: 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 alongaxis: 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 alongaxis: of each column for axis 0, of each row for axis 1.fn mean_axis(self : &Tensor<T, [?, ?]>, axis : i32) -> Tensor<f32, [?]>
The average alongaxis: 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 alongaxis: 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 alongaxis: 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 asother, element[i][j]the sum overkofself[i][k] * other[k][j]. Refused when this tensor’s column count differs fromother’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. Anf32holds about seven digits, so a long-running process loses resolution as the value grows –unix_timestampis thef64one.fn sleep(seconds : f32) -> i32
Pause this thread for at leastseconds. It may be longer; it is never shorter.fn unix_timestamp() -> f64
Seconds since the Unix epoch, as anf64. Wall-clock time, so it can move backwards when the system clock is adjusted;nowis 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 becausenameandunitmust point at NUL-terminated strings.
C bindings (the native functions this module is built on)
fn vx_get_time() -> f32
The Rust core behindnow.fn vx_sleep(seconds : f32) -> i32
The Rust core behindsleep.fn vx_unix_timestamp() -> f64
The Rust core behindunix_timestamp.fn vx_bench_report(name : *const i8, unit : *const i8, value : f32) -> i32
The Rust core behindbench_report.
std::vec
Vec<T>, a growable array.
Types
struct Vec<T>
A growable array ofT. It holds a buffer fromstd::allocand does its own element loads and stores. The buffer is released when the vector is dropped.struct VecIter<T>
An iterator over references to aVec<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 aVec<T>and hands out its elements by value. Built byinto_iter, which is whatfor x in vcalls. It holds the vector itself rather than a reference to it, asVecIterdoes: the vector was moved intointo_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 aVec<T>’s elements. Built byiter_mut. It borrows the vector mutably, so the vector cannot be used another way while the iterator is used.struct VecMap<T, NewItem>
The iteratorVecIter::mapbuilds: the inner walk plus the function applied to each item.
Functions
fn vec_bytes(cap : i64, elem_size : i64) -> i64
The size in bytes ofcapelements ofelem_sizebytes. Stops the program when the product does not fit in ani64.fn vec_align() -> i64
The alignment of a buffer: 16 bytes, the most any Vx scalar type needs, until Vx hasalignof<T>().fn vec_alloc(elem_size : i64, cap : i64) -> *mut i8
A buffer forcapelements ofelem_sizebytes, 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 ofold_capelements resized tonew_cap, keeping its contents. A nullptrmakes a new buffer.ptris invalid afterwards.fn vec_check_index(index : i64, len : i64) -> i32
Stops the program whenindexis outside0..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 forcapacityelements 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 asas_mut_slicedoes. Nothing frees the buffer, so the reference stays valid.fn as_slice(self : &Vec<T>) -> &T
A reference to the first element, with the caveatas_mut_slicedescribes.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 atindex.v[i]reads through this when the element type is notCopy.Panics
Whenindexis negative or not belowlen. 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 atindex, dropping the one that was there.Panics
Asget_refdoes, and for the same reason. Setting past the end does not extend the vector;pushis 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, orNonewhen the vector is empty. The capacity stays, so a laterpushreuses 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 atindex. For an element type that is notCopy,get_refgives a reference instead.Panics
Asget_refdoes.
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 itemiterhas left, in order.fn extend_one(self : &mut Vec<T>, item : T) -> i32
Pushitem.
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 emptyVec, asnewmakes.
FromIterator<T> for Vec<T> methods
fn from_iter<I : Iterator>(iter : I) -> Vec<T>
A newVecholding every itemiterhas 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 withfapplied 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 withfapplied.
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 freshVec, 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.rsbyscripts/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 —
W1001–W1034(25 codes) - Parser Errors —
E1001–E1013(13 codes) - Name Resolution Errors —
E2001–E2007(7 codes) - Type Errors —
E3001–E3059(58 codes) - Borrow/Ownership Errors —
E4001–E4014(14 codes) - Safety Errors —
E5001–E5002(2 codes) - Topology/Hardware Errors —
E6001–E6034(34 codes) - Tensor/Math Errors —
E7001–E7004(4 codes) - Contract/Verification Errors —
E8001–E8007(7 codes)
Warnings
Reported without stopping the compile. A warning means the program is accepted but something in it is probably not what was intended.
| Code | Meaning |
|---|---|
W1001 | Unused variable binding |
W1002 | A function in the program’s own file that nothing calls, directly or through other functions, starting from main. |
W1003 | Unreachable code after return, break, or continue |
W1004 | let 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. |
W1005 | A 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. |
W1007 | An 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. |
W1008 | A match arm whose body is empty, such as 1 => {}. |
W1009 | Unused function parameter |
W1010 | An unsafe block with nothing inside it that needs unsafe. |
W1013 | An as cast to the type the value already has, such as x as i32 when x is an i32. |
W1014 | An 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. |
W1022 | A transfer into the memory the value is already in. It only makes a copy. |
W1023 | spawn on(Topology::Current): the region runs where the code around it already runs. |
W1024 | Implicit 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. |
W1025 | Use 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. |
W1026 | A 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. |
W1027 | A declared relaxed transfer edge does not preserve visibility (the seam engine shows a consumer may read stale data). See the topology coherence check. |
W1028 | The 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. |
W1029 | A 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. |
W1030 | A 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. |
W1031 | A 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). |
W1033 | The 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. |
W1034 | The 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.
| Code | Meaning |
|---|---|
E1001 | Unexpected token |
E1002 | Unexpected end of file |
E1003 | Expected identifier |
E1004 | Expected type |
E1005 | Expected expression |
E1006 | Unclosed delimiter (paren/brace/bracket) |
E1007 | Missing semicolon |
E1008 | Missing comma |
E1009 | Invalid operator |
E1010 | Unknown topology variant |
E1011 | Unknown memory space |
E1012 | Unknown element type |
E1013 | Invalid macro invocation syntax |
Name Resolution Errors
Raised when a name cannot be resolved to a declaration, or resolves to something of the wrong kind.
| Code | Meaning |
|---|---|
E2001 | Undefined variable |
E2002 | Undefined function |
E2003 | Unknown enum |
E2004 | Unknown enum variant |
E2005 | Unknown struct field |
E2006 | Module does not export function |
E2007 | Method 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.
| Code | Meaning |
|---|---|
E3001 | Type mismatch in variable declaration |
E3002 | Type mismatch in return |
E3003 | Type mismatch in function argument |
E3004 | Type mismatch in binary operation |
E3005 | Type mismatch in relational operation |
E3006 | Type mismatch in logical operation |
E3007 | If branch type mismatch |
E3008 | Enum payload type mismatch |
E3009 | Enum payload arity mismatch |
E3010 | Function argument count mismatch |
E3011 | Unsupported cast |
E3012 | Type mismatch in struct field initialization |
E3013 | Missing struct field in initialization |
E3014 | Range type mismatch |
E3015 | Trait not implemented |
E3016 | A 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();. |
E3017 | Closure argument count or type mismatch |
E3018 | An 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. |
E3019 | A 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. |
E3036 | A 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. |
E3020 | A 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. |
E3021 | An 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. |
E3022 | An 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. |
E3023 | A 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. |
E3025 | extent(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. |
E3026 | A 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. |
E3028 | A 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. |
E3029 | A 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. |
E3030 | An 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. |
E3031 | impl 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. |
E3032 | A 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. |
E3033 | A 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. |
E3034 | A 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. |
E3035 | A 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. |
E3037 | An 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. |
E3038 | type 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. |
E3039 | I::Item disagrees with the impl for what I is: an argument bound it to one type and that impl binds Item to another. |
E3040 | I::Item where no impl for what I is binds an associated type named Item: I has no such bound, or the name is misspelled. |
E3041 | A 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. |
E3042 | A bound on a type parameter, T : Name, where no trait called Name is declared or imported. Usually a misspelling or a missing import. |
E3043 | A 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. |
E3044 | A 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]. |
E3045 | A number literal that does not fit usize, which holds 0 up to the largest i64. |
E3046 | A 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. |
E3047 | A 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. |
E3048 | An 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. |
E3049 | Two 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. |
E3050 | A 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. |
E3051 | A 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. |
E3052 | Arithmetic 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. |
E3053 | An 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 {}. |
E3054 | unsafe 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. |
E3055 | A 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. |
E3056 | spawn { .. } 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. |
E3057 | A 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(). |
E3058 | A 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. |
E3059 | An 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.
| Code | Meaning |
|---|---|
E4001 | Use of moved or consumed linear variable |
E4002 | Cannot access mutably borrowed variable |
E4003 | Cannot borrow as mutable (already immutably borrowed) |
E4004 | Cannot borrow (already mutably borrowed) |
E4005 | A 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. |
E4006 | An 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. |
E4007 | A 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. |
E4008 | A 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. |
E4009 | An 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. |
E4010 | A 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. |
E4011 | A 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. |
E4012 | A 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. |
E4013 | A 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. |
E4014 | A 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.
| Code | Meaning |
|---|---|
E5001 | Unsafe 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. |
E5002 | Unsafe 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.
| Code | Meaning |
|---|---|
E6001 | Topology mismatch in function call |
E6002 | Cannot transfer between memory spaces (no hardware path) |
E6003 | A 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. |
E6004 | Transfer violates the boundary contract at a seam (per-seam local-completeness / soundness obligation is sat; a stale read can violate the contract). |
E6005 | A user-defined topology declaration is incoherent: it cannot see its own default memory space (default_space ∉ visibility). See the topology coherence check. |
E6006 | A Memory declaration’s within: hierarchy forms a cycle (a space contains itself). |
E6007 | A Memory sub-space’s capacity exceeds its parent’s capacity (a child cannot be larger than what contains it). |
E6008 | A Memory declaration has a non-positive capacity, bandwidth, or granule. |
E6009 | A statically-shaped tensor placed in a memory space exceeds that space’s capacity. |
E6010 | The working set placed in a memory space (the sum of its tiles) exceeds capacity. Downgraded to W1028 when the space is declared overcommit. |
E6011 | A sub-space’s scope is broader than its parent’s (locality must narrow down within:). |
E6012 | The 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). |
E6013 | A 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. |
E6014 | A 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. |
E6015 | A 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). |
E6016 | A 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. |
E6017 | A 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. |
E6018 | A 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. |
E6019 | raw::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). |
E6020 | raw::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. |
E6021 | A 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). |
E6022 | An 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. |
E6023 | An 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). |
E6024 | A 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. |
E6025 | A 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. |
E6026 | A 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. |
E6027 | A 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. |
E6028 | A 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. |
E6029 | A 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. |
E6030 | A 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. |
E6031 | A 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. |
E6032 | An 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. |
E6033 | An 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. |
E6034 | An 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.
| Code | Meaning |
|---|---|
E7001 | Matmul dimension mismatch |
E7002 | Matmul element type mismatch |
E7003 | Reshape arithmetic mismatch |
E7004 | Non-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.
| Code | Meaning |
|---|---|
E8001 | Cannot prove postcondition |
E8002 | Comptime assert failed |
E8003 | Compile-time index out of range |
E8004 | Compile-time evaluation exceeded the call-depth limit |
E8005 | Compile-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. |
E8006 | A call does not meet the called function’s precondition (requires) |
E8007 | An 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.
| Flag | What it does |
|---|---|
--run | Compile and execute immediately, propagating the program’s exit code |
-c | Compile to an object file |
--emit-mlir | Emit the MLIR representation |
--emit-llvm | Emit LLVM IR |
--print-ast | Parse and typecheck, then print the AST |
--parse-only | Lex and parse only |
--emit-interface | Serialize 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
| Flag | What 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-seams | Discharge 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:
- The standard library. An installed toolchain keeps it in
stdlib/besidebin/, and a development build uses thestdlib/of the source tree it was built from.--std-path DIRreplaces it. Give the flag once for each directory. - The directory of the file you compile, so
import helper;reads thehelper.vxbeside 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
| Tool | Purpose |
|---|---|
vx-format | The canonical source formatter |
vx-opt | MLIR pass driver for the Vx dialect |
vx-analyzer | Language server |
vx-api | Compares the interfaces of two versions of a library and reports the version bump |
cargo vx-bench | Runs 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.
| Core | option, result, box, alloc, closure, iter |
| Collections | vec, hash_map, hash_set, string |
| Numerics | math, simd, tensor |
| System | io, fs, net, mmap, time, libc |
| Testing | googletest |
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
| Variable | Effect |
|---|---|
VX_RUNTIME_LIB_DIR | Where to find the Vx runtime library |
LLVM_CONFIG_PATH, MLIR_TRANSLATE_PATH, OPT_PATH, LLC_PATH, CLANG_PATH | Absolute paths to the LLVM tools, if they are not on PATH |
VX_DISPATCH_LIB | The accelerator dispatch backend to load |
ENZYME_LIB | The Enzyme plugin, for autodiff |
VX_ALLOW_UNVERIFIED | Downgrade 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
.vxfile that reproduces it, - the exact command you ran,
- what you expected and what happened,
vxc --versionand 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:
| Issue | What it is |
|---|---|
| Vx#494 | The unused-variable warning fires on a variable used only through method calls |
| Vx#445 | Twelve warning codes are declared but never emitted |
| Vx#423 | Issue 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:
- a machine file declaring its memory hierarchy and interconnect, and
- an MLIR pass plugin under
src/plugin/that lowers the Vx dialect for that target.
docs/adding_a_topology.md walks through the process.