All releases

Prismio 0.1.0

The first release. A self-hosted, statically typed, ahead-of-time compiled language with an inference-driven memory model, for macOS, Linux and Windows.

Published
Status
stable

Prismio 0.1.0 is the first release.

Prismio is a self-hosted, statically typed, ahead-of-time compiled language with an inference-driven memory model: you write no free, no lifetimes and no reference counts, and the compiler decides per allocation site which of six implementation tiers a value needs. Every code sample in the documentation is compiled by that compiler as part of the docs build, and the compiler reaches a byte-identical two-generation fixpoint building itself.

Downloads

Platform Archive Needs
macOS (Apple silicon) prismio-0.1.0-macos-arm64.tar.gz macOS 14 or later for the compiler; programs it builds run on macOS 11 or later
Linux (x86-64) prismio-0.1.0-linux-x64.tar.gz glibc 2.34 or later
Windows (x86-64) prismio-0.1.0-windows-x64.zip Visual Studio Build Tools and the Windows SDK (see below); no Visual C++ redistributable
Linux (ARM64) prismio-0.1.0-linux-arm64.tar.gz glibc 2.34 or later
Windows (ARM64) prismio-0.1.0-windows-arm64.zip the same build tools as x86-64

Each archive has a .sha256 beside it. The archives are not signed: the checksum shows the download is intact, not who made it, so take both from this release page. On macOS and Linux the installer does this for you and refuses an archive that does not match:

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

On Windows, unpack the .zip and put its bin directory on PATH. The compiler carries its own LLVM but links programs with the system's linker, so you also need the platform's C tools: the Xcode Command Line Tools on macOS, cc and the C library headers on Linux (build-essential on Debian and Ubuntu), or Visual Studio Build Tools with the Desktop development with C++ workload on Windows. The macOS, Linux x86-64 and Windows x86-64 archives are built and tested by the project's CI. The two ARM64 archives are built and tested on virtual machines, so they have had less use. There is no archive for Intel Macs; build from source (see the installation guide).

Highlights

  • A smaller compiler. It links three LLVM backends, AArch64, X86 and WebAssembly, instead of all 25, and is about half the size. --target accepts those three families.
  • Constant array globals. A module-level let words = ["alpha", "beta"] is read-only data, read by address, and builds nothing at run time.
  • print and println take anything printable, Option<T> included, and std.term styles any value that can be printed.
  • Reading input is shorter: readLineOr, prompt, readInt, readFloat, readLines, readWords and isAtEnd on stdin.
  • Faster to compile. The allocation analysis answers its whole-program questions from indexes built once, and the compiler emits its own IR in about half the time (1.4 s to 0.7 s), with identical output.
  • A fixed compiler bug. A function that ended in panic, unreachable or exit while owning a value emitted invalid IR; it compiles now.

What is new

Listed against the first release candidate, so a breaking change below is one you only meet if you used it; read the rest as the feature list. What the candidate already had is under Foundations.

Standard library

  • Standard input, in the new std.input: for line in stdin.lines(), stdin.readLine() → Option<String>, stdin.readAll(), and the shorter forms readLineOr(fallback), prompt(text), readInt() and readFloat() (an Int? and a Float?), readLines(), readWords() and the isAtEnd property. \n and \r\n both end a line. Counting 3 million lines takes 134 ms, against C++ getline's 156 ms and Rust lines()'s 274 ms.
  • std.time: Instant.now() and start.elapsed() on the monotonic clock, Duration in I64 nanoseconds, unixTime() and sleep(duration).
  • More of the file system: listDirectory (sorted, without . and ..), appendFile, rename (replacing the destination, on Windows too), removeDirectory (empty directories only), and metadata(path) → Option<Metadata> with size, modified, isDirectory and isFile.
  • readLines(path), a file one line at a time, and tryReadLines(path). The reader closes the file itself at the end; close() is for a loop that stops early.
  • StringBuilder in std.string, for text kept in a struct field: new, append, appendLine, length, isEmpty, toString. 640,000 appends take 73 ms and release everything.
  • Map methods and removal: m.get, getOr, set, has, remove, clear, keys(), values(), keyAt, valueAt, length, isEmpty, and m[k], which stops the program on a missing key. Removal is O(1). keyAt reads a key in place when no copy is wanted.
  • Option and Result methods: isSome, isNone, isOk, isErr as properties; unwrapOr and expect on both; okOr; ok() and err(); and map, andThen and mapErr, whose result type is the closure's.
  • Environment variables and the process id: process.env(name) → Option<String>, setEnv, removeEnv, process.pid.
  • std.math: Float roots, rounding, powers, logarithms and trigonometry, integer pow, gcd, lcm, floorDiv, floorMod and isqrt, and associated constants such as Float.PI and Int.MAX. Each Float function lowers to the instruction or C-library call a C compiler would use.
  • Printing: print, println, eprint and eprintln accept any type with a Display implementation, and Option<T> and String? print their value or none. They live in std.display, so a program that only prints imports nothing new.
  • std.unicode (grapheme clusters, terminal width, normalization), std.term (terminal colours and styles, applied to anything that can be printed), Process, Child and Stream for starting other programs, string interpolation "${x}", and eprint/eprintln for every type.

