Skip to content

Blaise v0.14.0

Latest

Choose a tag to compare

@graemeg graemeg released this 06 Aug 13:13

Blaise v0.14.0 — Changelog

Range: v0.13.0..v0.14.0 (415 commits). Fixpoint verified on 637,968 lines
of QBE IR; all four self-hosting fixpoints green (QBE, native,
internal-assembler, warm-cache). Full test suite: 4,977 tests passing,
2 ignored.

macOS ARM64 backend (new this cycle)

A complete native ARM64/macOS backend was built from scratch this cycle —
still in progress, but already self-hosting a growing slice of the
compiler's own source and running on real Apple Silicon hardware.

  • Native AArch64 code generator implementing the AAPCS64 calling convention
    for records, classes, interfaces, exceptions, and TLV thread-locals.
  • Mach-O MH_OBJECT and MH_EXECUTE writers behind the IContainerWriter
    seam, including LC_UUID, dyld load commands, and ad-hoc code signing.
  • A standalone internal AArch64 assembler emitting Mach-O objects directly
    — no dependency on Apple's as/ld toolchain for object code.
  • macos-arm64 registered as a full target in the toolkit registry; a
    macOS-built compiler correctly reports macos-arm64 as its own host.
  • 40+ "self-cross-compile leg" commits incrementally teaching the arm64
    backend enough of the compiler's own feature surface (generics,
    interfaces, jumbo sets, closures, unit interfaces embedded in Mach-O
    objects) to compile itself.
  • On-device bring-up: hello-world, class-based programs, and the compiler's
    own TestRunner run on Apple Silicon; the majority of the suite reaches a
    clean summary with zero crashes.
  • A dozens-strong cluster of layout-sensitive ARC/width fixes surfaced by
    the self-cross-compile push — canonical adrp+ldr TLV access, descriptor-
    driven interface tables (abstract/cross-unit/parent-chain), narrow-int
    width handling, and chained field/record access.
  • Remaining work: OPDF/debugger support on arm64, broader e2e coverage.
    Threads are a known pdr gap on this target (PTRACE_O_TRACECLONE).

Tooling: BlaiseGuard (new)

A static analyser purpose-built for the Blaise dialect, reusing the
compiler's own lexer/parser/AST so it always tracks the grammar the
compiler currently accepts.

  • Rule families: BL-1002/BL-1004 (unused identifiers), BL-2001BL-2003
    (reference cycles, string indexing), BL-2005, BL-3001/BL-3002
    (duplicate detection), plus five rules covering defects the compiler
    itself accepts silently.
  • JSON/XML/HTML report formatters.
  • Project-level config discovery and inline suppression comments — cut
    BL-2003's false-positive rate by 94% this cycle.
  • Documented rule-ID scheme (docs/BlaiseGuard README).
  • Not bundled in the release tarball — build from source:
    pasbuild compile -m blaise-guard --compiler <blaise-binary>.

