Wyst

An ARM64 language and compiler for making low-level behavior explicit and inspectable.

Conformance tests, byte-identical kernel builds, fuzzing, and QEMU fixtures check the work. I own the language and compiler decisions; AI assists implementation.

My day job is building web interfaces; Wyst scratches my low-level programming itch and puts my CS degree to use.

ARM64-only pre-1.0 Rust bootstrap compiler not memory-safe pronounced “wist,” an old word meaning “to know”

A tiny UART program.

main.wyst · excerpt
source ↗
/*
* QEMU `virt` is a generic virtual Arm board.
* Its PL011 UART provides serial text output.
*
* UART registers are memory-mapped I/O (MMIO):
* reads and writes at these addresses reach a
* device rather than ordinary RAM.
*
* A register address is UART0's base plus its
* PL011 byte offset. QEMU maps UART0's base at
* 0x0900_0000.
*/
register_map Pl011 {
  // DR is the data register.
  // DATA is its eight-bit transmit payload.
  DR: writeonly u32 at 0x00 {
    DATA: u8 at 0..=7
  }

  // FR is the flag register.
  // TXFF means the transmit FIFO is full.
  FR: readonly u32 at 0x18 {
    TXFF: bool at 5
  }
}

mmio UART0: Pl011 at 0x0900_0000

/*
* There is no process runtime to call `main`. The
* layout file, similar to a linker script, names
* `_start` as the entry point. QEMU jumps here after
* loading the kernel image.
*
* QEMU passes a device-tree blob (DTB) address in Arm
* register `x0`; it describes the virtual hardware.
* `naked` omits the usual function prologue because
* no valid stack exists yet. `never` records that
* there is no caller to return to.
*/
// Put the entry address on a 16-byte boundary.
#[align(16)]
pub naked fn _start(dtb: @u8 in x0) -> never {
  // __stack_top is a linker-style symbol from the layout.
  // Set the CPU stack pointer to that reserved RAM.
  asm establishes stack (
    stack: u64 in x1 = __stack_top,
  ) {
    mov sp, stack
  }

  // The stack is ready, so ordinary function calls are safe.
  kernel_main(dtb)
}

fn uart_write(byte: u8) {
  // Poll for space in the transmit FIFO.
  while UART0.FR.read().TXFF {
    cpu.nop()
  }

  UART0.DR.write(DATA = byte)
}

fn uart_hello() {
  // UART output is byte-oriented.
  const msg: [6]u8 = ['h', 'e', 'l', 'l', 'o', '\n']

  // #len is compile-time metadata;
  // the array has no runtime length field.
  for i in 0 ..< #len(msg) {
    uart_write(msg[i])
  }
}

fn kernel_main(dtb: @u8) -> never {
  // Production code would parse the DTB.
  // This fixture only checks that the handoff supplied it.
  if dtb == address<@u8>(0) {
    test_exit(0x63)
  }
  uart_hello()

  // Bare metal has no caller to return to. `test_exit`
  // plays the role of process exit for this QEMU test:
  // report status 0, then stop the virtual machine.
  test_exit(0)
}
scroll for more ↓
qemu / virt
hello