Mettle v0.11.0
v0.10.0 shipped the optimizer as a little helper. v0.11.0 does the same for safety: the compiler now catches use-after-free, dangling returns, loop off-by-ones, and null dereferences before the binary exists, explains what went wrong and where, and when it can't catch something statically, the runtime names the faulting address instead of printing an anonymous pointer.
The vectorizer also grew up: runtime scalar broadcasts, integer lanes, and a confirmed miscompile class found and fixed across the entire recognizer suite.
Compiler: memory safety diagnostics
Hard build errors
- Returning the address of a stack local, directly or through an alias
- Constant-size memory ops that exceed the destination array's capacity
- Constant array index out of bounds, including through pointer aliases (
var p = &a[2]; p[6]is a build error naminga[8]) - Loop off-by-one:
j <= 8over an int32[8] fails with the exact comparison to change - Divide-by-zero and over-width shift, with honest hardware explanations (
x << 32on int32 producesx; the hardware masks the count)
Warnings with fixes
- Use-after-free and double-free
- Stack address stored into a global
- Allocation that never escapes and is never freed (suggests
defer free(p)at the allocation site) - Null dereference through an unguarded pointer (any conditional on the pointer ends the claim)
Whole-program ownership inference
The compiler flattens imports into one program for codegen. It now uses the same call graph to compute per-function ownership summaries bottom-up: does this function free its parameter, store it, return a fresh allocation? The analysis re-runs with those summaries to catch UAF and leaks across call boundaries, through helper functions, and through wrapper allocators like make() -> malloc. Zero annotations required. Only definite summaries propagate; the zero-false-positive budget holds.
Runtime: access violation forensics
0x10 is no longer anonymous. With -s, a fault now explains the address:
Attempted to write inaccessible memory at 0x0000000000000010
This address is null plus offset 16: a field or array access through a null pointer
--> tests/debug_crash.mettle:13:14 in main
- Null-plus-offset: prints the field offset
- Stack region: dangling-frame note
- Freed heap: names the block, its size, and that it was already freed
--native-heap is now a UAF detector. Freed page-backed blocks keep their mapping with all access revoked and sit in a quarantine ring. Freed small blocks are poisoned 0xDD. A dangling read or write faults immediately with classification text, at the point of access, not three functions away where the corruption surfaces.
The static analyzer and the runtime fault handler compound: a UAF that escapes compile-time analysis faults instantly at runtime with the block named. Linux signal path covered.
Vectorizer: correctness and generalization
Confirmed miscompile fixed
Loops with a nonzero induction variable start (j = 3; while (j < n)) were silently replayed as 0..n by SIMD recognizers. Confirmed live repro returning 100 instead of 97. Fixed via a shared ir_iv_zero_at_header guard, one implementation, no duplicates. Eight recognizers had the same hole and all received the fix: sum_i32, dot_i32, dot_i8, exp_f32, memcmp byte-loop, clamp_i32, reverse-copy, both SLP MAC matchers, i2f, outer-lane vectorizer, and all pointer-walk matchers.
VLOOP generalized
- Runtime scalar broadcasts: any loop-invariant scalar symbol (param or local) now vectorizes as a broadcast.
out[i] = (a[i]*s1 + b[i])*s2with runtime coefficients vectorizes at 9.8x over scalar. - Integer lanes: new
IR_OP_SIMD_VLOOP_I32opcode covers int32/uint32 unit-stride maps and+-reductions over+ - * & | ^ <<literalDAGs, 8 lanes per iteration. Results are bit-exact against scalar. Int maps 3.9x, int reductions 5.8x.
uint32 wrap semantics
Unsigned sub-64-bit arithmetic now wraps mod 2^width consistently in all four modes (encoder, MIR, fallback backend, return canonicalization). Root cause was binary_emit_mov_reg_reg32 silently no-oping on dst==src, eating every uint32 zero-extension. Fixed with a dedicated binary_emit_movzx_reg_reg32 and three-layer canonicalization.
VS Code extension
- Full Debug Adapter Protocol: F5, conditional breakpoints, live variable editing, crash reporting via named-pipe debug runtime
- Optimization report panel: regression detection, verified fix application, click-to-source
@inline!and@noalloccontracts surface in the panel- Complete language service: go-to-definition, references, rename, outline, completion, signature help, inlay hints, CodeLens, hovers, compiler-backed diagnostics
- All memory diagnostic warnings and errors surface in the VS Code Problems panel with fix suggestions attached
Other
- DCE pass: dead code elimination in IR, improved compile speeds
- IR optimizer speedups: hash value maps, dead copy-propagation pruning,
getenvcaching type_checker.candir_lowering.csplit into focused modules- Bounded self-recursion inlining
- Site-aware inliner with repeated-refusal folding
- Linux build fix: explicit
-pthread -ldlfor glibc < 2.34 - 416/416 tests, 400/400 differential-fuzz seeds clean
What's Changed
- Add source-level debugger and interactive --explain optimization report panel with verified fixes by @marquisburg in #41
- Memory safety diagnostics, access violation forensics, and vectorizer correctness by @marquisburg in #42
Full Changelog: v0.10.0...v0.11.0