Language

  • Conversions: x as T? is the conversion that can fail and answers none -- 300 as U8?, 3.5 as Int?, "4x" as Int? -- with no allocation and no branch. x as String for every number, Bool and Char. "12" as Int is read at compile time, and "12Sf" as Int is a compile error.
  • Scalar optionals: Int?, Float?, Bool?, Char? and an enum's T?, a flag beside the value that is copied like T and never allocated -- in a binding, a field, a Vec, a Map value -- with unwrapOr, expect, ==, sort and println.
  • An enum is its own type, and a match naming every variant is exhaustive without _.
  • panic, unreachable, assert and exit, with no import. A failure prints its message and location to stderr and exits with status 101. A function may end in panic, unreachable or exit instead of a return.
  • Closure bounds: F: Fn(A, B) -> R in a parameter list, a where clause, or impl Fn(...). A type parameter used only in the result is solved from the closure's return type. A closure that does not fit is refused, naming both signatures.
  • % on Float, as C's fmod: the result takes the dividend's sign.
  • Declared properties: prop length(self) -> Int, read without parentheses.
  • Constant array globals: a module-level let holding an array of literals (let primes = [2, 3, 5, 7]) is read-only data. It cannot be assigned to, and a computed element is refused.
  • A type's own Default, the default value of any type, type-level functions (Type.fn()), and import groups (import {io, string} from std).
  • Arrays with a written length (Array<T, N>), returned by value and stored in struct fields; step, repeat, labelled loops, and for over every collection; extern let for a foreign global; 0xFF, 0o755 and 0b1010 literals, and scientific notation in float literals.

Tooling

  • prismio check --overlay <file> <text> and --module <name>: one file of a program, checked as an editor sees it.
  • The compiler carries its own LLVM 23 and no longer depends on an installed one, and a Windows program links with MSVC's link.exe. It contains the AArch64, X86 and WebAssembly backends only, so --target accepts those three families and any other triple stops with P1043; see targets.
  • A diagnostic that finds no overload names the argument types it was given and, for an optional, how to unwrap it; a generic type prints as the program wrote it (Option<Point>, not an internal name).
  • prismio bench reports in the terminal, and the compiler's own output is styled on a terminal.

Breaking changes

  • s.length() and x.sqrt no longer compile. A property is declared prop and read without parentheses; a method takes them.
  • a..b includes b; a..<b stops before it. A range counts down when its start is past its end: for i in 10..0 visits 10 down to 0, where it used to run no times, and step is a distance.
  • mut covers what a Vec or an array holds. v.push(x) on a binding that is not let mut is refused, and a function that changes a Vec it was given takes it inout. A store through a Slice follows the same rule.
  • A Float prints with at most fifteen significant digits: println(0.1 + 0.2) prints 0.3.
  • A match whose arms all return ends a function, so the fallback return after one is now unreachable code.
  • The old free-function API is gone from programs: the runtime's list_* entry points, the prefixed str* and char* functions (write s.trim(), n.toString()), and std.fs's raw read_file, join_path and the other nine, which are now internal. List<T> is spelled Vec<T>.
  • Every parse returns a scalar T?, not Option<T>: s.parseInt() is an Int?. Write s.parseInt().unwrapOr(0) where optionOr(s.parseInt(), 0) was, and x == none for optionIsNone(x). It is the same function as s as Int?, and it no longer allocates.
  • An Int is not an enum. let c: Color = 1, an Int passed for a Color and 7 as Color are refused; c as Int and n as Color? are the conversions.
  • PRISMIO_INLINE_ELEMS=0 is gone. The run-time switch that stored every list element boxed leaked; flat elements are always stored inline.

