Skip to content

Mettle v0.16.3

Choose a tag to compare

@suidvandiewereld suidvandiewereld released this 19 Aug 03:45
· 408 commits to main since this release

Mettle v0.16.3

28 commits since v0.16.2. The language surface is unchanged, so existing code compiles as before. This release is backend work: the loop recognizers gained a shared foundation and a rot gate, the register-allocated backend picked up most of what it previously bailed on, the compile-time interpreter grew a full memory and call model, and three correctness fixes landed.

Correctness

Byte vectorizer sign extension (release-only miscompile). A byte load's lane value is the zero-extended byte whatever the element's declared signedness, because every scalar backend loads bytes with movzx. Honoring the declared signedness led the kernel to select vpmovsxbd for int8 arrays, so a release build of if ((int32)a[i] > 100) saw -106 where a debug build saw 150. Fixed at both decode sites and covered by a regression test.

mingw gcc 16 dropped emulated TLS. MTLC_THREAD_LOCAL now degrades to a plain static where the toolchain no longer supplies emutls, so the compiler builds on gcc 16.

Fingerprint snapshot lifetime. The snapshot owns its label strings, and stddef.h is included where size_t is used.

Loop recognizers

The recognizer pipeline was split into canonical form, a worklist driver, and a tail stage, which replaced repeated full sweeps. Supporting that:

  • One strength-reduction table is now shared between the x86 and ARM64 backends. The table is proven against its own test, and the duplicate copy of the math in the backend was deleted.
  • Loop recognizers share an affine model. The arithmetic was hardened against overflow, the facts are computed lazily, and the third recognizer sweep is gone.
  • Canonical form is checked structurally, so a loop that merely stopped changing no longer passes as canonical.
  • A claim baseline (tests/loop_claims.baseline) fails the build when a kernel a recognizer used to claim goes unclaimed. Silent kernel rot was previously invisible. Regenerate with tools/regen-loop-claims.ps1.
  • A loop fingerprint diagnostic and an e-class pilot landed. The pilot was trimmed to what it actually measured, and the measurement is recorded alongside it.

Register-allocated backend

Functions that previously fell back to the older path now go through the allocated backend:

  • Heap allocation, global aggregates reached by name, and the byte-offset fill walk are lowered natively.
  • Global dirtiness is tracked flow-sensitively, so a function that both writes a global and makes a call can still be allocated.
  • MIR carries strings as the aggregates they are, and the string value convention is honored through loads, stores, and call arguments.
  • Float stack parameters are homed, float stack arguments are passed, integer literals passed to float parameters are folded at the call site, and any scalar coerces into a float argument.
  • Indirect calls are typed through temps.
  • rotate_add is lowered natively, popcnt is lowered, and vectorized reductions, scalar-reading vloops, and four more audited kernels cross the kernel bridge.
  • Nonzero fill starts fold into the kernel base and count.

Compile-time interpreter

ir_interp now models the whole memory and call model, and string literals have real backing memory. This widens what --verify and --pgo can evaluate at compile time instead of bailing.

Full Changelog: v0.16.2...v0.16.3