Getting Started
§0 — Pre-release Status
CSSL — Caveman Sigil Substrate Language, also called Sigil — is a single-source systems language that compiles to native x86-64 (AVX2 base, AVX-512 opportunistic) and SPIR-V (Vulkan 1.4 + Level-Zero). It is not yet a shipped production toolchain. What it is is a live, tested, rapidly advancing compiler with a complete v1 specification.
This guide is honest about what is implemented versus what is specified. Where
a feature exists in the specification but has not landed in the stage-0 compiler yet,
it is noted. The specification (25 .csl files) is complete and stable for v1.
Machine-readable versions of this guide:
§1 — Prerequisites
You need three things to build and run the CSSL compiler. GPU work adds a fourth.
Nightly channel, version pinned via rust-toolchain.toml in the repository. The build system will automatically download the pinned toolchain via rustup if you have rustup installed.
To clone the repository. Any recent Git version works (2.x+).
On Linux: gcc or clang. On Windows: MSVC build tools (or MinGW). Cargo uses this to link the final binaries.
LunarG Vulkan SDK for your platform. Includes glslc, validation layers, and spirv-tools. Required only if you are targeting SPIR-V output or running GPU compute.
Supported platforms
Primary dev targets are Linux x86-64 and Windows x86-64. macOS is supported via the Metal backend stub, but primary development and testing happens on Linux and Windows. The Cranelift stage-0 backend targets x86-64.
If you do not have Rust: visit rustup.rs and follow the instructions for your platform. The repository's rust-toolchain.toml will select the correct nightly channel automatically.
§2 — Installation
CSSL is pre-1.0. The primary installation path is building from source. This gives you the live development build with all 1,600+ tests runnable locally.
Build from source (recommended)
Clone the repository
git clone https://github.com/Apocky/CSSL3 cd CSSL3
The repository contains 25 specification files in specs/, 31 compiler crates in compiler-rs/, and examples in examples/.
Build the compiler
cargo build --release
This builds the full workspace, including csslc (the compiler driver). The rust-toolchain.toml in the repo root pins the exact nightly version — rustup will download it automatically if needed. First build takes several minutes.
Add csslc to your PATH
# Linux / macOS export PATH="$PATH:$(pwd)/target/release" # Windows (PowerShell) $env:PATH += ";$(Get-Location)\target\release"
Or invoke directly: ./target/release/csslc
Verify the build
csslc --version
You should see the compiler version string. Run the test suite to verify everything passes:
cargo test --release
Binary releases (alternative)
Pre-built binaries are available on the GitHub releases page for Linux and Windows x86-64. Because this is pre-1.0 software, builds may lag behind the tip of the development branch.
# Linux — download and install system-wide
curl -L https://github.com/Apocky/CSSL3/releases/latest/download/csslc-linux-x64 \
-o csslc
chmod +x csslc
sudo mv csslc /usr/local/bin/
§3 — Your First Program
CSSL source files use the .cssl extension. Every file that is the
entry point of a program or module begins with a module declaration.
Functions are introduced with fn. There are no semicolons; expressions
are the body.
Hello World
Create a file named hello.cssl:
module com.example.hello fn main() { print("Hello from Sigil!") }
Run it:
csslc run hello.cssl
Hello from Sigil!
The csslc run command compiles and immediately executes via the
Cranelift JIT backend — the stage-0 native code generator.
No intermediate files are written to disk for run; they are kept
in memory and executed directly.
A function with an effect row
Effects are declared after the return type with a / separator.
This function is pure on the CPU with no heap allocation — the compiler
verifies this at compile time:
module com.example.effects // A pure CPU function — no allocation, no I/O, deterministic. fn scale_gain(sample : f32, gain : f32) -> f32 / {CPU, NoAlloc, PureDet} { sample * gain } fn main() { let out = scale_gain(0.5, 0.8) print(out) }
The effect row / {CPU, NoAlloc, PureDet} is part of the function's type.
If the function body were to call an allocating function, the compiler would reject it.
Omitting the effect row means the compiler infers effects from the body.
Building a native binary
To compile to a standalone native binary instead of JIT-running:
csslc build hello.cssl # native x86-64 binary csslc build --emit spir-v hello.cssl # SPIR-V module for Vulkan ./hello # run the compiled binary
§4 — Project Structure
A CSSL project is a collection of .cssl source files. Each file begins
with a module declaration that names the module within a reverse-DNS
namespace (following the convention in the compiler's own specification).
Single-file programs
Any .cssl file with a fn main() is a runnable program.
No manifest file is required for single-file work:
module com.example.my_program use std::math::{vec3, normalize, dot} fn main() { let a = vec3(1.0, 0.0, 0.0) let b = vec3(0.0, 1.0, 0.0) let d = dot(normalize(a), normalize(b)) print(d) // 0.0 }
Multi-file projects
For larger projects, organize code across multiple .cssl files within
the same namespace. Each file is a module. A recommended layout:
The mod.cssl at the project root is the module entry point, similar
in role to lib.rs in Rust or index.ts in TypeScript.
It declares the module name and re-exports the public interface:
module com.example.my_engine pub use render::RenderGraph pub use audio::AudioCallback pub use physics::PhysicsWorld
Dual-surface syntax
CSSL has two syntactic surfaces that share one HIR (High-level IR): the Rust-hybrid surface (shown throughout this guide, familiar keywords, good for docs and onboarding) and the CSL-native surface (compressed, glyph-based, optimized for token density). Both parse to the same representation; a formatter round-trips losslessly between them. The examples in this guide use the Rust-hybrid surface.
§5 — Compilation & Running
The csslc driver is the single entry point for all compilation tasks.
Stage-0 uses Cranelift as the code generator for x86-64 output
(not LLVM — CSSL has no LLVM dependency anywhere in the pipeline). SPIR-V is
emitted via rspirv from day one.
Common commands
# JIT compile and run immediately (Cranelift backend) csslc run hello.cssl # Compile to native binary csslc build hello.cssl # Compile to SPIR-V (for Vulkan / Level-Zero) csslc build --emit spir-v hello.cssl # Show compiler diagnostics and effect-row verification csslc check hello.cssl # Print the HIR for debugging csslc dump --hir hello.cssl
The compilation pipeline
CSSL source goes through the following stages. Each stage in the stage-0 compiler is implemented; later stages are where features progressively land:
.cssl source → lex · parse → HIR // High-level IR · type-checked · effect-checked → IFC check // Information flow labels → MIR // MLIR dialect · AD source-to-source here → SMT discharge // Z3/CVC5 for refinement type obligations → LIR ↙ ↘ Cranelift → x86-64 rspirv → SPIR-V → Vulkan / Level-Zero
Cranelift is the stage-0 code generator — a deliberate throwaway bootstrapping choice. It generates correct x86-64 code quickly, which lets the compiler be developed and tested before the stage-1 bespoke x86-64 emitter is built. When stage-1 lands, it replaces Cranelift. You will not notice the difference from a user perspective. No LLVM. No MLIR dependency at runtime. No Enzyme.
Effect verification output
When the compiler verifies effect rows, it reports which obligations were discharged at compile time and which are wrapped in runtime assertions:
// audio_callback.cssl ✓ accepted effect-row: CPU ✓ compile-time NoAlloc ✓ 0 heap ops NoUnbounded ✓ loop bound: n/8 Deadline<1ms> ✓ SMT-provable PureDet ✓ bit-exact cross-machine refinement obligations: 1 sample_rate ∈ {44100, 48000, 96000, 192000} → SMT query ✓ discharged
§6 — Key Concepts
CSSL is built around five interlocking ideas. Each is a first-class part of the type system — not a library, not a runtime policy, and not opt-in. What follows is an orientation; full details are in the Sigil language reference.
Effects
Row-polymorphic effect signatures after / in the function type.
The compiler tracks what a function observes and refuses to accept functions
that violate their declared row. 28+ built-in effects covering resource
allocation, timing, hardware, power, thermal, and information flow.
Linear types & capabilities
Six reference capabilities control aliasing and mutation:
iso (isolated), trn (transition),
ref (shared-mutable via generational ref),
val (immutable-shared), box (read-only view),
tag (opaque handle). Linear values may only be resumed once
inside handlers — the compiler enforces this.
Information Flow Control
Decentralized Label Model (Jif-DLM): every SSA value carries
confidentiality and integrity labels threaded by the compiler.
The {Sensitive<dom>} effect marks sensitive domains.
Explicit declassification operators are required to move data from
sensitive to public context. Harm-enabling effect compositions are type errors.
Refinement types
LiquidHaskell-style predicates: {v : f32 | v >= 0.0} or
the shorthand tagged suffix f32'pos. Discharged by Z3/CVC5
through the {Verify<method>} effect at compile time.
Lipschitz proofs, layout refinements (@layout(std140|std430)),
and SDF shape invariants are all refinement types.
Source-to-source differentiation
Mark a function @differentiable. Use fwd_diff(fn)(args)
for forward mode or bwd_diff(fn)(args).d_x for reverse mode.
Lowered by source-to-source transformation on MIR — no LLVM-Enzyme, no
runtime tracing. Analytic gradients for SDF normals, inverse rendering,
and inverse physics are first-class.
Jet<T, N> — higher-order AD
Jet<T, N> is the dual-number type for forward-mode AD
in function composition. Useful for higher-order derivatives and
physically-based rendering algorithms that need gradient chains.
Defined in specs/17_JETS.csl.
A taste: effects + autodiff together
This example shows both an effect row and autodiff in a single function. The SDF normal is computed analytically — no finite differences:
module com.example.sdf_intro use std::math::{vec3, length, normalize} // Lipschitz-1 sphere SDF — differentiable, layout-safe. @differentiable @lipschitz(k = 1.0) fn sphere_sdf(p : vec3, r : f32'pos) -> f32 { length(p) - r } // Analytic surface normal via reverse-mode AD. // GPU, must finish in 16ms, no heap allocs. @fragment fn surface_normal(hit_pos : vec3) -> vec3 / {GPU, Deadline<16ms>, NoAlloc} { let grad = bwd_diff(sphere_sdf)(hit_pos, 0.5).d_p normalize(grad) } // bwd_diff is a compiler-level operator — no runtime overhead, // no tracing, no LLVM-Enzyme. Lowered at MIR before codegen.
§7 — Next Steps
You have the compiler built and a mental model of the language. Here is where to go next.
The compiler is written in Rust with a zero-warning, zero-clippy-lint standard.
Each commit must pass 6 CI gates: fmt, clippy,
test, doc, spec-xref, reproducibility.
See DECISIONS.md in the repository for the rationale behind key
architectural choices. Session handoffs are in handoffs/.