Skip to content

Repository files navigation

Mettle

Mettle

Mettle is a systems language where the toolchain is part of the contract.

Require what the compiler must do. Read what it did. Check that it kept your program's meaning.

Native x86-64, ARM64 and GPU code generation, with its own optimizer, linker, runtime and debugger. No LLVM, no VM, no GC.

Example

import "std/io";
import "std/mem";

@simd! fn checksum(data: uint8*, n: int64) -> int64 {
  var sum: int64 = 0;
  for i: int64 in 0..n {
    sum = sum + (int64)data[i];
  }
  return sum;
}

fn main() -> int32 {
  var n: int64 = 1024;
  var data: uint8* = malloc(n);
  for i: int64 in 0..n { data[i] = (uint8)(i & 255); }

  println("checksum = {checksum(data, n)}");
  free(data);
  return 0;
}
mettle --build --release checksum.mettle
./checksum          # on Windows, .\checksum.exe

You write the type on every var. Any expression fits between the braces of a string. And @simd! is a demand: that loop vectorizes or the build stops.

Ask what the optimizer did with it.

mettle --build --release --explain checksum.mettle
-- optimization report: checksum.mettle -------------------------------------
  1 missed optimization in this file, none with a fix the compiler can name

  checksum (loop @ line 6): vectorized -> vpsadbw, 32-wide byte sum (AVX2)
  main (loop @ line 15): NOT vectorized  [store-only-fill]
      15 | for i: int64 in 0..n { data[i] = (uint8)(i & 255); }
      \_ reason: the loop fills 1-byte elements, and the fill kernel covers 2-, 4- and 8-byte elements only
      \_ note: nothing to change here: this is a gap in the compiler, not a problem with the loop
  main (call to `println` @ line 17): inlined
  main (call to `checksum` @ line 17): inlined

It names the instruction it picked, and it names its own gaps.

Now free the buffer one line early.

  free(data);
  println("checksum = {checksum(data, n)}");
warning[M0101]: Use of `data` after it was freed (freed at line 17); this is use-after-free

   17 |    free(data);
   18 |    println("checksum = {checksum(data, n)}");
      :            ^

It found that by reading the program. You ran nothing and marked nothing.

Install

Linux:

curl -fsSL https://raw.githubusercontent.com/The-Mettle-Project/Mettle/main/install.sh | sh

Windows, in PowerShell:

irm https://raw.githubusercontent.com/The-Mettle-Project/Mettle/main/install.ps1 | iex

Both unpack to ~/.mettle or %LOCALAPPDATA%\Mettle and add that to your PATH. Neither needs root or admin.

What it does

Finds memory bugs while it compiles. It reads the whole program and reports use after free, double free, leaks, dangling returns, and pointers realloc left stale. You write no lifetimes and no ownership marks. It infers them, and it reports only what it can prove. See the memory analyser.

Checks the rest at run time, cheaply enough to ship. --safe checks every memory access at every optimization level, then proves away what it can: a constant index, a counter its loop already bounds, an index its own arithmetic bounds, one check that covers a whole loop. What survives compares against an allocation the loop resolved once, and costs a few instructions. A vectorized dot product pays nothing, a CRC 1.04x, a heapsort whose indices come out of comparisons 2.5x. See checked access.

Says what the optimizer did. --explain prints what became of every loop and every call, what stopped a loop from vectorizing, and what changed since your last build. It simulates each fix it offers before printing it, so it only prints fixes that worked. --explain-json feeds CI.

Fails the build when a promise breaks. @simd! demands that a loop vectorize, @inline! that every call site inline, @noalloc that a call graph allocate nothing. When the compiler cannot deliver, it stops and names the site that defeated it.

Vectorizes for AVX2 across reductions, maps, dot products, byte kernels, kernels over quantized integers, and some serial recurrences. It beats gcc -O3 on several kernels in the benchmark suite.

A branch that only picks a value counts as a value, so a clamp, a floor, a ReLU, a running extremum and a count of matches all vectorize, in whatever order you write the tests and whether or not you factor them into a helper. Buffers at file scope reach the same kernels as pointers passed in. --explain names the reason for every loop it leaves alone.

Offloads to NVIDIA GPUs, straight to PTX, with no nvcc and no CUDA runtime. Write kernel functions, declare them on the host, and launch them:

extern kernel(block = 256) vadd(a: float32*, b: float32*, c: float32*, n: int32);

dispatch vadd[work: n](da, db, dc, n);

It checks the arguments against the declaration, and the grid follows from the declared block. Subgroup collectives, atomics, tensor core operations, printf inside a kernel, and an occupancy report at build time all work. See GPU offload.

Runs your code while it compiles. @test functions run in the compiler and leave no binary behind. mettle trace interprets one function and prints its values line by line. --pgo runs main at build time and feeds the call counts it measured back to the optimizer.

Debugs and reports crashes on its own. Breakpoints, stepping, and reading and writing live variables over --debug-hooks, with no gdb, no PDB and no DWARF. Build with -s and a fault reports the bad address, such as a null field or a freed block.

Windows and Linux are both first-class targets. One source tree builds them and one test suite gates them. Each owns its runtime: Windows links its own PE images, Linux emits ELF and reaches the kernel through direct system calls, so neither carries a libc. Windows has std/ui for windows and controls; Linux does not. See what is missing.

Documentation

The rendered docs live at https://suidvandiewereld.github.io/Mettle/.

Build from source

This repository holds the whole toolchain under one src/: the language and its frontend, and libmtlc, which is the IR, the optimizers, code generation and native linking. There is nothing to fetch. The build runs offline.

Windows, with gcc or clang:

.\build.bat
.\tests\run_tests.ps1

Linux:

make -j"$(nproc)"
make check

make check runs the same tests/run_tests.ps1 the Windows build gates on, so a test written on either platform runs on both. It needs PowerShell Core. Without it, bash tools/test-elf-native.sh still covers the owned-ELF product on its own.

To build the backend alone, the archive another frontend links against:

.\build.bat --backend-only

See Mettle and libmtlc for the line between the frontend and the backend.

Samples live in examples/. The benchmark suites pair Mettle against C:

.\tools\benchmark\run-benchmarks.ps1

The editor extensions live in MettleMisc: mettle-syntax for VS Code and Cursor, clion-plugin for the IntelliJ family.

License

Apache 2.0. See LICENSE.

About

Mettle is a compiled, statically typed systems programming language.

Topics

Resources

Contributing

Stars

26 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages