Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Wyn is a functional, array-centric language for GPU programming. Function values are erased before execution, so programs run on GPU targets that have no first-class function pointers. The array surface provides one array kind for external inputs, intermediate values, and results.

Design Goals

  • GPU-targeted: language constraints (regular arrays, no recursion, no first-class function values) keep programs compatible with massively parallel hardware.
  • Array-oriented: first-class support for multi-dimensional arrays and array operations.
  • Type-safe: static type checking with type inference.
  • Functional: immutable data structures and expression-based computation.
  • Modular: encapsulation and reuse through first-class modules — signatures hide implementation details, and parameterized modules generalize components across types.

Key Features

  • Second-order array operators: map, reduce, scan, filter, and related combinators are the primary way to express bulk operations on arrays.
  • Uniqueness types: *T marks a value as consuming-only, letting arr with [i] = x mutate in place when the source is unique and copy when it is not.
  • Size-typed arrays: array lengths participate in the type system; def f(xs: [n]i32) [n]i32 declares a function whose output has the same length as its input.
  • Host-visible entries: a host invokes an entry, whose body uses the same values, functions, and array operators as the rest of the language.

Program Structure

Where other shader languages iterate, Wyn transforms.

Arrays are the primary data structure, aligning with the regular, data-parallel organization of modern GPU hardware. Operators such as map, reduce, scan, and filter consume and produce arrays, while function values specialize each operator invocation by capturing values from the surrounding scope.

Most non-trivial programs are compositions of these operators. A program describes transformations of values rather than a sequence of storage operations. Parallel evaluation is permitted only where the relevant language construct permits it.

A Wyn program is a sequence of declarations. The smallest interesting program is a single host-visible entry:

entry double(arr: []f32) []f32 = map(|x| x * 2.0, arr)

entry marks a root operation visible to the host. Anything that is not a root entry is an ordinary function or value, defined with def:

def gravity: f32 = 9.81

def step(dt: f32, v: f32) f32 = v + gravity * dt

Functions are first-class within the program — they can be passed to higher-order operators like map and reduce — but function values are erased before execution and do not exist at runtime.

Arrays are the primary aggregate. Sizes participate in the type system, so a function that takes an [n]f32 returns an array whose length is bound to that same n:

def normalize(xs: [n]f32) [n]f32 =
  let total = reduce(|a, b| a + b, 0.0, xs) in
  map(|x| x / total, xs)

A Wyn program can span multiple files. Each file is implicitly a module: declarations at file scope are members of that module. Files reference each other with import, which loads a sibling file and binds its declarations under the imported name; open brings a module’s members into the current scope unqualified. Modules can also be defined inline with module m = { ... }. Module types describe a module’s interface; parameterized modules take other modules as arguments.

A small standard library is automatically loaded. Top-level declarations from its files — including the second-order array operators (map, reduce, scan, filter, …) — are available unqualified throughout the program. The standard library also defines per-type modules (i32, f32, bool, …) that group operations on their type; programs can open such a module to bring its members into scope or address them by qualified name (f32.sqrt, i32.abs).