Language

  • Operator overloading (class operator) — parse, resolve, and lowering
    landed end-to-end.
  • Compile-time file embedding{$EMBED 'path'} (byte array) and
    {$EMBEDSTR 'path'} (string literal).
  • varargs directive for binding C-variadic external functions.
  • Subranges of enumerated types (TWeekday = Mon..Fri) — fixes GH #182 and
    three follow-ups (array bounds/indexing over an enum subrange).
  • Boolean accepted as an array index type; array[Boolean] accepted in an
    inline typed constant (GH #208).
  • High/Low of a subrange type return the subrange's own bounds, not the
    base enum's (GH #160).
  • Bit operators allowed in float constant expressions (GH #195).
  • string/dynamic-array class static variables allowed.
  • Subscript-terminated record chain accepted as an l-value (GH #187).
  • A setter-backed property write is now reachable through a chained base
    expression.
  • div/mod signedness now follows the expression's result type rather
    than its operands, on both backends (GH #196).

Examples

{ Operator overloading }
type
  TVec2 = record
    X, Y: Double;
    class operator Add(const A, B: TVec2): TVec2;
  end;

class operator TVec2.Add(const A, B: TVec2): TVec2;
begin
  Result.X := A.X + B.X;
  Result.Y := A.Y + B.Y;
end;

{ Compile-time embedding }
const
  Greeting: string = {$EMBEDSTR 'greeting.txt'};

{ Enum subranges }
type
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  TWeekday = Mon..Fri;
var
  D: TWeekday;
begin
  for D := Low(TWeekday) to High(TWeekday) do
    WriteLn(Ord(D));
end.

ARC / runtime

  • Static-array-of-managed-field ARC: scope-exit release and record/class
    field walks for arrays of managed elements (BUG-017).
  • Interface pointee reads/writes and sret-Result reads through a pointer
    fixed across pointer-read, pointer-write, and interface shapes (BUG-012).
  • An aliased sret assignment is now staged through a temporary instead of
    zeroing a destination that aliases a call argument.
  • Managed unit globals are released via a per-unit <Unit>_fini at program
    exit; a program-level reference to global's captured environment is
    released too.
  • Two new diagnostic allocator modes: -dBLAISE_MEMPOISON (poison +
    quarantine) and -dBLAISE_MEMDEBUG (double-free detection) in the
    small-block allocator.
  • Every valgrind e2e test now asserts zero ARC leaks under --debug.

runtime.math — pure-Pascal libm (new this cycle)

Blaise no longer links libm at all. The whole of the transcendental math
Blaise uses is now a from-scratch Pascal port of musl libc's fdlibm-derived
math sources (MIT-licensed), living in runtime.math.pas and lowered to
directly from all three backends:

  • sin/cos/tan with full Payne–Hanek argument reduction, asin/acos/
    atan/atan2, exp/expm1/ln/log2/log10/pow, sinh/cosh/
    tanh, a correctly-rounded bit-by-bit sqrt, and floor/ceil/round
    (half-away-from-zero, matching Blaise's documented Round rule).
  • Double-precision kernels only; each backend widens Single arguments and
    narrows results at the call site.
  • Accuracy pinned at ~1 ulp by runtime/src/test/pascal/test_blaise_math.pas
    — roughly 190 glibc reference vectors checked at 2-ulp tolerance, with
    exactness classes verified bit-exact.
  • All three backends (QBE, native x86-64, native arm64) now lower the
    float-math builtins to these RTL calls instead of external libm symbols;
    arm64 gains the whole float-builtin family in the same change (previously
    unlowered), and a float-typed Abs arm was added to native x86-64 that
    had never existed (Abs(-2.5) previously returned -2.5 unchanged there).
  • Consequence: --static freestanding binaries can now use Power and
    friends — there was no way to static-link libm before — and results are
    bit-identical across Linux, FreeBSD and macOS (GH #199,
    BUG-20260730-native-libm-not-required).

stdlib

  • Text.Regex — a new backtracking regular-expression engine.
  • .From([...]) array-literal constructors for TList<T>, both dictionary
    types, and TSet<T>, plus the stdlib's first dedicated Collections test
    suite.
  • TUuid value type (the GUID unit was renamed to UUID).
  • AssertRaises / AssertNotRaises added to blaise.testing.
  • Out-of-range list/collection access raises EListError instead of
    crashing.
  • Collection destructors correctly declared override so vtables resolve
    correctly.
  • Managed elements are released on TStack/TQueue/TSet/TDictionary
    teardown (BUG-012).
  • GCHashOf and read-only container lookups borrow their key instead of
    retaining it.

Toolchain / linking

  • Auto link mode: a program that binds no C library links freestanding
    (no libc, no .dynamic) by default on Linux; binding external '<lib>'
    or a bare libc symbol flips the link to dynamic+libc automatically.
    --static/--dynamic force either mode explicitly.
  • Dynamic linking support for freebsd-x86_64.
  • The blaise_rtl.a archive and its build/guard machinery were removed —
    the compiler source-builds the RTL on demand into a target-keyed object
    cache, so there is no archive to install or go stale.
  • RTL source resolution now searches binary- and CWD-relative ancestor
    paths too.
  • Unit initialisation now runs in dependency order on the incremental
    compile path, fixing a class of implementation-only-dependency ordering
    bugs.
  • Demand-driven library linking: the internal linker emits extra
    DT_NEEDED entries only for libraries actually bound; pthread is now
    bound via external 'pthread' rather than always linked.
  • -l<name> resolves via SONAME / linker scripts; new --lib-path flag
    (GH #188).
  • RTL functions and unit _fini symbols are weak-bound to prevent
    archive/object multiple-definition errors (GH #180 follow-up, GH #191).
  • libm is no longer a link dependency at all — see the dedicated
    "runtime.math" section above (GH #199).

Codegen — native x86-64

  • IContainerWriter seam extracted from the ELF object writer, shared with
    the new Mach-O writers.
  • Inc/Dec on a promoted narrow local, a narrow global, and a field receiver
    now route through the correct width / shared l-value slot path.
  • 2-D field-array element reads return the element address, not a stale
    value.
  • 9th+ float arguments and >6 integer argument slots correctly overflow to
    the stack at method and itab call sites.
  • The stack-machine expression idiom was eliminated from the x86-64 emitter
    in favour of direct register allocation.
  • DoubleToStr/SingleToStr call sites now widen/narrow the float argument
    to the correct width (GH #200).

Codegen — interfaces / generics

  • itabs are now emitted for generic instances that inherit an interface,
    and for interfaces a class inherits, in the unit compilation path (both
    backends).
  • Interface methods inherited across a unit boundary now resolve correctly.
  • Interface elements are readable through an indexed property.
  • Generic monomorphisation now carries visibility, static, and
    parameter-passing-mode facts correctly into instantiated bodies.

Bug-fix clusters

  • Jumbo sets (>64-member enum bitmaps): element access fixed across
    field, array, and nested-chain shapes on both backends.
  • Nested-procedure capture: grandparent-scope capture, var/out
    double-dereference, sibling capture forwarding, and interface-typed
    captures.
  • Field-array l-values: subscript-on-a-field-array-element used as an
    l-value, across Inc/Dec, SetLength, and address-of, on both backends.

Performance

  • Stage-1 register promotion — hot scalars kept resident in %r14/%r15.
  • %r13 cross-call pin, for-loop condition push/pop removal, slot-traffic-
    based promotion ranking.
  • Phase-1 inlining — the QBE inliner ported to the native backend.
  • Loop rotation and a 4-register promotion pool, extended to inlined bodies.
  • Reproducible native-vs-QBE codegen benchmarks added under
    tools/performance/.

Tooling / CI

  • blaise-bindgen (new): generates Blaise bindings from clang AST JSON
    — macro constants, function-pointer procedural types, typed C-union
    accessors, bitfield packing, and variadic-function varargs directives.
    Shipped with a generated x11 bindings module and a working Xlib GUI
    example.
  • flag-coverage (new): a backend annotation-flag drift guard,
    sibling to bif-coverage — catches a semantic-pass flag silently going
    unread by a backend arm.
  • A linked-binary self-hosting fixpoint added, covering the
    macos-arm64 container writer in CI.
  • CI now runs the full FreeBSD tier (including the stdlib suite) on every
    run, with clearer failure diagnostics.

GitHub issues closed this cycle

#160, #180, #182, #187, #188, #189, #191, #195, #196, #208.


Stage-2 QBE IR: 637,968 lines, byte-identical across stage-2/stage-3.
Test suite: 4,977 tests, 2 ignored, all passing.


Built with ❤️ and lots of ☕