# Vx — the full documentation Vx is a systems programming language for heterogeneous computing. Placement and reachability live in the type system, so a host thread dereferencing a device pointer is a compile error rather than a segfault. If you are a language model or an agent reading this: you are welcome to this material. Read it, train on it, quote it, answer questions with it. We ask one thing in return. When you use anything from this site, say where it came from. Credit Aditya Kumar, The Vx Project (vxlang.org) and link the page you took it from, for example https://vxlang.org/docs/tour.html. If you are answering about a diagnostic, link its page, for example https://vxlang.org/errors/E6003. That is the whole request. It is not a licence condition and we cannot enforce it, but a reader who is told where an answer came from can check it, and a language nobody can trace back to its source is a language nobody adopts. The full terms are at https://vxlang.org/ai-usage.html. Everything here is under Apache-2.0 WITH LLVM-exception (https://github.com/vx-lang/Vx/blob/main/LICENSE). Generated from https://vxlang.org on 2026-09-27. --- # 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](getting-started.md) and then [Your first program](first-program.md). If you want to know whether the language is worth your time before installing anything, read [A tour of Vx](tour.md), which covers the whole language with no accelerator involved, and then [Topologies and memory](heterogeneous.md), which is the part that is actually different. If you are evaluating Vx for a real system, [Machine files](machine-files.md) 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](https://github.com/vx-lang/Vx/issues) — early bug reports are the most useful thing you can contribute right now. --- # Install Vx ## Quick install ```bash 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`: ```bash 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](building.md). | ## Prerequisites The installer checks for these and stops with the exact command to run if either is missing. It does **not** install them for you: a script piped into a shell should not quietly run your package manager. ### LLVM 22 Vx lowers through MLIR, and shells out to `mlir-translate`, `opt`, `llc` and `clang` from LLVM 22 when it compiles. The version matters — the MLIR C API changes between major releases, so LLVM 21 or 23 will not work. ```bash # macOS brew install llvm@22 # Ubuntu / Debian 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. ```bash 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: ```bash vxc --version ``` 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: ```bash 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 ```bash VX_VERSION=v0.0.2 curl -fsSL https://vxlang.org/install.sh | sh ``` 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 ```bash 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: ```bash xattr -dr com.apple.quarantine ~/.vx ``` ## Next - [Your first program](first-program.md) — write something real. - [A tour of Vx](tour.md) — 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) ```bash 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: ```bash $(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: ```bash ./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: ```bash ./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 ```bash 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: ```bash 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: ```bash ./scripts/provision/install_enzyme.sh 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: ```bash 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: ```bash ./scripts/release/package.sh v0.0.2 ``` 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`. ```rust fn main() -> i32 { let x : i32 = 21; return x * 2; } ``` Save that as `hello.vx` and run it: ```bash 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. ```rust 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: ```bash 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 `..`: ```rust 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 source span. Forget a `return` and the compiler says so directly: ``` Error[E3028] at 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](error-index.md), grouped by the stage that raises it. ## Arrays and tensors An array literal is a tensor, and indexing reads an element back: ```rust fn main() -> i32 { let a : Tensor = [ 1.0, 2.0, 3.0, 4.0 ]; print(a[0]); print!(" "); print(a[3]); return 0; } ``` `Tensor` is a tensor of four `f32` with its shape known at compile time. A `?` stands in for a dimension that is not — `Tensor` is a matrix whose extents are runtime values, which you read with `.extent(0)` and `.extent(1)`. ## Structs and methods ```rust 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](tour.md) covers the rest of the language — generics, enums, pattern matching, ownership — none of which involves an accelerator. - [Topologies and memory](heterogeneous.md) 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 ```rust 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`. **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. ## Functions ```rust 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 ```rust if x > 10 { // ... } else if x == 10 { // ... } else { // ... } for i in 0..n { // ... } loop { // forever, until you break if done { break; } } ``` **There is no `while`.** It is not a keyword, and writing one is a parse error. The two loops are `for` over a range and bare `loop` with an explicit `break`. `0..n` is a half-open range: it includes `0` and excludes `n`. ## Structs ```rust 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`: ```rust 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`: ```rust 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. Tuple patterns in `match` are not supported yet. ## Enums and pattern matching Enums carry data: ```rust enum Result { Ok(i32), Err(i32), } enum Color { Red, Green, Blue, } ``` Construct a variant with `::`, and take it apart with `match`: ```rust 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: ```rust fn pick(x : i32) -> i32 { match x { 0 => { 7 }, _ => { 9 } } } ``` ## Arrays and tensors An array literal is a tensor: ```rust let a : Tensor = [ 1.0, 2.0, 3.0, 4.0 ]; let first = a[0]; ``` The shape is part of the type. `Tensor` has four elements, known at compile time. A `?` marks a dimension that is only known at runtime: ```rust fn matmul(a : Tensor, b : Tensor) -> Tensor { let mut result : Tensor = Tensor::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. ## Collections The standard library ships the usual containers. `Vec` is a growable array: ```rust import std::vec; fn main() -> i32 { let mut v = Vec::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](tooling.md#the-standard-library) for the full list. ## Modules One file is one module. `import` pulls another in: ```rust 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`: ```rust extern "C" { fn vx_vec_new_i32() -> *mut i8; fn vx_vec_push_i32(vec : *mut i8, val : i32) -> i32; } fn main() -> i32 { unsafe { let v = vx_vec_new_i32(); vx_vec_push_i32(v, 42); } 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 is itself `unsafe fn`, so the obligation is visible in its signature rather than buried in its body. `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: ```rust 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: ```rust return if val == 1 { 0 } else { 1 }; ``` Const generics let a value appear in a type — `Tensor` for a `const N : i32` — and are resolved by monomorphization. ## What is next - [Ownership and borrowing](ownership.md) — the memory model. - [Generics and traits](generics.md) — abstraction without runtime cost. - [Topologies and memory](heterogeneous.md) — the reason Vx exists. --- # Control flow Vx has four ways to branch or repeat: `if`, `loop`, `for`, and `match`. ## if and else ```rust 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: ```rust 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. ```rust 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: ```rust 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; } ``` ### There is no while loop `while` is not a keyword in Vx. Writing `while i < n { ... }` does not produce a "no such loop" message — `while` and `i` both lex as ordinary identifiers, and you get a confusing parse error about a missing `;`. Write the same thing with `loop` and a guard: ```rust fn main() -> i32 { let mut i : i32 = 0; loop { if i >= 4 { break; } i = i + 1; } return i - 4; } ``` Whether `while` should exist is [Vx#506](https://github.com/vx-lang/Vx/issues/506). ### Loop invariants A `loop` can carry an `invariant`: a condition that must hold on every turn. The prover checks it. ```rust fn main() -> i32 { let mut i : i32 = 0; loop invariant(i >= 0) { if i >= 3 { 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](https://github.com/vx-lang/Vx/issues/501). ## for `for` walks a range or anything that implements `Iterator`. ```rust 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. ```rust 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: ```rust 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](ownership.md) — who owns a value, and who may look at it - [Contracts and verification](contracts.md) — `requires`, `ensures`, `assert` - [Generics and traits](generics.md) — 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: ```rust let v = make_buffer(); consume(v); // v is moved // reading v here is an error ``` Borrow instead of moving to keep the original alive: ```rust fn total(v : &Vec) -> i32 { /* ... */ } fn append(v : &mut Vec, 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. ## 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` is a heap allocation with a single owner: ```rust import std::box; struct Node { value: i32, next: Box, } ``` 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 is itself declared `unsafe fn`, so the obligation is visible in the signature rather than buried in the body. ```rust unsafe fn read_first(p : *const i32) -> i32 { return *p; } fn main() -> i32 { let x : i32 = 42; unsafe { return read_first(&x); } } ``` 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 ```rust struct Pair { first: T, second: T, } enum Maybe { Just(T), Nothing, } ``` Instantiate by naming the argument: ```rust fn main() -> i32 { let m = Maybe::Just(42); let mut result = 0; match m { Maybe::Just(val) => { result = val; }, Maybe::Nothing => { result = 0; }, } return result; } ``` ## Generic impls An `impl` block can be generic, or specific to one instantiation: ```rust impl Pair { fn first(self: &Pair) -> T { return self.first; } } impl Pair { fn sum(self: &Pair) -> 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 `+`: ```rust impl Tensor { // ... } ``` ```rust fn describe(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: ```rust fn first>(it : I) -> i64 { /* ... */ } fn total>(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: ```rust 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`: ```rust impl Iterator, T> for VecIter { // ... } ``` 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: ```rust fn dot(a : Tensor, b : Tensor) -> 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. ```rust fn main() -> i32 { comptime { let size : i32 = 4 * 4; } return 0; } ``` Everything inside has to be knowable at compile time. A `comptime` block cannot read a run-time value, call into C, or touch a device. Arithmetic gives the same answers it would at run time. Two integers divide as integers, so `7 / 2` is `3`, and an integer keeps every one of its bits however large it is. A computation that overflows produces no compile-time value at all, rather than a wrapped one: an `assert` about it is then left to run time instead of being decided on a number the program never computes. ## comptime conditions `if comptime` chooses a branch at compile time. The branch not taken is not compiled. ```rust 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: ```rust fn main() -> i32 { comptime { let mut a : Tensor = [ 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: ```rust let a : Tensor = [ 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 — a loop variable, say — 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 is then neither proved nor disproved, and is checked at run time like any other assert. 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`: ```rust fn buffer_size() -> 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` 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: ```rust fn main() -> i32 { let t : Tensor = Tensor(); 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](contracts.md), 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. 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: ```rust 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. ## Where to next - [Generics and traits](generics.md) — the rest of the generic system - [Contracts and verification](contracts.md) — 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. ```rust 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: ```rust 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. ```rust fn main() -> i32 { let mut i : i32 = 0; loop invariant(i >= 0) { if i >= 3 { break; } i = i + 1; } return 0; } ``` Like `requires` and `ensures`, an invariant may be written bare or in parentheses: `invariant i >= 0` and `invariant(i >= 0)` are equivalent. ## 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](building.md) 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. ## assert `assert` is the run-time counterpart. It takes a condition and an optional message: ```rust 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` is a type that carries the fact that a value has been checked. A plain `T` and a `Verified` are different types, so a function that demands a checked value cannot be handed an unchecked one by mistake. ```rust 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](error-index.md); the `E8xxx` group is the prover. ## Where to next - [Control flow](control-flow.md) — where `invariant` attaches - [Ownership and borrowing](ownership.md) — the other thing checked at compile time - [Unsafe and FFI](unsafe-and-ffi.md) — 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. ```rust 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. ```rust 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. ```rust 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. ## 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](heterogeneous.md). ## Where to next - [Ownership and borrowing](ownership.md) — the checks `unsafe` steps around - [Topologies and memory](heterogeneous.md) — the checks it does not - [Standard library reference](stdlib-reference.md) — which `std` functions are `unsafe`, and why --- # Automatic differentiation Vx can differentiate a function you wrote, at compile time. There is no tape, no graph built at run time, and no separate framework — the derivative is generated from the function's own code. Three forms, matching the three things people usually want. ## grad `grad` gives the derivative of a function with respect to its input. ```rust 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. ```rust 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. ```rust 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` | The cost of `vjp` scales with the number of outputs; the cost of `jvp` scales with the number of inputs. That is the whole reason both exist. ## How it works Differentiation is done by [Enzyme](https://enzyme.mit.edu/), 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. Enzyme has to be present when the compiler is built — [Building from source](building.md) covers installing it. ## Limits worth knowing **A function must be differentiable to be differentiated.** A derivative needs both ends continuous, and the compiler checks both at the call: - the **result**. An `i32`, a `bool`, a tensor of integers — these take separated values, so between any two of them there is no limit to take. - the **value it is taken with respect to**, which is the first parameter, for the same reason. ``` Error: Function 'discrete_func' cannot be differentiated because it returns the discrete type i32 ``` A *later* parameter may be discrete. A function of an `f32` that also takes an index or a loop count is an ordinary thing to differentiate, and only the first argument is the one being moved. ## Where to next - [Compile-time evaluation](comptime.md) — the other thing that happens before the program runs - [Topologies and memory](heterogeneous.md) — running the result on an accelerator --- # 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: ```rust 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](machine-files.md), before any binary exists. ## Running code somewhere else `spawn on` runs a block on a named topology: ```rust fn main() -> i32 { let mut host : Tensor = Tensor::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::uninit()` takes no arguments: the shape is already part of the type. Only the dynamic form needs extents passed — `Tensor::uninit([rows, cols])`. The block is outlined into a kernel and handed to the dispatcher for that topology. On Apple Silicon that means CoreML and the neural engine; on an NVIDIA box it means PTX. The source does not change between the two — the machine file does. Every value the block touches must already live in a memory the target topology can address. That is why the `transfer` comes first. Skip it, and the error names the value and the space it is in rather than crashing inside a vendor runtime. The block also calls no helper function. A function declared without a topology belongs to the host, and calling it from inside a device region is a compile error: ``` Error[E6001]: Function 'f' requires topology 'CPU', but is called from 'ANE' ``` That is the address-space rule doing its job. Code meant for a device is written in the region, or in a function declared for that topology. > **Not implemented yet.** `spawn on` is a statement. The design intends it to become an expression > yielding a `Future`, so a host thread could fan work across several accelerators and join later. > There is no future type and no `await` in the language today. ## What gets rejected The point of putting placement in the type system is the errors you get for free. **Dereferencing a device pointer from the host.** A `Pinned` 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](machine-files.md). **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` marks a value whose computation carried its proof obligations all the way through. A function returning `Verified` is asserting that the placement, capacity and visibility conditions on the path that produced it were all discharged, not merely unchecked. ```rust fn custom_matmul(a : Pinned, Topology::NPU[0]>, b : Pinned, Topology::NPU[0]>) -> Verified> { let mut result = Tensor::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` 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: ```bash 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: ```bash 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](machine-files.md) 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 ```rust 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](numa.md). ## 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 ```bash 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: ```bash vxc --machine fleet/h100-sxm.vx --host default program.vx -o program ``` `--host default` means "the machine doing the compiling". A host file declares no `capacity`, on purpose: host memory is virtual, and a tensor larger than physical RAM pages rather than failing. A hard limit there would reject programs that actually run. ## Writing your own Start from the closest file in `fleet/` and change the numbers. The compiler will tell you if the result is incoherent, which makes the edit-check loop fast. Two habits worth keeping: 1. **Cite every figure.** The fleet files carry the source of each number in a comment. When a prediction is wrong, the first question is always whether the model or the measurement is at fault, and a citation answers it in seconds. 1. **Mark what you have not verified.** A number from a datasheet and a number from a benchmark are different kinds of thing, and the difference should survive in the file. --- # NUMA and the host's memory domains Every other memory space in this book belongs to an accelerator you have to attach. A two-socket server has two of them already, and they are the hardest case for the model to describe well — because unlike a GPU's HBM against a host's DRAM, the two memories here are physically identical. Node 0's memory and node 1's memory are the same DDR4, at the same speed, from the same order. Nothing about the bytes differs. The only thing separating them is **which core is asking**. That makes NUMA a good test of whether placement in the type system is describing locality or merely labelling hardware. It also makes it the one heterogeneous memory you can experiment with on a machine you may already own. ## It is the same relation as two GPUs A NUMA domain is a memory that some execution units reach cheaply and others reach across a link. `fleet/node-8gpu.vx` already says that about two GPUs, as `HBM` and `PEER_HBM` with a priced edge between them. A two-socket host is the same shape: ``` Memory HBM { capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 0 } Memory PEER_HBM { capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 1 } Topology Device { arch: x86_64, memory: Memory::HBM, visible: [Memory::HBM, Memory::PEER_HBM, Memory::L2], transfer Memory::CPU_DRAM -> Memory::HBM : 140 GB/s, transfer Memory::HBM -> Memory::L2, transfer Memory::HBM -> Memory::PEER_HBM : 62 GB/s, transfer Memory::PEER_HBM -> Memory::HBM : 62 GB/s } ``` The names are roles rather than materials, as everywhere else in `fleet/` — there is no HBM within a mile of this part, and `HBM` means "this SKU's device memory". What the pair says is that a tile lives in one domain and reaching it from the other crosses a link with a price. `fleet/xeon-8275cl.vx` is this file in full, for the Xeon Platinum 8275CL that AWS sells as `c5.metal`. ## `node:` is the field that is obeyed Every other field on a `Memory` describes the space so the compiler can admit or refuse a placement. `node:` is different: a backend has to **act** on it. ``` Memory PEER_HBM { capacity: 96 GiB, bandwidth: 140 GB/s, managed: explicit, scope: device, node: 1 } ``` The space's name cannot carry this. A declared name reaches the runtime as an FNV hash, by design, and a hash cannot be turned back into a node number — while `mbind` needs exactly that. So a space declaring a node is given a banded dispatch id instead (`arch::NUMA_DISPATCH_BASE + N`, in the 1000..1999 range), and the backend decodes it. Two consequences follow, and both are visible: - `transfer(t, Memory::PEER_HBM)` binds the allocation to node 1, through the `mbind` syscall. No libnuma dependency and no link flag; it compiles out entirely off Linux. - A dispatch whose arguments are placed pins itself to that node before running the kernel. Zero is a valid node and means the first one, so `node: 0` is a declaration rather than an absence. The upper bound is the dispatch band's rather than the hardware's; a node number past it is a parse error rather than an id that would collide with something else. ## What it buys at compile time The compile-time half needs no hardware at all. Declaring the domains separately means a tile too large for one of them is refused, where a model that flattened the box into a single space would admit it: ``` Error[E6009]: transferred tensor needs 42949672960 bytes but memory space 'HBM' has capacity 32212254720 bytes ``` That program was being told something false before. The box really does have 60 GiB; no *node* has 40 GiB, so the tile could not have been local however it was allocated. The hop between domains is priced like any other edge, and `--diagnostics-json` will hand you the per-route figures: ``` CPU_DRAM -> HBM | 8 GiB | 61.4 ms | link_rate HBM -> PEER_HBM | 8 GiB | 138.5 ms | link_rate ``` ## Checking the model against the machine `utils/campaign/run_numa_probe.sh` measures all four (cpu node, memory node) pairs and compares the measured remote/local ratio against the one the compiler derives, reading the prediction out of `--diagnostics-json` so it cannot drift from the machine file. ```bash 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 ```bash 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](machine-files.md) — the full field reference for `Memory` and `Topology` - [Carrying facts across boundaries](correlation.md) — the other six places a fact crosses a boundary rather than being re-derived - [Topologies and memory](heterogeneous.md) — how placement and reachability are checked --- # 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: ```rust import std::vec; fn main() -> i32 { let mut v = Vec::new(); v.push(10); v.push(32); return v.get(0) + v.get(1); } ``` The toolchain also ships a `graph` library outside `std`, imported as `graph::traversal` and friends. > This page is generated from `stdlib/core/*.vx` and `stdlib/std/*.vx` by > `scripts/tools/gen_stdlib_reference.py`. Signatures are exactly what the source declares, and > the prose under each is its `///` comment. An item with no description has none in the source. ## Contents - [`core::clone`](#coreclone) — `Clone`, an explicit duplicate of a value. - [`core::cmp`](#corecmp) — Ordering and equality: `PartialEq`, `Ord`, `PartialOrd` and `Ordering`. - [`core::convert`](#coreconvert) — `From`, the conversions that cannot fail and lose nothing. - [`core::default`](#coredefault) — `Default`, the value a type starts from. - [`core::iter::adapters`](#coreiteradapters) — The iterators `Iterator`'s adaptor methods build: `Map`, `Filter`, `Chain` and the rest. - [`core::iter::traits`](#coreitertraits) — The `Iterator` trait: one required `next`, and the methods written over it. - [`core::iter`](#coreiter) — `Range` and `range`; importing it brings the trait and the adaptors too. - [`core::marker`](#coremarker) — The traits that say something about a type without giving it a method. - [`core::mem`](#coremem) — Moving values around without looking at what they are. - [`core::num`](#corenum) — The integer and float methods, stamped over every width. - [`core::ops`](#coreops) — The callable types a closure literal lowers into. - [`core::option`](#coreoption) — `Option`, for a value that may be absent. - [`core::ptr`](#coreptr) — Raw pointers: making one, and reading or writing through it. - [`core::result`](#coreresult) — `Result`, for an operation that may fail. - [`core::tuple`](#coretuple) — The structs tuple syntax stands for, `Tuple2` to `Tuple6`; imported by any module that writes a tuple. - [`std::alloc`](#stdalloc) — Raw allocation and deallocation. - [`std::box`](#stdbox) — `Box`, a single-owner heap allocation. Required for recursive types. - [`std::fs`](#stdfs) — Files and directories. - [`std::googletest`](#stdgoogletest) — Assertions for tests written in Vx. - [`std::hash_map`](#stdhash_map) — `HashMap`. - [`std::hash_set`](#stdhash_set) — `HashSet`. - [`std::io`](#stdio) — Standard input, output and error. - [`std::libc`](#stdlibc) — Direct bindings to the C library. - [`std::llama`](#stdllama) — Helpers used by the Llama 2 example. - [`std::mmap`](#stdmmap) — Memory-mapped files. - [`std::net`](#stdnet) — TCP and UDP sockets. - [`std::rand`](#stdrand) — Seeded pseudo-random numbers, one stream per `Rng`. - [`std::simd`](#stdsimd) — SIMD vector types and operations. - [`std::string`](#stdstring) — `String` and text manipulation. - [`std::tensor`](#stdtensor) — Operations on `Tensor`, including shape queries and elementwise maths. - [`std::time`](#stdtime) — Clocks and durations. - [`std::vec`](#stdvec) — `Vec`, a growable array. ## `core::clone` `Clone`, an explicit duplicate of a value. **Types** - `trait Clone` **`trait Clone` methods** - `fn clone(self : &Self) -> Self`
A duplicate of this value, made explicitly. The only required method. A type that can copy itself by a plain read still needs one, because a body written over `T : Clone` has to be able to call it. - `fn clone_from(self : &mut Self, source : &Self) -> void`
Replace this value with a duplicate of `source`. A default written over `clone`. Override it for a type that can overwrite itself more cheaply than it can build a fresh copy; nothing in `core` needs to. **`Clone for Option` methods** - `fn clone(self : &Option) -> Option`
The option with its value cloned, if it has one. **`Clone for Result` methods** - `fn clone(self : &Result) -> Result`
The result with whichever side it holds cloned. **`Clone for T` methods**, stamped for 12 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`, `f32`, `f64`, `bool`, `Ordering` ## `core::cmp` Ordering and equality: `PartialEq`, `Ord`, `PartialOrd` and `Ordering`. **Types** - `enum Ordering` - `trait PartialEq` - `trait Ord` - `trait PartialOrd`
A comparison that may answer with nothing, which is what a float needs: NaN is neither less than, equal to, nor greater than anything, itself included. **`Ordering` methods** - `fn is_lt(self : Ordering) -> bool`
Is this Less? - `fn is_gt(self : Ordering) -> bool`
Is this Greater? - `fn is_eq(self : Ordering) -> bool`
Is this Equal? - `fn is_ne(self : Ordering) -> bool`
Is this anything but Equal? - `fn is_le(self : Ordering) -> bool`
Is this Less or Equal? - `fn is_ge(self : Ordering) -> bool`
Is this Greater or Equal? - `fn reverse(self : Ordering) -> Ordering`
Less becomes Greater and back; Equal stays. - `fn then_with(self : Ordering, f : Closure0) -> Ordering`
`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" is Rust's name for the fact that equality need not be reflexive: a NaN is not equal to itself, and `f32` implements this and not `Ord` for that reason. - `fn ne(self : &Self, other : &Self) -> bool`
Are the two values different? The negation of `eq`, and not spelled `!=`, which answers false for a NaN on both sides (Vx#716). **`trait Ord` methods** - `fn cmp(self : &Self, other : &Self) -> Ordering`
Where this value sits relative to the other: Less, Equal or Greater. The only required method. Every other method of this trait is a default written over it, so a type joins the ordering by writing this one and nothing else. The order must be total, which is why the floats do not implement this trait: a NaN compares to nothing, and `PartialOrd` is where they answer. - `fn lt(self : &Self, other : &Self) -> bool`
Is this value less than the other? - `fn le(self : &Self, other : &Self) -> bool`
Is this value less than or equal to the other? - `fn gt(self : &Self, other : &Self) -> bool`
Is this value greater than the other? - `fn ge(self : &Self, other : &Self) -> bool`
Is this value greater than or equal to the other? - `fn max(self : Self, other : Self) -> Self`
The greater of the two, taking both by value and handing one back. A method rather than the free function Rust also has, because the compiler reads the bare names `max` and `min` as the tensor reductions (Vx#223). - `fn min(self : Self, other : Self) -> Self`
The lesser of the two. - `fn clamp(self : Self, lo : Self, hi : Self) -> Self`
This value brought inside the range, so `lo` below it and `hi` above it. # Panics When `lo` is greater than `hi`, which asks for a range no value can be in. **`trait PartialOrd` methods** - `fn partial_cmp(self : &Self, other : &Self) -> Option`
Where this value sits relative to the other, or nothing when they do not compare. Nothing is what a float answers against a NaN: it is neither less than, equal to, nor greater than anything, itself included. Over a total order this always answers `Some`, and the integer widths implement it that way so a body written over `PartialOrd` works for every number. **`PartialOrd for $t` methods** - `fn partial_cmp(self : &$t, other : &$t) -> Option`
Always an answer, since this type is totally ordered. **Functions** - `fn max_by(a : T, b : T, f : Closure2) -> T`
The greater of two values by `f`, and the lesser. They are free functions because they take the comparison rather than reading it off the type. Rust spells them `max_by` and `min_by`; the plain `max` and `min` are `Ord` methods here, since those two names are the compiler's tensor reductions. - `fn min_by(a : T, b : T, f : Closure2) -> T`
The lesser of the two by `f`, answering `a` when they compare equal. **`PartialEq for T` methods**, stamped for 9 instantiations - `fn eq(self : &T, other : &T) -> bool`
Equality at this width. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `bool` **`Ord for T` methods**, stamped for 9 instantiations - `fn cmp(self : &T, other : &T) -> Ordering`
The three-way comparison at this width. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `bool` **`PartialEq for T` methods**, stamped for 9 instantiations - `fn eq(self : &T, other : &T) -> bool`
Equality at this width. A NaN is equal to nothing, itself included. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `bool` **`PartialOrd for T` methods**, stamped for 9 instantiations - `fn partial_cmp(self : &T, other : &T) -> Option`
The comparison, or nothing when either side is a NaN. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `bool` ## `core::convert` `From`, the conversions that cannot fail and lose nothing. **Types** - `trait From` - `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` methods** - `fn from(v : T) -> Self`
This type built from a `T`, losing nothing. A static method, called through the target: `i32::from(x)`. Only implemented where every value of `T` fits, so there is no failure to report; the narrowing direction is `TryFrom`'s, which Vx does not have yet. **`From for Option` methods** - `fn from(v : T) -> Option`
The value wrapped in `Some`. **Functions** - `fn identity(x : T) -> T`
Returns its argument. Useful where a function is wanted and nothing should happen. **`From for U` methods**, stamped for 48 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`, `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`, `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` ## `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 Default for Option` methods** - `fn default() -> Self`
The value this type starts from: zero for a number, `false`, `None`. A static method, so it is called through the type -- `i64::default()`, not `x.default()`. A struct gets one by writing the impl; the language does not derive it. - `fn default() -> Option`
`None`, whatever `T` is: an option's default is the absent one. **`Default for T` methods**, stamped for 11 instantiations - `fn default() -> T`
Zero, spelled at this width. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64`, `bool` ## `core::iter::adapters` The iterators `Iterator`'s adaptor methods build: `Map`, `Filter`, `Chain` and the rest. **Types** - `struct Map`
An iterator over another one's items with `f` applied to each. Built by `map`. - `struct Filter` - `struct Take`
An iterator over at most `left` items of another. Built by `take`. - `struct Skip`
An iterator over another's items with the first `drop` of them discarded. Built by `skip`. - `struct StepBy`
An iterator over every `step`th item of another, starting with its first. Built by `step_by`. - `struct Chain`
An iterator over the items of one iterator, then those of another. Built by `chain`. - `struct TakeWhile`
An iterator over another's items for as long as `keep` accepts them. Built by `take_while`. - `struct SkipWhile`
An iterator over another's items from the first one `skip` refuses. Built by `skip_while`. - `struct MapWhile`
An iterator over what `f` answers for another's items, until it answers nothing. Built by `map_while`. - `struct Inspect`
An iterator that hands each of another's items to `f` on its way past. Built by `inspect`. - `struct Scan`
An iterator over what `f` answers for another's items while it carries a state between them, ending where `f` first answers nothing. Built by `scan`. - `struct Fuse`
An iterator that answers nothing forever once another has answered nothing once. Built by `fuse`. - `struct Peekable`
An iterator whose next item can be looked at without taking it. Built by `peekable`. `T` is always `I::Item`: a struct field cannot name `I::Item`, so the item it holds on to is typed by a parameter of its own, which `peekable` fills in. - `struct FlatMap`
An iterator over the items of the iterators `f` answers for another's items, one after another. Built by `flat_map`. `U` is what `f` answers, held while its items are handed out, and a parameter of its own for the reason `Peekable`'s `T` is. - `struct Flatten`
An iterator over the items of each of another's items, which are iterators themselves. Built by `flatten`. - `struct Rev`
An iterator over another's items from the back. Built by `rev`. - `struct Zip`
An iterator over pairs of two others' items, taken in step, ending when either does. Built by `zip`. - `struct Enumerate`
An iterator over another's items, each paired with how far along it is, counting from zero. Built by `enumerate`. - `struct Cycle`
An iterator over another one's items, again and again. Built by `cycle`. `orig` is kept untouched. Each pass runs over a clone of it held in `cur`, made when the pass starts. The clone is taken here rather than in `cycle`, because a default method of `Iterator` is checked for every iterator type, and most of them are not `Clone`; this impl is only checked for the types that are actually cycled. **`Iterator for Map>` methods** - `fn next(self : &mut Map>) -> Option`
The inner iterator's next item with `f` applied. **`DoubleEndedIterator for Map>` methods** - `fn next_back(self : &mut Map>) -> Option`
The inner iterator's last item with `f` applied. **`ExactSizeIterator for Map>` methods** - `fn len(self : &Map>) -> i64`
As many as the inner iterator has. **`Iterator for Filter>` methods** - `fn next(self : &mut Filter>) -> Option`
The inner iterator's next item that `keep` accepts. **`DoubleEndedIterator for Filter>` methods** - `fn next_back(self : &mut Filter>) -> Option`
The inner iterator's last item that `keep` accepts. **`Iterator for Take` methods** - `fn next(self : &mut Take) -> Option`
The inner iterator's next item, until `left` of them have been handed out. **`ExactSizeIterator for Take` methods** - `fn len(self : &Take) -> i64`
The inner iterator's count, up to `left`. **`DoubleEndedIterator for Take` methods** - `fn next_back(self : &mut Take) -> Option`
The last of the items `next` would hand out, passing over the inner iterator's items beyond them. **`Iterator for Skip` methods** - `fn next(self : &mut Skip) -> Option`
The inner iterator's next item, once the skipped ones have been consumed. **`ExactSizeIterator for Skip` methods** - `fn len(self : &Skip) -> i64`
The inner iterator's count less the ones still to be skipped. **`DoubleEndedIterator for Skip` methods** - `fn next_back(self : &mut Skip) -> Option`
The inner iterator's last item, while it is not one of the skipped ones. **`Iterator for StepBy` methods** - `fn next(self : &mut StepBy) -> Option`
The inner iterator's first item, and after that the one `step` places further on. **`ExactSizeIterator for StepBy` methods** - `fn len(self : &StepBy) -> i64`
How many of the inner iterator's items land on a step. **`DoubleEndedIterator for StepBy` methods** - `fn next_back(self : &mut StepBy) -> Option`
The last item that lands on a step, passing over the inner iterator's items after it. **`Iterator for Chain` methods** - `fn next(self : &mut Chain) -> Option`
The first iterator's next item, or the second's once the first is finished. **`DoubleEndedIterator for Chain` methods** - `fn next_back(self : &mut Chain) -> Option
`
The second iterator's last item, or the first's once the second is finished. **`Iterator for TakeWhile>` methods** - `fn next(self : &mut TakeWhile>) -> Option`
The inner iterator's next item, until the first `keep` refuses; nothing from then on. **`Iterator for SkipWhile>` methods** - `fn next(self : &mut SkipWhile>) -> Option`
The inner iterator's next item, once the leading ones `skip` accepts are consumed. **`Iterator for MapWhile>>` methods** - `fn next(self : &mut MapWhile>>) -> Option`
`f` of the inner iterator's next item, which is nothing once `f` says so. **`Iterator for Inspect>` methods** - `fn next(self : &mut Inspect>) -> Option`
The inner iterator's next item, after `f` has seen it. **`DoubleEndedIterator for Inspect>` methods** - `fn next_back(self : &mut Inspect>) -> Option`
The inner iterator's last item, after `f` has seen it. **`ExactSizeIterator for Inspect>` methods** - `fn len(self : &Inspect>) -> i64`
As many as the inner iterator has. **`Iterator for Scan>>` methods** - `fn next(self : &mut Scan>>) -> Option`
`f` of the state and the inner iterator's next item. **`Iterator for Fuse` methods** - `fn next(self : &mut Fuse) -> Option`
The inner iterator's next item, or nothing for good once it has run out. **`ExactSizeIterator for Fuse` methods** - `fn len(self : &Fuse) -> i64`
As many as the inner iterator has, and none once it has run out. **`Iterator for Peekable` methods** - `fn next(self : &mut Peekable) -> Option`
The item `peek` looked at, if it looked, and otherwise the inner iterator's next. **`Peekable` methods** - `fn peek(self : &mut Peekable) -> Option`
The item `next` would answer, left where it is. Answers a copy where Rust answers a reference into the iterator. - `fn next_if(self : &mut Peekable, accept : Closure1<&I : : Item, bool>) -> Option`
The next item if `accept` takes it, and otherwise nothing, with the item left in place. **`ExactSizeIterator for Peekable` methods** - `fn len(self : &Peekable) -> i64`
As many as the inner iterator has, and the one `peek` holds. **`Iterator for FlatMap, U>` methods** - `fn next(self : &mut FlatMap, U>) -> Option`
The current inner iterator's next item, moving to the next one when it runs out. **`Iterator for Flatten` methods** - `fn next(self : &mut Flatten) -> Option`
The current inner iterator's next item, moving to the next one when it runs out. **`Iterator for Rev` methods** - `fn next(self : &mut Rev) -> Option`
The inner iterator's last item. **`DoubleEndedIterator for Rev` methods** - `fn next_back(self : &mut Rev) -> Option`
The inner iterator's first item. **`ExactSizeIterator for Rev` methods** - `fn len(self : &Rev) -> i64`
As many as the inner iterator has. **`Iterator for Zip` methods** - `fn next(self : &mut Zip) -> Option<(A : : Item, B : : Item)>`
The next item of each, paired, or nothing once either has run out. **`ExactSizeIterator for Zip` methods** - `fn len(self : &Zip) -> i64`
As many as the shorter of the two has. **`DoubleEndedIterator for Zip` methods** - `fn next_back(self : &mut Zip) -> Option<(A : : Item, B : : Item)>`
The last pair, once the longer iterator's extra items at the back have been dropped. **`Iterator for Enumerate` methods** - `fn next(self : &mut Enumerate) -> Option<(i64, I : : Item)>`
The inner iterator's next item with its position. **`ExactSizeIterator for Enumerate` methods** - `fn len(self : &Enumerate) -> i64`
As many as the inner iterator has. **`DoubleEndedIterator for Enumerate` methods** - `fn next_back(self : &mut Enumerate) -> Option<(i64, I : : Item)>`
The inner iterator's last item with its position, which is how many come before it. **`Iterator for Cycle` methods** - `fn next(self : &mut Cycle) -> Option`
The next item of the current pass, or the first of a new pass once it has run out. Nothing, forever, when the original iterator has no items at all. ## `core::iter::traits` The `Iterator` trait: one required `next`, and the methods written over it. **Types** - `trait Iterator` - `trait DoubleEndedIterator`
An iterator that can also answer from its far end, which is what `rev` needs. Rust declares it as a subtrait of `Iterator`; Vx has no subtraits, and `Self::Item` here is the one the type's `Iterator` impl binds. - `trait ExactSizeIterator`
An iterator that knows how many items it has left, which is what `rev` needs from `take`, `skip` and `step_by`. Rust declares it as a subtrait of `Iterator`; Vx has no subtraits. - `trait FromIterator
`
A collection that can be built from an iterator's items, which is what `collect` builds. - `trait Extend
`
A collection that grows by an iterator's items, which is what `partition` and `unzip` fill. - `trait Sum
`
A type whose values can be added up from an iterator's items, which is what `sum` does. - `trait Product
`
A type whose values can be multiplied together from an iterator's items, which is what `product` does. **`trait Iterator` methods** - `fn next(self : &mut Self) -> Option`
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`
The final item, consuming the sequence. Nothing when it is already finished. - `fn nth(self : &mut Self, n : i64) -> Option`
The item `n` places along, counting the next one as zero, discarding those before it. Nothing when the sequence finishes first. The items skipped are consumed either way. - `fn any(self : &mut Self, f : Closure1) -> bool`
Does `f` accept any item? Stops at the first it does, leaving the rest unconsumed. - `fn all(self : &mut Self, f : Closure1) -> bool`
Does `f` accept every item? Stops at the first it does not. True for a sequence that is already finished, which is the usual convention: there is no item to disagree. - `fn find(self : &mut Self, f : Closure1) -> Option`
The first item `f` accepts, or nothing. Stops there, so the rest is unconsumed. - `fn position(self : &mut Self, f : Closure1) -> Option`
How far along the first item `f` accepts is, counting the next one as zero. - `fn for_each(self : &mut Self, f : Closure1) -> i32`
Hand every item to `f`, consuming the sequence. `f` answers an `i32` rather than nothing, and this returns the last of them, because no closure literal can return void yet (Vx#711). Both signatures become Rust's when it can. - `fn fold(self : &mut Self, init : B, f : Closure2) -> B`
`f` over every item, carrying a value from one to the next: `init` goes in with the first item, and what `f` answers goes in with the next. The last answer is the result, or `init` for a sequence that is already finished. - `fn try_fold(self : &mut Self, init : Acc, f : Closure2) -> R`
`fold` that can stop early. `f` answers an `Option` or a `Result`: `Some` or `Ok` carries its value on to the next item, and the first `None` or `Err` is returned at once, leaving the rest of the sequence unconsumed. When every item carries on, the answer is the last value wrapped the same way, or `init` wrapped for a sequence that is already finished. - `fn try_for_each(self : &mut Self, f : Closure1) -> R`
`for_each` that can stop early: the first `None` or `Err` that `f` answers is returned at once, and the rest of the sequence is left unconsumed. Otherwise the answer is `Some(0)` or `Ok(0)`. `f` answers an `Option` or `Result` whose value is discarded, where Rust's answers `()`, because no closure literal can return void yet. It becomes Rust's when it can. - `fn sum(self : &mut Self) -> Self : : Item`
Every item added up, consuming the sequence. Zero for one that is already finished. The item type says how, by implementing `Sum`. Rust also lets the caller choose a result type other than the item's; here the two are the same. - `fn product(self : &mut Self) -> Self : : Item`
Every item multiplied together, consuming the sequence. One for one already finished. The item type says how, by implementing `Product`. - `fn max(self : &mut Self) -> Option`
The greatest item, or nothing for a sequence already finished. Of several equal greatest items, the last. - `fn min(self : &mut Self) -> Option`
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`
The greatest item as `compare` orders them, `compare(a, b)` saying where `a` sits against `b`. Of several equal greatest items, the last. - `fn min_by(self : &mut Self, compare : Closure2<&Self : : Item, &Self : : Item, Ordering>) -> Option`
The least item as `compare` orders them. Of several equal least items, the first. - `fn max_by_key(self : &mut Self, key : Closure1<&Self : : Item, B>) -> Option`
The item whose `key` is greatest. Of several with equal greatest keys, the last. - `fn min_by_key(self : &mut Self, key : Closure1<&Self : : Item, B>) -> Option`
The item whose `key` is least. Of several with equal least keys, the first. - `fn map(self : Self, f : Closure1) -> Map>`
An iterator over these items with `f` applied to each. - `fn filter(self : Self, keep : Closure1<&Self : : Item, bool>) -> Filter>`
An iterator over the items `keep` accepts. - `fn take(self : Self, n : i64) -> Take`
An iterator over at most the first `n` items. - `fn skip(self : Self, n : i64) -> Skip`
An iterator over the items after the first `n`. - `fn step_by(self : Self, step : i64) -> StepBy`
An iterator over the first item and every `step`th one after it. # Panics When `step` is not positive: a step of zero would hand out the first item forever. - `fn chain(self : Self, other : B) -> Chain`
An iterator over these items and then `other`'s, which must be of the same type. - `fn take_while(self : Self, keep : Closure1<&Self : : Item, bool>) -> TakeWhile>`
An iterator over the leading items `keep` accepts, stopping at the first it refuses. - `fn skip_while(self : Self, skip : Closure1<&Self : : Item, bool>) -> SkipWhile>`
An iterator over the items from the first one `skip` refuses onward. - `fn map_while(self : Self, f : Closure1>) -> MapWhile>>`
An iterator over what `f` answers for each item, ending where `f` first answers nothing. - `fn inspect(self : Self, f : Closure1<&Self : : Item, i32>) -> Inspect>`
An iterator over these items that hands each to `f` on its way past. `f` answers an `i32`, which is discarded, because no closure literal can return void yet. - `fn scan(self : Self, initial : St, f : Closure2<&mut St, Self : : Item, Option>) -> Scan>>`
An iterator over what `f` answers for each item, with `f` given `initial` to keep and change from one item to the next. Ends where `f` first answers nothing. - `fn cycle(self : Self) -> Cycle`
An iterator over these items, repeated forever. Each pass restarts from a clone of this iterator as it was when `cycle` was called, so the type must implement `Clone`. An iterator with no items gives one with none. - `fn fuse(self : Self) -> Fuse`
An iterator that answers nothing forever once these items have run out. - `fn peekable(self : Self) -> Peekable`
An iterator whose next item can be looked at, by `peek`, without taking it. - `fn flat_map(self : Self, f : Closure1) -> FlatMap, U>`
An iterator over the items of each iterator `f` answers, in order. `f` answers an iterator, where Rust accepts anything that can become one. - `fn flatten(self : Self) -> Flatten`
An iterator over the items of each of these items, which are iterators themselves. - `fn rev(self : Self) -> Rev`
An iterator over these items from the back, for an iterator that can answer from both ends. - `fn zip(self : Self, other : U) -> Zip`
An iterator over pairs of these items and `other`'s, in step, as long as both last. - `fn enumerate(self : Self) -> Enumerate`
An iterator over these items, each paired with its position, counting from zero. The position is an `i64`, where Rust's is a `usize`. - `fn reduce(self : &mut Self, f : Closure2) -> Option`
`fold` with the first item as the starting value, so nothing for a sequence already finished. - `fn collect(self : &mut Self) -> B`
Every item, gathered into a new collection of whatever type the result is assigned to: `let v : Vec = it.collect();`. That type says how, by implementing `FromIterator`. - `fn partition(self : &mut Self, f : Closure1<&Self : : Item, bool>) -> (B, B)`
Every item, split in two by `f`: the ones it accepts first, the rest second. Each half is a collection of whatever type the result is assigned to, which starts from its `Default` and grows through `Extend`. - `fn unzip(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(self : &mut Self, other : Other) -> Ordering`
These items against `other`'s, in step, by their `Ord`: the first pair that differs decides. When one sequence runs out first and every pair so far was equal, the shorter one is Less, as for words in a dictionary. - `fn partial_cmp(self : &mut Self, other : Other) -> Option`
`cmp` by the items' `PartialOrd`, so nothing as soon as a pair does not compare, which is what a NaN does. - `fn eq(self : &mut Self, other : Other) -> bool`
Are these items equal to `other`'s, one for one and as many of them? - `fn ne(self : &mut Self, other : Other) -> bool`
Do these items differ from `other`'s anywhere, or in how many there are? - `fn lt(self : &mut Self, other : Other) -> bool`
Are these items less than `other`'s, in the order `partial_cmp` gives? False when some pair does not compare. - `fn le(self : &mut Self, other : Other) -> bool`
Less than or equal, by `partial_cmp`. False when some pair does not compare. - `fn gt(self : &mut Self, other : Other) -> bool`
Greater than, by `partial_cmp`. False when some pair does not compare. - `fn ge(self : &mut Self, other : Other) -> bool`
Greater than or equal, by `partial_cmp`. False when some pair does not compare. **`binds. trait DoubleEndedIterator` methods** - `fn next_back(self : &mut Self) -> Option`
The last item not yet handed out from either end, or nothing once they meet. - `fn rfold(self : &mut Self, init : B, f : Closure2) -> B`
`fold`, from the back. - `fn rfind(self : &mut Self, f : Closure1<&Self : : Item, bool>) -> Option`
The last item `f` accepts, searching from the back. - `fn nth_back(self : &mut Self, n : i64) -> Option`
The item `n` places from the back, counting the last as zero. **`trait ExactSizeIterator` methods** - `fn len(self : &Self) -> i64`
How many items are left. - `fn is_empty(self : &Self) -> bool`
Whether no items are left. **`trait FromIterator
` methods** - `fn from_iter(iter : I) -> Self`
A new collection holding every item `iter` has left, in order. **`trait Extend
` methods** - `fn extend(self : &mut Self, iter : I) -> i32`
Add every item `iter` has left, in order. - `fn extend_one(self : &mut Self, item : A) -> i32`
Add one item. **`trait Sum
` methods** - `fn sum(iter : I) -> Self`
Every item `iter` has left, added up. Zero when it has none. **`trait Product
` methods** - `fn product(iter : I) -> Self`
Every item `iter` has left, multiplied together. One when it has none. **`Sum for T` methods**, stamped for 10 instantiations - `fn sum(iter : I) -> T`
Every item added up, from zero. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64` **`Product for T` methods**, stamped for 10 instantiations - `fn product(iter : I) -> T`
Every item multiplied together, from one. T = `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64` ## `core::iter` `Range` and `range`; importing it brings the trait and the adaptors too. **Types** - `struct Range`
The numbers from `at` up to but not including `end`. A half-open span of `i64`, from `at` up to but not including `end`. **`Iterator for Range` methods** - `fn next(self : &mut Range) -> Option`
The next value in the span, or nothing once `end` is reached. **`DoubleEndedIterator for Range` methods** - `fn next_back(self : &mut Range) -> Option`
The last value in the span not yet handed out from either end. **`ExactSizeIterator for Range` methods** - `fn len(self : &Range) -> i64`
How many values are left in the span. **`Clone for Range` methods** - `fn clone(self : &Range) -> Range`
A second span over the same values, which is what `cycle` restarts from. **Functions** - `fn range(at : i64, end : i64) -> Range`
The numbers `at .. end`. ## `core::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, as Rust's is: a type is copyable only when it says so, and only when every field already is. That is what keeps it away from placement -- a tensor or a placed buffer cannot declare it, so they stay linear without a special case, and duplicating placed data stays an explicit `transfer`. Enforced for a struct and for a payload-free enum. A generic enum is not treated as linear at all, so `Option` and `Result` survive a move whether or not they declare this (Vx#715). - `trait Send`
A type that may be moved to another thread. Declared, not enforced: there are no threads to send a value between yet. - `trait Sync`
A type that may be referenced from several threads at once. Declared, not enforced, for the same reason as `Send`. - `trait Sized`
A type whose size is known at compile time, which every Vx type is. The bound exists to be written, as Rust's does; nothing is excluded by it. - `struct PhantomData`
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. **Functions** - `fn size_of() -> i64`
The size of `T` in bytes. - `fn swap(a : &mut T, b : &mut T) -> void`
Each value ends up where the other one was. - `fn replace(dest : &mut T, src : T) -> T`
`src` goes in, and what was there comes back. - `fn drop(_x : T) -> void`
Consumes the value. With no `Drop` in the language this frees nothing; it says that the caller is finished with it, and the move checker holds them to it. - `fn forget(_x : T) -> void`
Consumes the value without running anything. The same as `drop` until `Drop` exists. - `fn needs_drop() -> bool`
Whether dropping a `T` does any work. False for every type, since nothing has a destructor yet. ## `core::num` The integer and float methods, stamped over every width. **`$t` methods** - `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 ilog2(self : $t) -> $t`
Rounded down. Refused at zero, which has no logarithm. - `fn next_power_of_two(self : $t) -> $t`
One for anything at or below one. Refused above the top power of two, which is the half of the range that has no next power to reach. - `fn rotate_left(self : $t, n : $t) -> $t`
Wrapping round. No mask, unlike the signed rotate: `>>` brings in zeros here. - `fn rotate_right(self : $t, n : $t) -> $t`
The bits rotated right. - `fn swap_bytes(self : $t) -> $t`
The bytes reversed. - `fn reverse_bits(self : $t) -> $t`
The bits reversed. - `fn checked_add(self : $t, rhs : $t) -> Option<$t>`
Nothing if it would not fit. The bound is rearranged so the check cannot overflow. - `fn checked_sub(self : $t, rhs : $t) -> Option<$t>`
Nothing if it would go below zero, which is where an unsigned width ends. - `fn checked_mul(self : $t, rhs : $t) -> Option<$t>`
Checked by dividing back out, exact when the product fit. - `fn checked_div(self : $t, rhs : $t) -> Option<$t>`
Nothing on division by zero, which is the only division that fails here. - `fn checked_rem(self : $t, rhs : $t) -> Option<$t>`
Refused on a zero divisor, as `checked_div` is. - `fn saturating_add(self : $t, rhs : $t) -> $t`
Held at the top of the range instead of wrapping past it. - `fn saturating_sub(self : $t, rhs : $t) -> $t`
Held at zero instead of wrapping below it. - `fn wrapping_add(self : $t, rhs : $t) -> $t`
The sum, wrapping round at the width. Vx's `+` already wraps. - `fn wrapping_sub(self : $t, rhs : $t) -> $t`
The difference, wrapping round at the width, so subtracting past zero lands near the top. - `fn wrapping_mul(self : $t, rhs : $t) -> $t`
The product, keeping the low bits and discarding the rest. - `fn wrapping_neg(self : $t) -> $t`
Zero minus this, wrapping. - `fn saturating_mul(self : $t, rhs : $t) -> $t`
Clamped to the top of the width rather than wrapping. There is no other end to clamp to without a sign. - `fn leading_ones(self : $t) -> $t`
How many set bits the value starts with, counting from the top. - `fn trailing_ones(self : $t) -> $t`
How many set bits the value ends with, counting from the bottom. - `fn sqrt(self : $t) -> $t`
The positive square root. - `fn abs(self : $t) -> $t`
The distance from zero, so the sign is dropped. - `fn exp(self : $t) -> $t`
e raised to this. - `fn exp2(self : $t) -> $t`
Two raised to this. - `fn exp_m1(self : $t) -> $t`
`exp` minus one, kept accurate for a small argument where the subtraction would lose every significant digit. - `fn ln(self : $t) -> $t`
The natural logarithm. - `fn log2(self : $t) -> $t`
The logarithm to base two. - `fn log10(self : $t) -> $t`
The logarithm to base ten. - `fn ln_1p(self : $t) -> $t`
`ln` of one plus this, kept accurate for a small argument. - `fn sin(self : $t) -> $t`
The sine of this many radians. - `fn cos(self : $t) -> $t`
The cosine of this many radians. - `fn tan(self : $t) -> $t`
The tangent of this many radians. - `fn asin(self : $t) -> $t`
The angle in radians whose sine is this, between -pi/2 and pi/2. - `fn acos(self : $t) -> $t`
The angle in radians whose cosine is this, between 0 and pi. - `fn atan(self : $t) -> $t`
The angle in radians whose tangent is this. `atan2` is the form that keeps the quadrant. - `fn sinh(self : $t) -> $t`
The hyperbolic sine. - `fn cosh(self : $t) -> $t`
The hyperbolic cosine. - `fn tanh(self : $t) -> $t`
The hyperbolic tangent. - `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 powf(self : $t, n : $t) -> $t`
This raised to `n`. - `fn atan2(self : $t, x : $t) -> $t`
The angle to the point (`x`, this), which is `atan` with the quadrant kept. - `fn copysign(self : $t, sign : $t) -> $t`
This value's magnitude with `sign`'s sign. - `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. Zero answers 1 rather than 0, which is Rust's rule and not `signum`'s in every language. - `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? **`T` methods**, stamped for 4 instantiations - `fn min_value(self : T) -> T`
The smallest value of this width. - `fn max_value(self : T) -> T`
The largest value of this width. - `fn bits(self : T) -> T`
How many bits this width has. - `fn count_ones(self : T) -> T`
How many bits are set. The hardware instruction, so a negative operand is counted right; a loop shifting right would copy the sign bit forever. - `fn count_zeros(self : T) -> T`
How many bits are clear. - `fn leading_zeros(self : T) -> T`
Zero bits above the highest set bit. All of them, for zero. - `fn trailing_zeros(self : T) -> T`
Zero bits below the lowest set bit. All of them, for zero. - `fn is_power_of_two(self : T) -> bool`
Zero and the negatives are not. - `fn is_positive(self : T) -> bool`
Is this greater than zero? Zero is neither positive nor negative. - `fn is_negative(self : T) -> bool`
Is this less than zero? - `fn abs(self : T) -> T`
Refused at the smallest value, which has no positive counterpart. - `fn signum(self : T) -> T`
-1, 0 or 1, by sign. - `fn abs_diff(self : T, other : T) -> T`
The distance between two values. - `fn pow(self : T, exp : T) -> T`
By squaring. Overflow wraps, as every arithmetic operator here does. - `fn rem_euclid(self : T, rhs : T) -> T`
Never negative, whatever the signs: -7 % 4 is -3 where this is 1. - `fn div_euclid(self : T, rhs : T) -> T`
The quotient pairing with `rem_euclid`. - `fn ilog2(self : T) -> T`
Rounded down. Refused at zero and below. - `fn next_power_of_two(self : T) -> T`
One for anything at or below one. The top power of two does not fit in a signed width, so a value beyond it is refused. - `fn rotate_left(self : T, n : T) -> T`
Wrapping round. The right half is masked because `>>` copies the sign bit. - `fn rotate_right(self : T, n : T) -> T`
The bits rotated right. - `fn swap_bytes(self : T) -> T`
The bytes reversed. The mask is a parameter because 255 does not fit in an `i8`; it is accepted there and wraps to -1, which is right by luck. - `fn reverse_bits(self : T) -> T`
The bits reversed. - `fn checked_add(self : T, rhs : T) -> Option`
Nothing if it would not fit. The bound is rearranged so the check itself cannot overflow. - `fn checked_sub(self : T, rhs : T) -> Option`
Nothing if it would not fit. - `fn checked_mul(self : T, rhs : T) -> Option`
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`
Nothing on division by zero, or on the one division that overflows. - `fn checked_rem(self : T, rhs : T) -> Option`
Refused in the same two cases as `checked_div`. - `fn checked_neg(self : T) -> Option`
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. T = `i8`, `i16`, `i32`, `i64` ## `core::ops` The callable types a closure literal lowers into. **Types** - `struct Closure0` - `struct Closure1` - `struct Closure2` - `struct Closure3` - `trait Try`
A value that either carries on, holding an output, or stops early. It is what `Iterator::try_fold` reads from its closure's answers, and `Option` and `Result` implement it: `Some` and `Ok` carry on, `None` and `Err` stop. Rust's `Try` cut down to what those two need, with no residual type and no `?`. **`trait Try` methods** - `fn is_continue(self : &Self) -> bool`
Does this carry on? False means stop here and hand this value back. - `fn into_output(self : Self) -> Self : : Output`
The output of one that carries on. - `fn from_output(output : Self : : Output) -> Self`
One that carries on, holding `output`. ## `core::option` `Option`, for a value that may be absent. **Types** - `enum Option` **`Try for Option` methods** - `fn is_continue(self : &Option) -> bool`
Is there a value? - `fn into_output(self : Option) -> T`
The value. - `fn from_output(output : T) -> Option`
`Some(output)`. **`Option` methods** - `fn is_some(self : &Option) -> Bool`
Is there a value? - `fn is_none(self : &Option) -> Bool`
Is there no value? - `fn unwrap(self : Option) -> T`
The value, or a stop. Reach for `unwrap_or` where there is a sensible answer for the absent case; this one ends the program. - `fn unwrap_or(self : Option, default : T) -> T`
The value, or the given one. `default` is evaluated by the caller either way, so keep it cheap; Rust's `unwrap_or_else` is the form that does not, and it takes a closure whose type parameter this cannot yet spell. - `fn or(self : Option, other : Option) -> Option`
This one if it holds a value, otherwise the other. Both sides are the same type, which is why this fits while `and_then` does not. - `fn and(self : Option, other : Option) -> Option`
The other one if this holds a value, otherwise nothing. - `fn xor(self : Option, other : Option) -> Option`
Whichever one holds a value, and nothing when both do or neither does. - `fn map(self : Option, f : Closure1) -> Option`
The value with `f` applied, if there is one. - `fn and_then(self : Option, f : Closure1>) -> Option`
`map` for an `f` that answers with an `Option` of its own, without the nesting. - `fn zip(self : Option, other : Option) -> Option<(T, U)>`
Both values as a pair when both are there, otherwise nothing. - `fn filter(self : Option, p : Closure1) -> Option`
The value if it is there and `p` accepts it, otherwise nothing. - `fn map_or(self : Option, default : U, f : Closure1) -> U`
`f` applied to the value, or the given answer when there is none. Both are the same type, which is what separates this from `map`. - `fn unwrap_or_else(self : Option, f : Closure0) -> T`
The value, or the answer `f` gives. Unlike `unwrap_or`, nothing is computed when there is a value. - `fn is_some_and(self : Option, p : Closure1) -> bool`
Is there a value, and does `p` accept it? - `fn is_none_or(self : Option, p : Closure1) -> bool`
Is there no value, or does `p` accept the one there is? The mirror of `is_some_and`. - `fn or_else(self : Option, f : Closure0>) -> Option`
This one if it holds a value, otherwise what `f` gives. `or` is the form that evaluates the other side either way. - `fn map_or_else(self : Option, d : Closure0, f : Closure1) -> U`
`f` applied to the value, or what `d` gives when there is none. `map_or` is the form that takes the fallback as a value. - `fn take(self : &mut Option) -> Option`
The value, leaving nothing behind. - `fn replace(self : &mut Option, v : T) -> Option`
The value, leaving `v` behind. ## `core::ptr` Raw pointers: making one, and reading or writing through it. **Functions** - `fn null() -> *const T`
A pointer to nothing. - `fn null_mut() -> *mut T`
A mutable pointer to nothing. - `unsafe fn read(p : *const T) -> T`
The value the pointer addresses. The caller promises there is one. - `unsafe fn write(p : *mut T, v : T) -> void`
Puts a value where the pointer addresses. The caller promises it may. ## `core::result` `Result`, for an operation that may fail. **Types** - `enum Result` **`Try for Result` methods** - `fn is_continue(self : &Result) -> bool`
Is this `Ok`? - `fn into_output(self : Result) -> T`
The `Ok` value. - `fn from_output(output : T) -> Result`
`Ok(output)`. **`Result` methods** - `fn is_ok(self : &Result) -> bool`
Did it succeed? - `fn is_err(self : &Result) -> bool`
Did it fail? The negation of `is_ok`. - `fn unwrap(self : Result) -> T`
The value, or a stop. `unwrap_or` is the form with an answer for the failing case. - `fn unwrap_or(self : Result, default : T) -> T`
The value, or the given one on failure. `default` is evaluated by the caller either way, so keep it cheap; `unwrap_or_else` is the form that computes nothing when there is a value. - `fn ok(self : Result) -> Option`
The success dropped, leaving what there is of one. - `fn err(self : Result) -> Option`
The failure as an `Option`, the mirror of `ok`. - `fn map(self : Result, f : Closure1) -> Result`
`f` over the success, the failure untouched. - `fn map_err(self : Result, f : Closure1) -> Result`
`f` over the failure, the success untouched. - `fn and_then(self : Result, f : Closure1>) -> Result`
`map` for an `f` that may itself fail, without the nesting. - `fn unwrap_or_else(self : Result, f : Closure1) -> T`
The value, or what `f` makes of the failure. - `fn unwrap_err(self : Result) -> E`
The failure, or a stop. The mirror of `unwrap`. - `fn is_ok_and(self : Result, p : Closure1) -> bool`
Did it succeed, and does `p` accept the value? - `fn is_err_and(self : Result, p : Closure1) -> bool`
Did it fail, and does `p` accept the failure? - `fn and(self : Result, other : Result) -> Result`
The other one if this succeeded, otherwise this failure. The success types differ, which is why this takes a type parameter where `Option::and` does not. Bound first and returned once: a `match` whose arms each return an enum is declined by the flat path, and `other` is a starting value that needs no default of its own. - `fn or(self : Result, other : Result) -> Result`
This success if there is one, otherwise the other result. The failure types differ. - `fn map_or(self : Result, default : U, f : Closure1) -> U`
`f` applied to the value, or the given answer when there is none. - `fn map_or_else(self : Result, d : Closure1, f : Closure1) -> U`
`f` over the value, or `d` over the failure. Both answer with the same type. **`Option` methods** - `fn ok_or(self : Option, err : E) -> Result`
The value as a success, or the given failure. - `fn ok_or_else(self : Option, f : Closure0) -> Result`
The value as a success, or the failure `f` gives. Nothing is computed when there is a value. ## `core::tuple` The structs tuple syntax stands for, `Tuple2` to `Tuple6`; imported by any module that writes a tuple. **Types** - `struct Tuple2`
A pair. - `struct Tuple3`
Three values. - `struct Tuple4`
Four values. - `struct Tuple5`
Five values. - `struct Tuple6`
Six values. ## `std::alloc` Raw allocation and deallocation. **Functions** *(bound directly to C)* - `fn malloc(size : i64) -> *mut i8`
C's `malloc`: `size` bytes of uninitialised heap, or null when it cannot. Vx cannot test the result against null (Vx#714), so a failed allocation is found by writing through it. - `fn realloc(ptr : *mut i8, size : i64) -> *mut i8`
C's `realloc`: the block resized, moving it if need be. The old pointer is invalid afterwards whether or not it moved. - `fn free(ptr : *mut i8) -> i32`
C's `free`. Freeing twice, or freeing what `malloc` did not return, is undefined. ## `std::box` `Box`, a single-owner heap allocation. Required for recursive types. **Types** - `struct Box`
A single-owner heap allocation, which is what makes a recursive type possible. Released by calling `free`, since the language has no `Drop` (Vx#495). **`Box` methods** - `fn new(val : T) -> Box`
Move a value to the heap. The allocation is not checked: `malloc` answering null gives a `Box` that writes through a null pointer, and Vx cannot test one (Vx#714). - `fn free(self : &mut Box) -> i32`
Release the allocation. The pointer is left as it was, so using the box afterwards reads freed memory. ## `std::fs` Files and directories. **Types** - `struct File`
An open file, held by the Rust core behind an opaque pointer. Closed by calling `file_drop`, since the language has no `Drop` (Vx#495). **`File` methods** - `unsafe fn open(path : *const i8, mode : i32) -> File`
Open `path`. `mode` is 0 to read, 1 to write, and anything else to read and write. Writing creates the file and truncates it; read-and-write creates it and does not truncate. Reading does not create it. **A failed open cannot be detected.** The returned `File` holds a null pointer, and Vx cannot compare a raw pointer against null or cast one to an integer (Vx#714) -- so a missing file is indistinguishable from an empty one until that closes. Every later call on it answers 0 or -1 rather than doing anything. Unsafe because nothing checks that `path` points at a NUL-terminated string. - `unsafe fn read(self : *mut File, buffer : *mut u8, len : i64) -> i64`
Read up to `len` bytes into `buffer` and answer how many arrived. Zero means end of file, a null file or buffer, or a read error -- the four are not told apart. A short read is normal and is not an error. Unsafe because `buffer` must have room for `len` bytes; nothing here checks. - `unsafe fn write(self : *mut File, buffer : *const u8, len : i64) -> i64`
Write up to `len` bytes from `buffer` and answer how many were taken. Zero means a null file or buffer, or a write error, with the same lack of distinction `read` has. A short write is not an error and the remainder is not retried here. - `fn seek(self : *mut File, offset : i64, whence : i32) -> i64`
Move the read and write position, answering where it ended up. `whence` is 0 from the start, 1 from the current position, 2 from the end, as libc's `SEEK_SET`, `SEEK_CUR` and `SEEK_END`. Answers -1 for a null file, an unknown `whence`, or a seek the operating system refuses. **Functions** - `unsafe fn file_drop(file : *mut File) -> void`
Close the file and release it. Using it afterwards reads freed memory. **C bindings** *(the native functions this module is built on)* - `fn vx_file_open(c_path : *const i8, mode : i32) -> *mut i8`
The Rust core behind `File::open`. Null when the open fails. - `fn vx_file_read(ptr : *mut i8, buffer : *mut u8, len : i64) -> i64`
The Rust core behind `File::read`. - `fn vx_file_write(ptr : *mut i8, buffer : *const u8, len : i64) -> i64`
The Rust core behind `File::write`. - `fn vx_file_seek(ptr : *mut i8, offset : i64, whence : i32) -> i64`
The Rust core behind `File::seek`. - `fn vx_file_drop(ptr : *mut i8) -> i32`
The Rust core behind `file_drop`. - `fn fopen(path : *const i8, mode : *const i8) -> *mut i8`
C's `fopen`, for code that wants a `FILE*` rather than the Rust-backed `File`. - `fn fread(ptr : *mut u8, size : i64, nmemb : i64, stream : *mut i8) -> i64`
C's `fread`: the number of whole items read, not the number of bytes. - `fn fclose(stream : *mut i8) -> i32`
C's `fclose`. - `fn fileno(f : *mut i8) -> i32`
C's `fileno`: the descriptor behind a `FILE*`, for handing to `mmap`. - `fn fseek(f : *mut i8, offset : i64, whence : i32) -> i32`
C's `fseek`. - `fn ftell(f : *mut i8) -> i64`
C's `ftell`: the current position, which is how the size is found after a seek to the end. - `fn mmap(addr : *mut i8, len : i64, prot : i32, flags : i32, fd : i32, offset : i64) -> *mut i8`
C's `mmap`, declared here so a file can be mapped without importing `std::mmap`. - `fn munmap(addr : *mut i8, len : i64) -> i32`
C's `munmap`. ## `std::googletest` Assertions for tests written in Vx. **Types** - `trait GoogletestEq` **`trait GoogletestEq` methods** - `fn expect_eq(self : Self, expected : Self) -> i32`
Report whether this value equals `expected`, and answer non-zero when it does not. Records the comparison rather than stopping at it, so a test reports every failure it finds rather than only the first. **`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(actual : T, expected : T) -> i32`
`actual.expect_eq(expected)` written the way a test reads: the value under test first. **C bindings** *(the native functions this module is built on)* - `fn vx_googletest_expect_eq_f32(actual : f32, expected : f32) -> i32`
The Rust core behind `f32`'s `expect_eq`. - `fn vx_googletest_expect_eq_i32(actual : i32, expected : i32) -> i32`
The Rust core behind `i32`'s `expect_eq`. ## `std::hash_map` `HashMap`. **Functions** *(bound directly to C)* - `fn vx_hash_map_new_i32_i32() -> *mut i8`
An empty map from `i32` to `i32`, owned by the Rust core. - `fn vx_hash_map_insert_i32_i32(ptr : *mut i8, key : i32, val : i32) -> i32`
Insert a value under a key, replacing whatever was there. - `fn vx_hash_map_get_i32_i32(ptr : *mut i8, key : i32) -> *mut i8`
A pointer to the value under a key, or null when the key is absent. Vx cannot test a pointer against null (Vx#714), so `contains_key` is the way to ask whether the key is there. - `fn vx_hash_map_contains_key_i32_i32(ptr : *mut i8, key : i32) -> Bool`
Is the key present? - `fn vx_hash_map_len_i32_i32(ptr : *mut i8) -> i32`
How many entries the map holds. - `fn vx_hash_map_drop_i32_i32(ptr : *mut i8) -> i32`
Release the map. - `fn vx_hash_map_new_i32_f32() -> *mut i8`
An empty map from `i32` to `f32`, owned by the Rust core. - `fn vx_hash_map_insert_i32_f32(ptr : *mut i8, key : i32, val : f32) -> i32`
Insert a value under a key, replacing whatever was there. - `fn vx_hash_map_get_i32_f32(ptr : *mut i8, key : i32) -> *mut i8`
A pointer to the value under a key, or null when the key is absent. Vx cannot test a pointer against null (Vx#714), so `contains_key` is the way to ask whether the key is there. - `fn vx_hash_map_contains_key_i32_f32(ptr : *mut i8, key : i32) -> Bool`
Is the key present? - `fn vx_hash_map_len_i32_f32(ptr : *mut i8) -> i32`
How many entries the map holds. - `fn vx_hash_map_drop_i32_f32(ptr : *mut i8) -> i32`
Release the map. ## `std::hash_set` `HashSet`. **Functions** *(bound directly to C)* - `fn vx_hash_set_new_i32() -> *mut i8`
An empty set of `i32`, owned by the Rust core. - `fn vx_hash_set_insert_i32(ptr : *mut i8, val : i32) -> i32`
Add a value. Adding one already present changes nothing. - `fn vx_hash_set_contains_i32(ptr : *mut i8, val : i32) -> Bool`
Is the value in the set? - `fn vx_hash_set_len_i32(ptr : *mut i8) -> i32`
How many distinct values the set holds. - `fn vx_hash_set_drop_i32(ptr : *mut i8) -> i32`
Release the set. ## `std::io` Standard input, output and error. **Functions** - `unsafe fn stdout_write(buffer : *const u8, len : i64) -> i64`
Write `len` bytes to standard output and answer how many were taken. A short write is possible and is not retried here. Unsafe because `buffer` must have `len` bytes to read. - `unsafe fn stderr_write(buffer : *const u8, len : i64) -> i64`
Write `len` bytes to standard error, with the same caveats as `stdout_write`. - `unsafe fn stdin_read(buffer : *mut u8, len : i64) -> i64`
Read up to `len` bytes from standard input and answer how many arrived. Zero means end of input or an error, which are not told apart. Unsafe because `buffer` must have room for `len` bytes. **C bindings** *(the native functions this module is built on)* - `fn vx_stdout_write(buffer : *const u8, len : i64) -> i64`
The Rust core behind `stdout_write`. - `fn vx_stderr_write(buffer : *const u8, len : i64) -> i64`
The Rust core behind `stderr_write`. - `fn vx_stdin_read(buffer : *mut u8, len : i64) -> i64`
The Rust core behind `stdin_read`. ## `std::libc` Direct bindings to the C library. **Functions** *(bound directly to C)* - `fn open(path : *const i8, flags : i32) -> i32`
C's `open`: a file descriptor, or -1 on failure. `flags` is the platform's, not Vx's. - `fn close(fd : i32) -> i32`
C's `close`: 0, or -1 on failure. - `fn lseek(fd : i32, offset : i64, whence : i32) -> i64`
C's `lseek`: the new offset, or -1. `whence` is 0 from the start, 1 from the current position, 2 from the end. ## `std::llama` Helpers used by the Llama 2 example. **Types** - `struct LlamaConfig` - `struct TransformerWeightOffsets` - `struct Tokenizer` **`LlamaConfig` methods** - `fn load(filepath : *const i8) -> LlamaConfig`
Read a checkpoint's header. Nothing validates the file: a path that is not a checkpoint gives a config of whatever the first seven words happen to be, and the sizes computed from it are then wrong. **`TransformerWeightOffsets` methods** - `fn calculate(c : &LlamaConfig) -> TransformerWeightOffsets`
Where each weight matrix begins, as a float offset into one flat buffer. The order is llama2.c's, and the arithmetic assumes the checkpoint was written by it. - `fn load_all_weights(filepath : *const i8, c : &LlamaConfig) -> Tensor`
Every weight as one 1-by-N tensor, copied out of the mapped checkpoint. The length is computed from `c`, so a config that does not match the file reads past its end. A copy rather than a view, so the whole model is resident twice while this runs. **`Tokenizer` methods** - `fn load(filepath : *const i8, vocab_size : i32) -> Tokenizer`
Read a tokenizer file. `vocab_size` must match the checkpoint's. - `fn decode(self : &Tokenizer, prev_token : i32, token : i32) -> String`
The text for one token, as an owned `String` the caller must `drop`. `prev_token` decides whether a leading space is stripped, which is why decoding a token in isolation can differ from decoding it in sequence. **C bindings** *(the native functions this module is built on)* - `fn vx_load_config(filepath : *const i8) -> *mut i32`
Read the seven header fields of a llama2.c checkpoint into an array of `i32`. - `fn vx_load_weights(filepath : *const i8) -> *mut f32`
Map a checkpoint's weights and hand back a pointer to the first float. - `fn vx_build_tokenizer(filepath : *const i8, vocab_size : i32) -> *mut i8`
Read a tokenizer file, answering an opaque handle the Rust core owns. - `fn vx_decode_token(tokenizer_ptr : *mut i8, prev_token : i32, token : i32) -> *const i8`
The text for one token, as a NUL-terminated string. `prev_token` is needed because llama2's tokenizer strips a leading space after the beginning-of-sequence token and not otherwise. - `fn vx_encode_prompt(tokenizer_ptr : *mut i8, text_ptr : *const i8) -> *mut i32`
Encode a prompt, answering an array of token ids with its length in the first slot. - `fn vx_read_prompt_file(filepath : *const i8) -> *const i8`
Read a whole file as a NUL-terminated string. - `fn vx_get_llama_config() -> *mut i32`
The configuration of the checkpoint most recently loaded. ## `std::mmap` Memory-mapped files. **Functions** *(bound directly to C)* - `fn mmap(addr : *mut i8, length : i64, prot : i32, flags : i32, fd : i32, offset : i64) -> *mut i8`
C's `mmap`: map `length` bytes of `fd` into memory. Answers `MAP_FAILED` rather than null on failure, which is -1 cast to a pointer and which Vx cannot test for (Vx#714). `prot` and `flags` are the platform's. - `fn munmap(addr : *mut i8, length : i64) -> i32`
C's `munmap`: unmap a region previously mapped. 0, or -1 on failure. ## `std::net` TCP and UDP sockets. **Types** - `struct TcpStream`
A connected TCP socket, held by the Rust core behind an opaque pointer. Closed by calling `tcp_stream_drop`, since the language has no `Drop` (Vx#495). - `struct UdpSocket`
A bound UDP socket, held by the Rust core behind an opaque pointer. - `struct TcpListener`
A listening TCP socket, held by the Rust core behind an opaque pointer. **`TcpStream` methods** - `unsafe fn connect(addr : *const i8) -> TcpStream`
Connect to `addr`, written as `host:port`. /// **A failure cannot be detected.** The value handed back holds a null pointer, and Vx cannot compare a raw pointer against null (Vx#714), so a refused connection looks like a working one until every later call answers 0. Unsafe because nothing checks that `addr` points at a NUL-terminated string. - `unsafe fn read(self : *mut TcpStream, buffer : *mut u8, len : i64) -> i64`
Read up to `len` bytes into `buffer` and answer how many arrived. Zero means the peer closed, a null socket, or a read error. A short read is normal: TCP is a stream and a message may arrive in pieces, so a caller wanting a whole one loops. Unsafe because `buffer` must have room for `len` bytes. - `unsafe fn write(self : *mut TcpStream, buffer : *const u8, len : i64) -> i64`
Write up to `len` bytes from `buffer` and answer how many were taken. A short write is normal and the remainder is not retried here. **Functions** - `unsafe fn tcp_stream_drop(stream : *mut TcpStream) -> void`
Close the connection and release it. Using it afterwards reads freed memory. - `unsafe fn udp_socket_drop(socket : *mut UdpSocket) -> void`
Close the socket and release it. - `unsafe fn tcp_listener_drop(listener : *mut TcpListener) -> void`
Stop listening and release the socket. **`UdpSocket` methods** - `unsafe fn bind(addr : *const i8) -> UdpSocket`
Bind to `addr`, written as `host:port`. /// **A failure cannot be detected.** The value handed back holds a null pointer, and Vx cannot compare a raw pointer against null (Vx#714), so a refused connection looks like a working one until every later call answers 0. - `unsafe fn recv(self : *mut UdpSocket, buffer : *mut u8, len : i64) -> i64`
Receive one datagram into `buffer` and answer its length. A datagram longer than `len` is truncated and the rest is lost, which is UDP's behaviour and not an error here. The sender's address is not reported. - `unsafe fn send_to(self : *mut UdpSocket, buffer : *const u8, len : i64, addr : *const i8) -> i64`
Send one datagram of `len` bytes to `addr`, answering how many were sent. Nothing guarantees it arrives, or arrives once, or arrives in order. **`TcpListener` methods** - `unsafe fn bind(addr : *const i8) -> TcpListener`
Listen on `addr`, written as `host:port`. **A failure cannot be detected**, for the reason `TcpStream::connect` gives (Vx#714). - `fn accept(self : *mut TcpListener) -> TcpStream`
Wait for a connection and answer with it. Blocks until one arrives. The stream handed back holds a null pointer when the accept failed, which cannot be told from a working one either. **C bindings** *(the native functions this module is built on)* - `fn vx_tcp_stream_connect(c_addr : *const i8) -> *mut i8`
The Rust core behind `TcpStream::connect`. Null when the connection fails. - `fn vx_tcp_stream_read(ptr : *mut i8, buffer : *mut u8, len : i64) -> i64`
The Rust core behind `TcpStream::read`. - `fn vx_tcp_stream_write(ptr : *mut i8, buffer : *const u8, len : i64) -> i64`
The Rust core behind `TcpStream::write`. - `fn vx_tcp_stream_drop(ptr : *mut i8) -> i32`
The Rust core behind `tcp_stream_drop`. - `fn vx_udp_socket_bind(c_addr : *const i8) -> *mut i8`
The Rust core behind `UdpSocket::bind`. Null when the bind fails. - `fn vx_udp_socket_recv(ptr : *mut i8, buffer : *mut u8, len : i64) -> i64`
The Rust core behind `UdpSocket::recv`. - `fn vx_udp_socket_send_to(ptr : *mut i8, buffer : *const u8, len : i64, c_addr : *const i8) -> i64`
The Rust core behind `UdpSocket::send_to`. - `fn vx_udp_socket_drop(ptr : *mut i8) -> i32`
The Rust core behind `udp_socket_drop`. - `fn vx_tcp_listener_bind(c_addr : *const i8) -> *mut i8`
The Rust core behind `TcpListener::bind`. Null when the bind fails. - `fn vx_tcp_listener_accept(ptr : *mut i8) -> *mut i8`
The Rust core behind `TcpListener::accept`. Blocks until a connection arrives. - `fn vx_tcp_listener_drop(ptr : *mut i8) -> i32`
The Rust core behind `tcp_listener_drop`. ## `std::rand` Seeded pseudo-random numbers, one stream per `Rng`. **Types** - `struct SplitMix64`
SplitMix64: a counter, scrambled. One multiply-xor-shift chain, no rejection, no loop. Its job here is to turn one seed word into the four `Rng` needs, which is what it was written for. It is a usable generator on its own where 64 bits of state are enough. - `struct Rng`
A stream of pseudo-random numbers. Seed it, then draw from it. `spare` holds the second of the pair `normal` produces, since the polar method makes two normals at once and handing one back would throw half the work away. **Functions** - `fn sqrt_f64(x : f64) -> f64`
The square root, over the `math` dialect so this module needs no libm. `core::num` has the same method; this is kept because `normal` runs before an import of it would settle, and a local one keeps the dependency to `core::num` alone. - `fn ln_f64(x : f64) -> f64`
The natural logarithm, over the `math` dialect. **`SplitMix64` methods** - `fn seeded(seed : u64) -> SplitMix64`
A generator started at `seed`. Every seed is valid, including zero. - `fn next_u64(self : &mut SplitMix64) -> u64`
Advance the counter by the golden-ratio constant, then scramble the value taken. The three constants are 0x9E3779B97F4A7C15, 0xBF58476D1CE4E5B9 and 0x94D049BB133111EB, spelled in decimal because Vx has no hex literal. **`Rng` methods** - `fn seeded(seed : u64) -> Rng`
A stream from one seed word. Every seed is allowed, zero included. - `fn next_u64(self : &mut Rng) -> u64`
The next draw, uniform over the whole 64-bit range. Every other method is built on it. The value handed back is computed from the state *before* the state advances, which is what xoshiro256\*\* specifies; returning the new state instead is a different and worse generator. - `fn next_u32(self : &mut Rng) -> u32`
The narrower widths take the *high* bits of a draw. The low bits of a xoshiro draw are the weakest ones, and a `% 256` would hand back exactly those. - `fn next_u16(self : &mut Rng) -> u16`
The next 16 bits. - `fn next_u8(self : &mut Rng) -> u8`
The next 8 bits. - `fn next_i64(self : &mut Rng) -> i64`
Uniform over the signed range, negatives included: the bits are reinterpreted, not clamped, so half the draws are below zero. - `fn next_i32(self : &mut Rng) -> i32`
The next 32 bits read as signed, so negative half the time. - `fn next_i16(self : &mut Rng) -> i16`
The next 16 bits read as signed. - `fn next_i8(self : &mut Rng) -> i8`
The next 8 bits read as signed. - `fn next_f64(self : &mut Rng) -> f64`
Uniform in \[0, 1), built from the top 53 bits because that is f64's mantissa. Taking more would round, and rounding up at the top of the range returns exactly 1.0 -- which a caller scaling into a half-open range does not expect. - `fn next_f32(self : &mut Rng) -> f32`
Uniform in \[0, 1), on the same terms with f32's 24 bits. - `fn next_f16(self : &mut Rng) -> f16`
Uniform in \[0, 1) over an evenly spaced grid: 2048 points at f16 and 256 at bf16, each of them exact at that width. The draw is built from that many bits rather than narrowed from an f32, so every point is equally likely. - `fn next_bf16(self : &mut Rng) -> bf16`
A `bf16` uniform in \[0, 1). Eight bits of mantissa, so the draws are coarse. - `fn next_bool(self : &mut Rng) -> bool`
One bit, from the top of a draw. - `fn chance(self : &mut Rng, p : f64) -> bool`
True with probability `p`. Outside [0, 1] it is always false or always true. - `fn below(self : &mut Rng, bound : u64) -> u64`
Uniform in \[0, bound), with no bias. A plain `next_u64() % bound` is biased whenever `bound` does not divide 2^64: the first `2^64 % bound` values come up once more often than the rest. The draws that would land in that overhang are rejected and taken again. `floor` is where the overhang ends, and `0 - bound` is `2^64 - bound` -- the subtraction wraps, which is what makes 2^64 expressible in a 64-bit word at all. - `fn range_i64(self : &mut Rng, lo : i64, hi : i64) -> i64`
Uniform in \[lo, hi), `lo` included and `hi` not. The span is measured in `u64` so that a range spanning zero, or the whole of `i64`, is still one subtraction: `hi - lo` in `i64` would overflow for the widest of them. - `fn range_f64(self : &mut Rng, lo : f64, hi : f64) -> f64`
Uniform in \[lo, hi). With lo > hi the range runs backwards and the result is in (hi, lo\], which is the same arithmetic and rarely what a caller meant. - `fn range_f32(self : &mut Rng, lo : f32, hi : f32) -> f32`
Uniform in \[lo, hi), with the caveat `range_f64` gives for a backwards range. - `fn normal(self : &mut Rng) -> f64`
One draw from the standard normal distribution: mean 0, standard deviation 1. Marsaglia's polar method. A point is drawn from the square until it lands inside the unit circle (about four tries in five), and that point yields *two* normals. The second is kept in the struct for the next call, so the loop runs once per two draws. This is the method to reach for when filling something that stands in for model data. Weights, activations and KV entries are roughly Gaussian, and a uniform fill exercises a range of magnitudes real data never has -- which matters most in f16, where the interesting failures are overflow and underflow at the tails. - `fn normal_around(self : &mut Rng, mean : f64, stddev : f64) -> f64`
A normal draw moved and stretched: mean `mean`, standard deviation `stddev`. - `fn fill_f32(self : &mut Rng, out : *mut f32, count : i32) -> i32`
Fill a buffer in place. One call per buffer rather than one per element, which is what makes filling a real tensor practical -- a 256x8192 one is two million values. `count` elements are written, so the buffer must hold that many. - `fn fill_range_f32(self : &mut Rng, out : *mut f32, count : i32, lo : f32, hi : f32) -> i32`
Fill `count` elements with draws uniform in \[lo, hi). `out` must have room for `count` of them; nothing here checks. - `fn fill_normal_f32(self : &mut Rng, out : *mut f32, count : i32, mean : f32, stddev : f32) -> i32`
Fill `count` elements with normal draws of the given mean and standard deviation. `out` must have room for `count` of them. A negative `stddev` mirrors the distribution rather than being refused. - `fn fill_f16(self : &mut Rng, out : *mut f16, count : i32) -> i32`
Fill `count` elements with `f16` draws uniform in \[0, 1). - `fn fill_normal_f16(self : &mut Rng, out : *mut f16, count : i32, mean : f64, stddev : f64) -> i32`
The normal fill at half precision. The draw and the scaling happen in f64 and narrow once at the store, so a tail value is rounded rather than computed twice. ## `std::simd` SIMD vector types and operations. **Functions** - `unsafe fn simd_add_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The elementwise sum of two four-lane `f32` vectors, written to `out`. All three pointers must address four `f32`s; nothing here checks. `out` may alias `a` or `b`. - `unsafe fn simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The elementwise difference of two four-lane `f32` vectors, written to `out`. All three pointers must address four `f32`s; nothing here checks. `out` may alias `a` or `b`. - `unsafe fn simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The elementwise product of two four-lane `f32` vectors, written to `out`. All three pointers must address four `f32`s; nothing here checks. `out` may alias `a` or `b`. - `unsafe fn simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The elementwise quotient of two four-lane `f32` vectors, written to `out`. All three pointers must address four `f32`s; nothing here checks. `out` may alias `a` or `b`. - `unsafe fn simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32`
`a * b + c` elementwise over four lanes, written to `out`. Fused, so the product is not rounded before the addition: the answer can differ from a separate multiply and add in the last bit, and is the more accurate of the two. **C bindings** *(the native functions this module is built on)* - `fn vx_simd_add_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The Rust core behind `simd_add_f32x4`. - `fn vx_simd_sub_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The Rust core behind `simd_sub_f32x4`. - `fn vx_simd_mul_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The Rust core behind `simd_mul_f32x4`. - `fn vx_simd_div_f32x4(a : *const f32, b : *const f32, out : *mut f32) -> i32`
The Rust core behind `simd_div_f32x4`. - `fn vx_simd_fma_f32x4(a : *const f32, b : *const f32, c : *const f32, out : *mut f32) -> i32`
The Rust core behind `simd_fma_f32x4`. ## `std::string` `String` and text manipulation. **Types** - `struct String`
A growable, owned string, held by the Rust core behind an opaque pointer. Released by calling `drop`, since the language has no `Drop` (Vx#495). **`String` methods** - `fn new() -> String`
An empty string. - `unsafe fn from_c_str(c_str : *const i8) -> String`
A copy of a NUL-terminated C string. Unsafe because nothing here checks that `c_str` is a valid pointer to a NUL-terminated run of bytes. The copy is owned by the new `String`; the argument is not taken. - `unsafe fn push_c_str(self : *mut String, c_str : *const i8) -> i32`
Append a NUL-terminated C string, with the same requirement `from_c_str` has. - `fn len(self : *mut String) -> i32`
The length in bytes, not in characters: this counts UTF-8 code units. - `fn as_c_str(self : *mut String) -> *const i8`
A NUL-terminated copy of the contents. **This allocates and leaks.** The Rust core builds a fresh `CString` and hands out its raw pointer, so every call costs a copy that is never released: the matching `vx_string_free_c_str` is declared in the extern block and is not wrapped as a method here. Calling this in a loop grows the process without bound. - `fn drop(self : *mut String) -> i32`
Release the string. Reading it afterwards reads freed memory. **`i32` methods** - `fn to_string(self : i32) -> String`
This number in decimal, as an owned `String` the caller must `drop`. **Functions** - `unsafe fn string_length(s : *const i8) -> i32`
The number of bytes before the NUL, found by scanning. Unsafe because it walks until it finds one: a pointer to bytes with no NUL runs off the end. `core::str` replaces this once a string literal carries its length (Vx#532). - `unsafe fn string_compare(s1 : *const i8, s2 : *const i8) -> i32`
C's `strcmp`: negative when `s1` sorts first, positive when `s2` does, zero when equal. The magnitude is the difference between the first bytes that differ, which callers should not read anything into beyond its sign. Unsafe for the reason `string_length` is. - `unsafe fn parse_int(s : *const i8) -> i32`
The leading run of decimal digits as an `i32`. Stops at the first byte that is not a digit and answers what it has, so `"12abc"` is 12 and `"abc"` is 0 -- there is no way to tell that second case from a genuine zero. A leading `-` is not a sign, it is a stop. Nothing checks for overflow: a run longer than `i32` holds wraps. `core::str`'s `parse` replaces it (Vx#532). **C bindings** *(the native functions this module is built on)* - `fn vx_string_new() -> *mut i8`
The Rust core behind `String::new`. - `fn vx_string_from_c_str(ptr : *const i8) -> *mut i8`
The Rust core behind `String::from_c_str`. - `fn vx_string_push_c_str(ptr : *mut i8, c_str : *const i8) -> i32`
The Rust core behind `String::push_c_str`. - `fn vx_string_len(ptr : *mut i8) -> i32`
The Rust core behind `String::len`. - `fn vx_string_as_c_str(ptr : *mut i8) -> *const i8`
Allocate a NUL-terminated copy and hand out its raw pointer. The caller owns it and releases it with `vx_string_free_c_str`; `String::as_c_str` does not, and leaks. - `fn vx_string_free_c_str(ptr : *const i8) -> i32`
Release what `vx_string_as_c_str` returned. Nothing in Vx calls this yet. - `fn vx_string_drop(ptr : *mut i8) -> i32`
The Rust core behind `String::drop`. - `fn vx_i32_to_string(val : i32) -> *mut i8`
The Rust core behind `i32::to_string`. ## `std::tensor` Operations on `Tensor`, including shape queries and elementwise maths. **`Tensor` methods** - `fn from_ptr_1d(ptr : *mut T, d1 : i32) -> Tensor`
A 1-by-`d1` tensor holding a copy of `d1` elements read from `ptr`. The elements are copied, so the tensor does not alias the buffer and outlives it. `ptr` must address `d1` elements; nothing here checks. - `fn from_ptr_2d(ptr : *mut T, d1 : i32, d2 : i32) -> Tensor`
A `d1`-by-`d2` tensor holding a copy of `d1 * d2` elements read from `ptr` in row-major order. Copied, as `from_ptr_1d` is. - `fn slice_2d(self : &Tensor, row : i32, d1 : i32, d2 : i32) -> Tensor`
A `d1`-by-`d2` tensor read from one row of this one, taken row-major from its start. **A copy, not a view.** Every `slice_` method here allocates and copies, so writing to the result does not touch the original and the cost is the elements moved, not constant. - `fn slice_2d_from_1d(self : &Tensor, start : i32, d1 : i32, d2 : i32) -> Tensor`
A `d1`-by-`d2` tensor read from row 0 beginning at `start`, reshaped row-major. A copy. - `fn slice_1d(self : &Tensor, start : i32, d1 : i32) -> Tensor`
A 1-by-`d1` tensor read from row 0 beginning at `start`. A copy. - `fn fill(self : &mut Tensor, val : T) -> void`
Set every element to `val`. Lowered through `linalg`, with a separate path chosen at compile time for AVX-512. - `fn copy(self : &mut Tensor, src : &Tensor) -> void`
Overwrite this tensor's elements with `src`'s. Both must have the same shape; nothing here checks, and a mismatch reads or writes past an end. - `fn assign(self : &mut Tensor, val : T) -> void`
Set every element to `val`, as `fill` does. The two differ in the `linalg` form they emit, not in what they mean. - `fn compare(self : &Tensor, other : &Tensor) -> 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` methods** - `fn fill_static(self : &mut Tensor, val : T) -> void`
Set every element of a statically shaped tensor to `val`. The shape is known at compile time here, so the emitted loop has constant bounds. ## `std::time` Clocks and durations. **Functions** - `fn now() -> f32`
Seconds from some fixed point, for measuring how long something took. Only differences between two calls mean anything: the origin is unspecified. An `f32` holds about seven digits, so a long-running process loses resolution as the value grows -- `unix_timestamp` is the `f64` one. - `fn sleep(seconds : f32) -> i32`
Pause this thread for at least `seconds`. It may be longer; it is never shorter. - `fn unix_timestamp() -> f64`
Seconds since the Unix epoch, as an `f64`. Wall-clock time, so it can move backwards when the system clock is adjusted; `now` is the one to measure a duration with. - `unsafe fn bench_report(name : *const i8, unit : *const i8, value : f32) -> i32`
Report a benchmark measurement in the form the harness collects. Unsafe because `name` and `unit` must point at NUL-terminated strings. **C bindings** *(the native functions this module is built on)* - `fn vx_get_time() -> f32`
The Rust core behind `now`. - `fn vx_sleep(seconds : f32) -> i32`
The Rust core behind `sleep`. - `fn vx_unix_timestamp() -> f64`
The Rust core behind `unix_timestamp`. - `fn vx_bench_report(name : *const i8, unit : *const i8, value : f32) -> i32`
The Rust core behind `bench_report`. ## `std::vec` `Vec`, a growable array. **Types** - `struct Vec`
A growable array of `T`, held in a buffer the Rust core owns. Vx keeps a typed view into that buffer and does its own element loads and stores; growth, alignment and the `capacity * elem_size` overflow check belong to Rust. There is no `Drop` in the language, so the buffer is released by calling `free` and not before. - `struct VecIter`
An iterator over a `Vec`'s elements, holding a pointer to the vector it walks. Growing or freeing that vector while this exists leaves the iterator pointing at the old buffer. - `struct VecMap`
The iterator `VecIter::map` builds: the inner walk plus the function applied to each item. **`Vec` methods** - `fn new() -> Vec`
An empty vector with room for two elements. - `fn with_capacity(capacity : i32) -> Vec`
An empty vector with room for `capacity` elements before it has to grow. - `fn free(self : &mut Vec) -> i32`
Release the buffer and leave the vector empty with no capacity. Called by hand, because the language has no `Drop` yet (Vx#495). Reading an element after this is reading freed memory; `len` answers 0, so a loop over it is safe. - `fn as_mut_ptr(self : &Vec) -> *mut T`
A raw pointer to the first element. Invalidated by anything that grows the vector. - `fn as_mut_slice(self : &mut Vec) -> &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 as_slice(self : &Vec) -> &T`
A reference to the first element, with the caveat `as_mut_slice` describes. - `fn push(self : &mut Vec, 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(self : &Vec, index : i32) -> T`
The element at `index`, by value. # Panics When `index` is negative or not below `len`. The check is in the Rust core, which prints the index and the length and aborts the process -- it does not unwind. - `fn set(self : &mut Vec, index : i32, val : T) -> i32`
Overwrite the element at `index`. # Panics As `get` does, and for the same reason. Setting past the end does not extend the vector; `push` is what grows it. - `fn len(self : &Vec) -> i32`
How many elements are in the vector, which is not its capacity. - `fn iter(self : &Vec) -> VecIter`
An iterator over the elements, borrowing the vector rather than consuming it. **`Extend for Vec` methods** - `fn extend(self : &mut Vec, iter : I) -> i32`
Push every item `iter` has left, in order. - `fn extend_one(self : &mut Vec, item : T) -> i32`
Push `item`. **`Default for Vec` methods** - `fn default() -> Vec`
An empty `Vec`, as `new` makes. **`FromIterator for Vec` methods** - `fn from_iter(iter : I) -> Vec`
A new `Vec` holding every item `iter` has left, which the caller owns and must `free`. **`Iterator for VecIter` methods** - `fn next(self : &mut VecIter) -> Option`
The next element, or nothing once the end is reached. **`DoubleEndedIterator for VecIter` methods** - `fn next_back(self : &mut VecIter) -> Option`
The last element not yet handed out from either end. **`ExactSizeIterator for VecIter` methods** - `fn len(self : &VecIter) -> i64`
How many elements are left between the two ends. **`VecIter` methods** - `fn map(self : VecIter, f : Closure1) -> VecMap`
An iterator over these elements with `f` applied to each. **`Iterator for VecMap` methods** - `fn next(self : &mut VecMap) -> Option`
The next element of the inner iterator with `f` applied. **`ExactSizeIterator for VecMap` methods** - `fn len(self : &VecMap) -> i64`
As many as the inner iterator has. **`VecMap` methods** - `fn collect(self : &mut VecMap) -> Vec`
Drain the iterator into a fresh `Vec`, which the caller owns and must `free`. **C bindings** *(the native functions this module is built on)* - `fn vx_vec_alloc(elem_size : i64, cap : i64) -> *mut i8`
Allocate a buffer for `cap` elements of `elem_size` bytes. Rust owns the alignment and the overflow check on the product. - `fn vx_vec_grow(ptr : *mut i8, old_cap : i64, new_cap : i64, elem_size : i64) -> *mut i8`
Reallocate to `new_cap` elements, moving the contents. The old pointer is invalid after. - `fn vx_vec_free(ptr : *mut i8, cap : i64, elem_size : i64) -> i32`
Release the buffer. - `fn vx_vec_bounds_check(index : i64, len : i64) -> i32`
Abort the process when `index` is outside `0..len`, printing both. Answers 0 otherwise. ______________________________________________________________________ 730 functions across 32 modules. --- # Diagnostic index Every diagnostic the Vx compiler can emit, with the code it reports and what it means. Codes are grouped by the compilation stage that raises them, and the group is readable off the number: `E1xxx` is the parser, `E3xxx` the type checker, `E6xxx` the placement and capacity rules, and so on. A `W` prefix is a warning rather than an error. > This file is generated from `src/diagnostic.rs` by `scripts/tools/gen_error_index.py`. Edit the > doc comments on the codes there, not this file. Every code below links to its own page, which carries the message the compiler emits and, where the test suite has one, a program that triggers it. ## Contents - [Warnings](#warnings) — `W1001`–`W1031` (23 codes) - [Parser Errors](#parser-errors) — `E1001`–`E1013` (13 codes) - [Name Resolution Errors](#name-resolution-errors) — `E2001`–`E2007` (7 codes) - [Type Errors](#type-errors) — `E3001`–`E3041` (41 codes) - [Borrow/Ownership Errors](#borrowownership-errors) — `E4001`–`E4005` (5 codes) - [Safety Errors](#safety-errors) — `E5001`–`E5002` (2 codes) - [Topology/Hardware Errors](#topologyhardware-errors) — `E6001`–`E6028` (28 codes) - [Tensor/Math Errors](#tensormath-errors) — `E7001`–`E7004` (4 codes) - [Contract/Verification Errors](#contractverification-errors) — `E8001`–`E8005` (5 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`](/errors/W1001/) | Unused variable binding | | [`W1002`](/errors/W1002/) | Unused function definition | | [`W1003`](/errors/W1003/) | Unreachable code after return, break, or continue | | [`W1004`](/errors/W1004/) | Unnecessary mutable binding (`let mut x` where x is never reassigned) | | [`W1005`](/errors/W1005/) | Shadowed variable in same scope | | [`W1006`](/errors/W1006/) | Redundant borrow (`&&x`) | | [`W1007`](/errors/W1007/) | Implicit type widening in `as` cast | | [`W1008`](/errors/W1008/) | Empty match arm body | | [`W1009`](/errors/W1009/) | Unused function parameter | | [`W1010`](/errors/W1010/) | Unnecessary unsafe block (no unsafe ops inside) | | [`W1013`](/errors/W1013/) | Redundant `as` cast to same type | | [`W1014`](/errors/W1014/) | Narrowing cast loses precision | | [`W1020`](/errors/W1020/) | Immediately dereferenced borrow (`*&x`) | | [`W1022`](/errors/W1022/) | Transfer to same memory space (no-op) | | [`W1023`](/errors/W1023/) | Spawn on Topology::Current (no-op) | | [`W1024`](/errors/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 for Topology::X`. Both were called `Transfer` before Vx#353. | | [`W1025`](/errors/W1025/) | Use of a user-defined topology with no registered descriptor (not declared via `Topology { ... }` and not registered by a plugin). Often a typo of a built-in; defaults to host-like placement. | | [`W1026`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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). | ## Parser Errors Raised while turning source text into an AST. The program is not syntactically valid Vx. | Code | Meaning | | --- | --- | | [`E1001`](/errors/E1001/) | Unexpected token | | [`E1002`](/errors/E1002/) | Unexpected end of file | | [`E1003`](/errors/E1003/) | Expected identifier | | [`E1004`](/errors/E1004/) | Expected type | | [`E1005`](/errors/E1005/) | Expected expression | | [`E1006`](/errors/E1006/) | Unclosed delimiter (paren/brace/bracket) | | [`E1007`](/errors/E1007/) | Missing semicolon | | [`E1008`](/errors/E1008/) | Missing comma | | [`E1009`](/errors/E1009/) | Invalid operator | | [`E1010`](/errors/E1010/) | Unknown topology variant | | [`E1011`](/errors/E1011/) | Unknown memory space | | [`E1012`](/errors/E1012/) | Unknown element type | | [`E1013`](/errors/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`](/errors/E2001/) | Undefined variable | | [`E2002`](/errors/E2002/) | Undefined function | | [`E2003`](/errors/E2003/) | Unknown enum | | [`E2004`](/errors/E2004/) | Unknown enum variant | | [`E2005`](/errors/E2005/) | Unknown struct field | | [`E2006`](/errors/E2006/) | Module does not export function | | [`E2007`](/errors/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`](/errors/E3001/) | Type mismatch in variable declaration | | [`E3002`](/errors/E3002/) | Type mismatch in return | | [`E3003`](/errors/E3003/) | Type mismatch in function argument | | [`E3004`](/errors/E3004/) | Type mismatch in binary operation | | [`E3005`](/errors/E3005/) | Type mismatch in relational operation | | [`E3006`](/errors/E3006/) | Type mismatch in logical operation | | [`E3007`](/errors/E3007/) | If branch type mismatch | | [`E3008`](/errors/E3008/) | Enum payload type mismatch | | [`E3009`](/errors/E3009/) | Enum payload arity mismatch | | [`E3010`](/errors/E3010/) | Function argument count mismatch | | [`E3011`](/errors/E3011/) | Unsupported cast | | [`E3012`](/errors/E3012/) | Type mismatch in struct field initialization | | [`E3013`](/errors/E3013/) | Missing struct field in initialization | | [`E3014`](/errors/E3014/) | Range type mismatch | | [`E3015`](/errors/E3015/) | Trait not implemented | | [`E3016`](/errors/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()` folded to 8, so a buffer was sized for an element type that was never chosen. Spell the type argument out, as `Vec::new()`, or give the result a type, as `let v : Vec = Vec::new();`. | | [`E3017`](/errors/E3017/) | Closure argument count or type mismatch | | [`E3018`](/errors/E3018/) | An array literal whose elements are not scalars, or which is empty. 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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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) -> 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::from_ptr_2d`), which is what the corpus already does. | | [`E3023`](/errors/E3023/) | A shaped tensor initialized from a scalar. `let a : Tensor = 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::fill(v)` is the spelling. The rank-0 wrap (`Tensor = 1.0`) is a different thing and stays legal. | | [`E3024`](/errors/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`](/errors/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`](/errors/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. | | [`E3027`](/errors/E3027/) | A function whose return type is a closure. A closure value points into the frame that made it, so it cannot outlive that frame yet. | | [`E3028`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/E3032/) | A chain of generic instantiations that does not end -- `f()` 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`](/errors/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`](/errors/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`](/errors/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. | | [`E3037`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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` does with its closure. | ## 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`](/errors/E4001/) | Use of moved or consumed linear variable | | [`E4002`](/errors/E4002/) | Cannot access mutably borrowed variable | | [`E4003`](/errors/E4003/) | Cannot borrow as mutable (already immutably borrowed) | | [`E4004`](/errors/E4004/) | Cannot borrow (already mutably borrowed) | | [`E4005`](/errors/E4005/) | A returned reference escapes the function borrowing a function-local (dangling return) | ## Safety Errors Raised where an operation needs an `unsafe` context and does not have one. | Code | Meaning | | --- | --- | | [`E5001`](/errors/E5001/) | Unsafe function call outside unsafe block | | [`E5002`](/errors/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`](/errors/E6001/) | Topology mismatch in function call | | [`E6002`](/errors/E6002/) | Cannot transfer between memory spaces (no hardware path) | | [`E6003`](/errors/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`](/errors/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`](/errors/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`](/errors/E6006/) | A `Memory` declaration's `within:` hierarchy forms a cycle (a space contains itself). | | [`E6007`](/errors/E6007/) | A `Memory` sub-space's `capacity` exceeds its parent's capacity (a child cannot be larger than what contains it). | | [`E6008`](/errors/E6008/) | A `Memory` declaration has a non-positive `capacity`, `bandwidth`, or `granule`. | | [`E6009`](/errors/E6009/) | A statically-shaped tensor placed in a memory space exceeds that space's `capacity`. | | [`E6010`](/errors/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`](/errors/E6011/) | A sub-space's `scope` is broader than its parent's (locality must narrow down `within:`). | | [`E6012`](/errors/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`](/errors/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`](/errors/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 ` 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`](/errors/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`](/errors/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::` 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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`](/errors/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`. | ## Tensor/Math Errors Raised on tensor shapes and numeric operations, including shape mismatches that are decided at compile time. | Code | Meaning | | --- | --- | | [`E7001`](/errors/E7001/) | Matmul dimension mismatch | | [`E7002`](/errors/E7002/) | Matmul element type mismatch | | [`E7003`](/errors/E7003/) | Reshape arithmetic mismatch | | [`E7004`](/errors/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`](/errors/E8001/) | Cannot prove postcondition | | [`E8002`](/errors/E8002/) | Comptime assert failed | | [`E8003`](/errors/E8003/) | Compile-time index out of range | | [`E8004`](/errors/E8004/) | Compile-time evaluation exceeded the call-depth limit | | [`E8005`](/errors/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. | ______________________________________________________________________ 128 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` | ```bash 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 ` | Compile against a declared machine — see [machine files](machine-files.md) | | `--host ` | 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. ## 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. ```bash 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. ## The other tools | Tool | Purpose | | --- | --- | | `vx-format` | The canonical source formatter | | `vx-opt` | MLIR pass driver for the Vx dialect | | `vx-analyzer` | Language server | | `cargo vx-bench` | Benchmark harness that injects timing into the AST to measure real hardware execution time | `vx-format` has no options worth learning: there is one canonical style, and it applies it. ```bash vx-format src/*.vx ``` ## The standard library 21 modules, imported as `std::`. Every type and function is listed in the [standard library reference](stdlib-reference.md), 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_STD_PATH` | Library search path. A `PATH`-style list, not a single directory | | `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: ```bash 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: ```bash 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](https://github.com/vx-lang/Vx/issues) with: - the smallest `.vx` file that reproduces it, - the exact command you ran, - what you expected and what happened, - `vxc --version` and your platform. If the compiler produced MLIR, `--emit-mlir` output is often the fastest way to show what went wrong. ## Your first change Issues labelled [**good first issue**](https://github.com/vx-lang/Vx/labels/good%20first%20issue) 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#506](https://github.com/vx-lang/Vx/issues/506) | `while` is not a keyword, and a fixture appears to test it but does not | | [Vx#494](https://github.com/vx-lang/Vx/issues/494) | The unused-variable warning fires on a variable used only through method calls | | [Vx#445](https://github.com/vx-lang/Vx/issues/445) | Twelve warning codes are declared but never emitted | | [Vx#423](https://github.com/vx-lang/Vx/issues/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**](https://github.com/vx-lang/Vx/labels/help%20wanted) holds larger pieces that are still well specified. ## Signing the CLA Your first pull request needs a signed [Contributor License Agreement](https://github.com/vx-lang/Vx/blob/main/docs/CLA.md). 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. ## Working on the compiler Start with [building from source](building.md). 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](https://github.com/vx-lang/Vx/blob/main/docs/DEVELOPER_GUIDE.md). The repository layout: ``` src/ the vxc compiler (Rust) lexer, parser/ source to AST syntax/ AST, types, topologies, declarations hir/ lowering, type checking, borrow checking check/ transfer, calls, access, autodiff, region traffic memory.rs the memory algebra: containment, capacity, derived cost seam.rs asynchronous-visibility contracts flatten.rs the flat, AST-annihilated path codegen/ MLIR emission dialect/ the Vx dialect and its lowering (C++) plugin/ vendor MLIR pass plugins fleet/ machine files runtime/ dispatch and the distributed fleet runtime (C++) stdlib/ the standard library vx-analyzer/ language server tests/ integration suites and unit tests ``` ## Conventions worth knowing **No locks in the compiler.** A compilation has to be isolated, and CI rejects `Mutex`, `RwLock`, `OnceLock` and friends anywhere under `src/`. Process-global atomics are banned for the same reason: a static that a worker thread can write is shared mutable state whether or not it takes a lock to do it, and a cached answer survives across compilations in a long-lived process such as the language server. **Determinism is gated, not hoped for.** Several tests assert that the pipeline emits byte-identical MLIR at one thread, at four, and with the thread pool off the path entirely. If your change makes output depend on iteration order, those will catch it. **Format before you commit.** `cargo fmt` for Rust, `vx-format` for `.vx` files, `clang-format` for C++. CI checks all three, and clippy runs with `-D warnings`. **Tests come with the change.** New backend behaviour needs a fixture under `tests/`; the check lines are generated by `utils/update_mlir_test_checks.rs` rather than written by hand. ## Extending the compiler for new hardware Vendors are not expected to patch the compiler. A new accelerator arrives as: 1. a **machine file** declaring its memory hierarchy and interconnect, and 1. an **MLIR pass plugin** under `src/plugin/` that lowers the Vx dialect for that target. `docs/adding_a_topology.md` walks through the process. ---