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.
The predecessor: Sigil v1
Section titled “The predecessor: Sigil v1”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.
What an LoA violation looks like
Section titled “What an LoA violation looks like”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.
The design response
Section titled “The design response”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 typesstruct 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.
The six non-negotiables
Section titled “The six non-negotiables”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:
| ID | Requirement | Rationale |
|---|---|---|
| F1 | GPU/CPU distinction must be a type-level fact | Cross-boundary calls are undefined behavior; the type checker must catch them |
| F2 | Effect system must be non-optional | A function’s effects are part of its contract; opt-in effect tracking is ignored under pressure |
| F3 | Autodiff must be compiler-native, not library | Library-based AD leaks into the type system and API surface; correctness requires the compiler to own it |
| F4 | Coordinate spaces must be distinct types | Comments and naming conventions fail under refactoring; only the type checker is reliable |
| F5 | Zero-cost verification | Verification that adds runtime overhead will be disabled in production; must be compile-time or zero-cost at runtime |
| F6 | No LLVM dependency | LLVM’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.
What this means in practice
Section titled “What this means in practice”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.
What Sigil is not trying to fix
Section titled “What Sigil is not trying to fix”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.
Next step
Section titled “Next step”→ Hello Triangle — the simplest Sigil program, annotated line by line