Skip to content

Mettle v0.14.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 13:19
· 713 commits to main since this release

Mettle stops carrying its own backend. It compiles against libmtlc, and this
release names the exact backend commit it uses.

BREAKING CHANGE

Building from source now needs libmtlc.
Fetch it once, then build as before:

./get-libmtlc.sh     # Windows: .\get-libmtlc.ps1
make                 # Windows: .\build.bat

Installing a release needs nothing extra. The backend is already in the binary.

The backend source has left this repository: src/codegen, src/linker,
src/compiler, src/debug, and the IR core with its optimizer. Send patches
for any of those to libmtlc.

Where each half lives

  • This repository holds the language. The lexer, the parser, the type checker,
    the memory safety analysis, IR lowering, the driver, the runtime, the
    standard library.
  • libmtlc holds the IR, the optimizers, code generation for x86-64, ARM64, PTX
    and SPIR-V, and PE and ELF linking.
  • libmtlc.version names one commit, so you can rebuild the compiler that
    shipped. Set LIBMTLC_DIR to build against a checkout of your own.
  • docs/mettle-and-libmtlc.md covers the split, the
    include paths, and how to work on both halves at once.

Fixed: two silent miscompiles

The differential fuzzer found both. --verify missed both, and called the
affected program clean.

  • A shared scaled index addressed the wrong element. a[i] * b[i] computes
    i << 2 twice, and once the optimizer folds the two into one value, two
    address computations read it. The x86 address fold wanted that value to have
    a single reader, so the pair fell through to a fallback that folded the
    already-scaled value in as a unit index. Release builds on the register
    allocating backend read the wrong element. The fold now handles a shared
    index and keeps the shift for whoever else needs it, which is also one
    instruction fewer than before.
  • A spilled base and a spilled index could land in one register. The scaled
    load and address encoders staged each spilled operand through a scratch
    register, and for one combination of allocations they picked the same one, so
    [base + index*4] encoded as [r11 + r11*4]. Each staging register is now
    chosen clear of the ones already in use.

Faster compiles

  • Profiling on 200k-line inputs found the compiler in linear strcmp scans, a
    getenv per pass event, and heap churn. Hashed lookups, lazy teardown and a
    leaner inliner take a 226k-line build from 2244ms to 1082ms, and peak memory
    from 892MB to 508MB.
  • --verify costs 2 to 4 times a plain compile, down from 8.5. The interpreter
    no longer zeroes 570KB per machine, and the pass driver reuses its snapshot
    when a pass changed nothing. ui_demo.mettle drops from 6.5s to 0.36s.

The ML optimizer now proves its work

  • --ml-opt runs every model decision through the reference interpreter before
    it stands. On a divergence it rolls back, then re-applies one decision at a
    time, so the bad one is named with a counterexample and dropped while the
    rest hold. No --verify needed.
  • That makes --ml-opt-speculative usable. The model's dead-code deletions
    carry no proof, so they stand on the validator's word alone.
  • Across the 49 benchmark programs: 1,190 proposals, 348 applied, 537 rejected
    with counterexamples. Every binary matched its baseline at run time.
  • Writing the speculative action found four holes in the validator. This closes
    all four. Each had been blinding --verify too.

--explain-json: stable ids and four new sections

  • Every decision carries a short id beside its prose. Wordings improve;
    int32-sum-narrow-acc does not. Tools should read the ids.
  • A per-function table of instruction counts before and after.
  • A pass ledger that says what each pass did and which lines it touched. The
    vectorizer's row reports both the SIMD kernel it added and the scalar work it
    retired.
  • Per-loop cycles per iteration and the port each loop bottlenecks on. The
    optimizer knows which loops it refused to vectorize; only code generation
    knows what those loops then cost.
  • A call graph and a hotspot ranking.
  • docs/explain-json.md documents the schema.

Mettle for CLion (new)

A JetBrains plugin in tools/clion-plugin. It loads in CLion and across the
IntelliJ family.

  • Diagnostics come from the compiler, so the editor shows the same codes and
    help lines as the command line.
  • The debugger drives --debug-hooks: breakpoints, stepping, the call stack,
    and variables you can edit. Windows only for now.
  • An optimization report window reads --explain-json and applies the fixes the
    compiler already checked.
  • Lexer, parser, completion, navigation and rename cover the language as the
    compiler parses it, down to optional end of line semicolons.

Other

  • The benchmark harness builds its C baselines with clang under -Clang.
  • Every CI job fetches the pinned backend and caches it. The sanitizer job
    instruments the backend too, which is most of the compiler.

What's Changed

Full Changelog: v0.13.0...v0.14.0