Fixed

  • No known memory corruption remains. A recursive enum built from let-bound children no longer double-frees; three ownership shapes that freed memory that was not live — a function returning optionOr(o, d) for a caller's literal, returning a payload binder, and moving one into a new enum — now keep the value alive instead.
  • Channel<Int> is refused, with a message to send a one-field struct: it used to send a number where the runtime reads a pointer. See channels.
  • An array as a type argument is refused (Vec<[Int]>, a function returning Box<[Int]>), because it pointed into a frame the container outlived. See generics.
  • A task whose thread cannot start is a panic (exit 101, with the reason), instead of running inline, which could deadlock on a channel.
  • Results are released where nothing names them: a result passed straight into another call (println(f(x)), x.trim().toUpper()), and one discarded as a statement.
  • Floats follow IEEE 754: NaN != NaN is true, -x keeps the sign of zero, and a float-to-integer as saturates instead of producing an undefined value.
  • A function ending in panic, unreachable or exit while it owns a value no longer emits invalid IR, and the six extern fn exit declarations that worked around it are gone.
  • A C-produced String inside a loop no longer leaks, I64.MAX keeps its type, and resultIsErr can be called.

Foundations

  • A self-hosted lexer, parser, import resolver, semantic analysis, AIF and LLVM IR generation, with a three-generation bootstrap from a committed, target-neutral LLVM IR seed
  • Windows, macOS and Linux, cross-compilation (--target, --sysroot), -g DWARF debug information, a JIT (--jit), and the UMS package manifest (build.ums) with debug and release profiles, project commands, C sources compiled into a program, a lockfile and local path dependencies
  • Structs, payload enums, Option/Result, optionals (T?), arrays, Vec<T>, Slice<T> views and String
  • Generics by monomorphisation, with per-specialisation container layout; methods and impl blocks; traits, checked statically at the instantiation
  • Closures: a struct plus a call function resolved by overloading, so no function pointer and no indirect call
  • Module namespacing and visibility: public/private/internal, module-qualified calls and selective imports
  • Concurrency: spawn/join/Task<R>, and a blocking typed Channel<T> whose receive answers T?
  • The memory model: six inferred tiers — frame storage, region arenas with automatic placement, scope-bound ownership, non-atomic reference counting, atomic counting where a value provably crosses threads, and a cycle collector. --verify checks the inference at run time, --why explains one site's tier, and a differential test runs the whole engine against an independent oracle
  • Programmer-directed AoS↔SoA data views

Not included

async/await, an executor, atomics and other synchronization types, and a specified memory-ordering model. User-written lifetimes, a non-owning typed pointer, exceptions and a ? operator, macros, a package registry and version solving, importing modules from a path dependency, C++ sources and library targets in the manifest, aliased imports, mobile toolchains, and a formatter, linter or language server. Random numbers, calendar dates and time zones, networking, JSON and regular expressions. Collections beyond Vec<T> and Map<K, V> — chunked vectors, VecDeque<T>, sets, a sorted map and a priority queue — are Coming Soon; see what to use until then.

WebAssembly is blocked, not in progress. Prismio emits wasm32 IR, but there is no C library for wasm32-unknown-unknown, so the runtime cannot be built for it.

Performance

On the maintained suite of 62 workloads, each written the same way in Prismio, C++ and Rust, Prismio's geometric-mean time is 0.87× of C++ (clang++ -O3 -flto) and 0.84× of Rust (-C opt-level=3 -C lto=fat), with peak memory level with C++ and 4% below Rust. That was measured on 2026-10-02 on an Apple M5 (arm64, macOS), 5 runs each, with checksum agreement asserted before any timing is read.

Against C++ it is faster on 20 workloads, level on 40 (inside the suite's noise rule) and slower on 2: prime_sieve and large_buffer_copy. Against Rust it is faster on 23, level on 34 and slower on 5: vector_growth, convolution, large_buffer_copy, base64_codec and indirect_calls. Fifteen more workloads are listed in the suite but not implemented yet and are not counted. These are one machine's numbers; prismio bench re-runs the suite on yours.

Known limits

  • Char is a byte, not a Unicode scalar; std.string and std.unicode give the second, character-level reading of a String.
  • A heap value is released where it is bound to a name, and most values passed straight on are released too, but some temporary shapes still leak. Each page that has one says so, and --verify reports it as leaked with no violation.
  • Diagnostics have source spans and recovery, but their codes are not yet stable.

Exact diagnostic prose, AIF report shape, source syntax, runtime ABI and package layout remain pre-1.0 and may change.