−18° astronomical twilight

type · match · effect · !io

Dawn

A small, elegant functional language: immutable data, algebraic data types with exhaustive matching, effects in the type signature.

main.dawnOpen in playground ↗
type Shape =
  | Circle(r: Float)
  | Rect(w: Float, h: Float)

fn area(s: Shape) -> Float =
  match s {
    Circle(r) -> 3.14159 * r * r
    Rect(w, h) -> w * h
  }

pub fn main() -> Unit !io =
  [Circle(1.0), Rect(2.0, 3.0)]
  |> map(area)
  |> fold(0.0, (a, x) => a + x)
  |> t => println("total: ${t}")
dawn run main.dawn
total: 9.14159

−12° nautical twilight · three ideas

What the signature tells you.

What a function touches, computes ahead of time and matches on, checked by the compiler.

01 · effect

Effects in the type

Pure by default: touching IO needs !io, and effects you declare are answered by with handle.

effect Ask {
  fn ask() -> Int
}

fn total() -> Int !Ask =
  ask() + ask()

pub fn main() -> Unit !io = {
  with handle Ask { ask() => 21 }
  println("${total()}")
}
42
02 · comptime

Compile-time evaluation

comptime { ... } runs an ordinary function at compile time and burns the result into the constant pool.

fn fib(n: Int) -> Int =
  if n < 2 { n }
  else { fib(n - 1) + fib(n - 2) }

const FIB10: Int =
  comptime { fib(10) }

pub fn main() -> Unit !io =
  println("fib(10) = ${FIB10}")
fib(10) = 55
03 · match

Data and exhaustive matching

Immutable algebraic data, and a match that misses a case does not compile.

type Shape =
  | Circle(r: Float)
  | Rect(w: Float, h: Float)
  | Point

fn describe(s: Shape) -> String =
  match s {
    Circle(r) -> "circle ${r}"
    Rect(w, h) -> "rect ${w} x ${h}"
    Point -> "a point"
  }

pub fn main() -> Unit !io = {
  let xs = [Circle(1.0), Point]
  for s in xs {
    println(describe(s))
  }
}
circle 1.0 a point

−6° civil twilight · two backends

Two roads, one answer.

One source, two backends, one answer main.dawn is compiled to JVM bytecode and, separately, to C handed on to cc. Both programs run, and their stdout, stderr and exit code must be byte-identical; a difference is a red build. main.dawn JVM bytecode runs on JDK 21 C → cc a native binary out err exit out err exit stdout · stderr · exit code, byte for byte a difference is a red build

JVM bytecode and C are peer backends. On every push the differential corpus runs on both, and any difference in output fails the build.

176programs in the differential corpus, each run on both backends
every pusha difference in stdout, stderr or exit code is a red build
116,129lines of Dawn in the self-hosted compiler

−3° first light · the GPU

A kernel is a Dawn function too.

A cuTile kernel is an ordinary Dawn function under a named effect, lowered to CUDA Tile IR. A pure fake device gives the same answer with no GPU.

102/105 public Tile IR opcodes implemented, each run on a GPU against a host reference. The cuTile page has the coverage tables, the gates and the device ledgers →

device
  1. vadda Dawn fn, !Dev
  2. recordthe ops it raises
  3. Tile IRtext or bytecode
  4. tileirasassembles a cubin
host
  1. runa Dawn fn, !Gpu
  2. launch("vadd")answered by a handler
  3. with_gpu_realthe CUDA driver, native onlywith_gpu_fakethe host reference, pure
fn vadd(a: Param[Float], b: Param[Float], out: Param[Float]) -> Unit !Dev = {
  let ta = load_cell(a)
  let tb = load_cell(b)
  store_cell(out, add(ta, tb))
}
the add, in the Tile IR dawn run prints for it%19 = addf %12, %17 rounding<nearest_even> : tile<128xf64>
device · !Dev

The kernel

Running it records the operations it raises; the record becomes Tile IR, and tileiras assembles a cubin.

  • Checked on real hardwaretile-gpu-diff holds each kernel against a host reference; CI reds what its ledger does not cover.
fn run(xs: List[Float], ys: List[Float]) -> Result[List[Float], ForeignError] !Gpu = {
  let n = len(xs)
  let a = alloc(F64, n)?
  let b = alloc(F64, n)?
  let out = alloc(F64, n)?
  copy_from_host(a, xs)?
  copy_from_host(b, ys)?
  launch("vadd", blocks(n), [erase(buffer_arg(a)), erase(buffer_arg(b)), erase(buffer_arg(out))])?
  sync()?
  let got = copy_to_host(out)?
  free(a)?
  free(b)?
  free(out)?
  Ok(got)
}
what the same dawn run prints for it, under with_gpu_fakevadd on the fake device: [11.0, 22.0, 33.0, 44.0]
host · !Gpu

The host program

with_gpu_real answers from the CUDA driver, with_gpu_fake from host memory.

  • Native talks to the GPUOnly native reaches libcuda; on the JVM a real launch is refused.
  • The fake device is pureSo a !Gpu program runs in its tests, on either backend, with no GPU.

0° sunrise · install

Pick a road and run it.

Two toolchains on every release, each with its SHA-256.

Without a JVM

dawnc-linux-x86_64

  • static
  • C backend
  • std inside
base=https://github.com/dawnop/dawn-lang/releases/latest/download
curl -fsSLO $base/dawnc-linux-x86_64
curl -fsSLO $base/dawnc-linux-x86_64.sha256
sha256sum -c dawnc-linux-x86_64.sha256
chmod +x dawnc-linux-x86_64
sudo mv dawnc-linux-x86_64 /usr/local/bin/dawnc

printf 'pub fn main() -> Unit !io = println("hello, dawn")\n' \
  > hello.dawn
dawnc run hello.dawn
hello, dawn

With JDK 21

dawn-selfhost.jar

  • any platform
  • JVM backend
  • std inside
base=https://github.com/dawnop/dawn-lang/releases/latest/download
curl -fsSLO $base/dawn-selfhost.jar
curl -fsSLO $base/dawn-selfhost.jar.sha256
# macOS: shasum -a 256 -c
sha256sum -c dawn-selfhost.jar.sha256

printf 'pub fn main() -> Unit !io = println("hello, dawn")\n' \
  > hello.dawn
java -jar dawn-selfhost.jar run hello.dawn
hello, dawn

Latest release · Tutorial, chapter 1 · Specification · Examples