Skip to content

Why Sigil Exists

Every programming language is a theory about what classes of bugs matter enough to prevent at compile time. Sigil’s theory is specific: Level-of-Abstraction (LoA) violations are the dominant failure mode in GPU-adjacent systems code, and they are preventable.

Before the current compiler, there was Sigil v1 — a transpiler that took .si source files and emitted Odin + WGSL. It was used in the Layers of Abstraction (LoA) game engine, specifically versions V11 and V12.

The transpiler worked. Programs compiled. But a class of bugs persisted that the transpiler couldn’t catch: bugs where code that was written for one level of abstraction was accidentally used at another.

Concretely: a shader written to operate in world space gets called with coordinates in screen space. Or a CPU-side math function gets called from GPU code through a shared header, silently compiling but producing nonsense because the numeric representations differ. Or a gradient computation is written for a function in normalized form but called on an unnormalized input.

In C++/HLSL/WGSL these are runtime failures: wrong pixels, NaN explosions, visual glitches that only appear on specific geometry or at specific viewing angles. The compiler sees no issue because the types are all float. The coordinate space isn’t a type — it’s a comment. Comments don’t stop bugs.

Sigil treats coordinate spaces, GPU/CPU distinction, and mathematical invariants as types, not conventions. The LoA violation that took a day to debug in LoA V11 becomes a compile error in Sigil:

// Distinct types — not aliases, not newtypes with casts, actual distinct types
struct WorldPos { inner: Vec3 }
struct ScreenPos { inner: Vec2 }
struct ClipPos { inner: Vec4 }
// The shader accepts ClipPos, not Vec4.
// Passing a WorldPos is a compile error — not a type cast, a compile error.
#[shader(vertex)]
fn vs_main(world: WorldPos) -> ClipPos !gpu { /* ... */ }

This is the type system as invariant enforcer — not via runtime assertions, not via naming conventions, but via the type checker rejecting invalid programs at compile time.

Sigil’s design was shaped by six constraints that emerged from the LoA failure analysis. These are not features that were added because they seemed nice — they are requirements that fell out of the problem:

IDRequirementRationale
F1GPU/CPU distinction must be a type-level factCross-boundary calls are undefined behavior; the type checker must catch them
F2Effect system must be non-optionalA function’s effects are part of its contract; opt-in effect tracking is ignored under pressure
F3Autodiff must be compiler-native, not libraryLibrary-based AD leaks into the type system and API surface; correctness requires the compiler to own it
F4Coordinate spaces must be distinct typesComments and naming conventions fail under refactoring; only the type checker is reliable
F5Zero-cost verificationVerification that adds runtime overhead will be disabled in production; must be compile-time or zero-cost at runtime
F6No LLVM dependencyLLVM’s compilation model conflicts with the JIT/hot-reload use cases central to graphics work

Every major design decision in Sigil can be traced back to one or more of F1–F6. When a proposed feature would require violating one of these, the feature is redesigned, not the non-negotiable.

F1+F4 together mean that the type system is richer than most systems languages. There’s more ceremony when you define a new data type. This is intentional — the ceremony is the safety.

F2 means you can’t write a function and ignore its effects. The effect row is inferred automatically in most cases, but you can’t call an !io function from a pure context without the compiler telling you so. This feels restrictive until the first time it catches a GPU shader accidentally triggering CPU-side logging.

F3 means autodiff is not an afterthought. The operator is part of the language, as first-class as +. This matters for anything involving optimization, simulation, or differentiable rendering.

F5 means verification annotations are cheap enough to leave on in production. The SMT solver runs at compile time. The generated binary has no verification overhead unless you explicitly opt into runtime assertions.

F6 means the compiler is self-contained, fast to build, and can be embedded in tools that need JIT compilation without pulling in LLVM’s 1GB+ dependency chain.

Sigil is not trying to be a safer Rust. Rust already solves memory safety. Sigil’s target domain is the class of programs that Rust doesn’t help with: GPU pipelines, differentiable systems, real-time audio, physics — places where the bugs aren’t use-after-free but are coordinate-space confusion, effect leakage, and incorrect gradient computations.

Sigil and Rust are complementary. The Sigil compiler is itself written in Rust. A typical project might use Rust for the engine host layer and Sigil for the GPU-facing compute kernel layer.

Hello Triangle — the simplest Sigil program, annotated line by line