Skip to content

Releases: graemeg/blaise

Blaise v0.10.0 (alpha) — Incremental compile, Native Backend and more

07 Jun 01:20

Choose a tag to compare

Released: 2026-06-07

Language

  • Mandatory () on all zero-argument function/procedure/method/constructor calls
  • Procedure-field invocation: Obj.ProcField() dispatches through stored code pointer
  • Removed IsNoArgFuncCall / NoArgFuncDecl dead code from AST and codegen
  • Diamond operator: infer generic type args from LHS in assignments (TList<>.Create())
  • Exit(Value) shorthand for function-result early returns
  • not operator extended to integer types (bitwise complement)
  • Set-valued constants (const X = [a, b]) and set literals as set of arguments
  • threadvar support for thread-local storage
  • out parameter mode tracked distinctly from var
  • Calling convention directives (cdecl, stdcall) preserved on routine declarations

Native x86-64 Backend

  • M1: empty program compiles and runs
  • M2: integer arithmetic, Write/WriteLn
  • M3: if/while/repeat control flow
  • M4: records, static arrays, integer variables, for loops
  • M5: user functions, parameters, recursion, break/continue/exit, var/out params, indirect calls, >6 args via stack, wider integer family
  • M6: float (Double/Single) parity
  • M7a: record-returning functions (sret)
  • M7b: class system — method dispatch, vtables, constructors, destructors, method pointers
  • M7c: string operations parity
  • M7d: exception handling (try/except/finally)
  • M7e: open arrays and dynamic arrays
  • M7f: interface dispatch through itab
  • M8: var/out arguments to method calls, inherited calls
  • Full generics support (monomorphised classes, records, interfaces, free routines)
  • For-in and case statements
  • Inc/Dec built-ins and class-local ARC
  • .Free intrinsic
  • TAddrOfExpr, TDerefExpr, TPointerWriteStmt
  • ARC retain/release for string/class/interface value params and field assignments
  • Array field subscript read and var-param validation
  • Interface parameters in method/constructor/inherited calls
  • ICodeGen interface for backend dispatch

Incremental Separate Compilation

  • .bif (Blaise Interface File) format — compact unit-interface serialisation
  • .bif embedded inside .o files for zero-extra-file discovery
  • --incremental flag: skip unchanged units, reuse cached .o + .bif
  • Parallel incremental compilation via worker threads
  • Resolved all EImportError crashes in warm incremental compile
  • .bif covers: constants, types (simple, record, class, interface, generic templates), routines, globals, inline bodies, calling conventions
  • .bif validation header with compiler ID and source hash
  • TUnitInterface export/import for classes, interfaces, records, generics, virtual methods, override vtable slots, implements lists, attributes
  • Per-unit symbol cache for uses-chain retrieval
  • Layered symbol lookup — current-unit before uses-chain
  • Unit-prefix symbol mangling for free routines, classes, and address-of

ARC & Memory

  • Atomic ARC refcount operations for thread safety
  • Mutex-protected weak reference table for thread safety
  • Per-thread memory allocator via threadvar
  • Thread-local exception globals
  • TThread.Create(False) joins on Free() via ARC
  • Release old value when assigning nil to a class variable
  • Emit _ClassAddRef when assigning Pointer to class-typed variable
  • Release owned RHS when storing to [Unretained] class field
  • Retain/release by-value interface params in callee
  • Elide retain/release for const string and class params (perf)
  • Elide const-param retain/release, retain transient args (perf)

Codegen (QBE)

  • Unit-prefix mangling for multi-unit builds
  • System-unit defs emitted in unit-mode
  • Generic interface-instance typeinfo in multi-unit builds
  • WriteLn(Double/Single) no longer crashes QBE with type mismatch
  • Unsigned 32-bit values print as unsigned in WriteLn
  • @RecordField inside methods and pointer-to-record deref
  • Record method calls in statement position
  • Var/out params passed by reference in inherited calls
  • Method interface param passed as fat pointer
  • Generic class name mangled in itab symbol for interface assignment
  • Leak-tracker typeinfo reference uses class unit prefix
  • For-in class enumerator calls use method's unit prefix
  • Program-scope generic methods don't get unit prefix in ResolvedQbeName
  • Class fields shadow same-named program globals in method bodies

Compiler

  • --dump-ast flag for post-semantic AST inspection
  • --skip-dep-codegen for unit-as-top-level builds
  • Generic record support (monomorphisation)
  • Retain generic templates so repeat instantiation works
  • Auto-tag OwningUnit on global Define
  • FDefineOwningUnit, FCurrentUsesChain, BuildUsesChain, LookupViaUsesChain
  • Member-lookup visibility seam (IsVisibleFromUnit)
  • Accept set literals as set of arguments
  • Remove FPC conditional-compilation directives

Runtime & Standard Library

  • WriteLn prints True/False for Boolean values
  • TStringList.Text / LoadFromFile preserve verbatim lines
  • Drop legacy platform aliases; derive constants from target
  • Runtime split: drop 7 build-driver shims, use unit-as-top-level
  • RTL build switched to unit-as-top-level

Kanban Tool

  • TUI Kanban board application (new)
  • CLI mode for non-interactive task creation
  • UTC datetime storage with local display
  • External file change detection and merge
  • Rounded box-drawing corners and coloured borders
  • Moved from examples/ to tools/ as a PasBuild module

Tests

  • 2627 tests passing (up from 2293 in v0.9.0)
  • Native backend E2E suite runs through both QBE and native backends
  • Standalone pointer-to-record field deref test
  • Nested record field assign + method call tests
  • Separate-compilation round-trip E2E tests
  • Implicit System unit reachable without explicit uses
  • Test runner includes stdout in e2e exit-code mismatch messages

Tooling

  • Rolling-bootstrap script (scripts/rolling-bootstrap.sh) for development-checkout builds
  • Fixpoint script drives runtime build with stage-1 binary explicitly

Summary

  • 262,202 lines of verified QBE IR at fixpoint
  • 2627 tests passing
  • FIXPOINT_OK — self-hosting verified

Blaise v0.9.0 (alpha) — Runtime port & bug squashing

02 Jun 09:29

Choose a tag to compare

Changelog — Blaise v0.9.0

Fixpoint: stage-2 == stage-3 at 166,479 lines of QBE IR (up from 135,311 in v0.8.0).
Test suite: 2,293 tests, 0 failures.


Runtime — C elimination

  • Port blaise_arc_class.c to pure Pascal (blaise_arc.pas)
  • Port blaise_exc.c to Pascal + x86_64 assembly (blaise_setjmp_x86_64.s
    remains as the only non-Pascal file)
  • Port blaise_weak.c to pure Pascal
  • Port blaise_float.c to pure Pascal Grisu1 float-to-string implementation
  • Port blaise_str_fmt.c to pure Pascal _StringFormatN
  • Consolidate blaise_io / blaise_process / blaise_sys / blaise_time
    into rtl.platform.posix Pascal unit
  • Enable pipefail in the runtime Makefile so compiler errors fail the build

FFI / Codegen — interop correctness

  • Records by value through SysV aggregate ABI: record-by-value parameters
    now use QBE :_ffi_<Name> aggregate types so fields are scattered into
    INTEGER/SSE registers per the platform ABI. Applied uniformly to all call
    sites (external and intra-Blaise), so callbacks are consistent. Win64 ABI
    comes from QBE for free. Previously, a bare l <addr> was passed and the
    callee read the pointer bits as struct contents.
  • Single field alignment corrected to 4 bytes (was falling through to
    the 8-byte catch-all, doubling the size of Single-heavy records and
    breaking C struct interop)
  • Double literal narrowed to Single on record field store — previously
    emitted stores d_<lit>, ... which QBE rejected as a type mismatch
  • Double expression narrowed to Single before FFI call — a Double-
    typed actual passed to a Single formal put 8 bytes into the float slot;
    the C callee read the low mantissa half as IEEE-754 single (noise)
  • Narrow FFI return values masked to declared width: Byte/Boolean
    returns are ANDed with 255, Word with 65535, SmallInt sign-extended
    via shl 16 / sar 16. C ABI leaves upper bits undefined for sub-int
    returns; callers observing the full word saw garbage.
  • @A[i] on dynamic arraysEmitAddrOfExpr now has a tyDynArray
    branch mirroring the open-array path. Previously raised "Unsupported
    L-value form for var argument".
  • Overloaded Destroy in ARC field-cleanupEmitFieldCleanupFn now
    calls the overload-mangled symbol (stored as DestroyResolvedQbeName on
    TRecordTypeDesc) rather than the bare <Class>_Destroy label, which was
    never emitted for overloaded forms, causing a linker undefined-symbol error.
  • Fix storel for proc-pointer static-array slots; add Expr()() postfix call
  • Use narrow load/store for implicit-Self Byte/Word/SmallInt fields
  • Fix QBE types for Single float arithmetic and comparisons
  • Emit correct data items for typed array consts inside function bodies
  • Fix ARC retain/release on array element writes
  • Allocate 16 bytes for procedure-of-object class fields
  • Retain/release class refs stored through typed pointer
  • Nil class/string field slots after .Free to prevent double-free
  • Load class pointer before offsetting in var-param L-value
  • Preserve ARC variables across try/finally re-raise

ARC — correctness

  • finally blocks run on non-local exit (Exit, Break, Continue)
    via FFinallyStack — previously skipped entirely on non-local unwinds
  • [Unretained] attribute for non-owning class references (back-pointers,
    AST Resolved*Type fields, symtab links) that must not participate in ARC
  • Release +1-owned temporaries produced by function/property returns when
    used transiently or passed as value arguments
  • Release +1-owned receiver temporaries after method calls
  • Honour [Weak] on implicit-Self class field assignments
  • Retain caught exception when binding to handler variable (except E: T do)
  • Eliminate double-_ClassAddRef in constructor-with-args codegen
  • Eliminate double-_ClassAddRef on function-return class assignments
  • Wire owned-arg-temp release into all remaining call paths
  • Remove 50+ redundant Free calls from AST destructors (field cleanup
    already handled by ARC)
  • Remove redundant Free calls from symbol-table destructors

Language — new features

  • Nested procedures with captured-variable closure (full access to
    enclosing scope locals)
  • Custom attributes[MyAttribute] / [MyAttribute(args)] on any
    declaration; compiler validates attribute class and resolves constructor
  • [Unretained] non-owning reference attribute
  • SmallInt and Word — 16-bit signed/unsigned integer types with correct
    narrow load/store and Byte/Word field packing at 1-byte / 2-byte stride
  • UInt64 / QWord — 64-bit unsigned integer type
  • packed record — suppresses per-field alignment padding
  • sar arithmetic right-shift operator
  • High / Low extended to all ordinal types; targeted float error message
  • SizeOf(expression) — previously only SizeOf(TypeName) was accepted
  • / is real division — distinct from div (integer division)
  • for..in over dynamic arrays
  • Octal / binary literals (0o777, 0b1010) and underscore separators
    in numeric literals (1_000_000, 0xFF_FF)
  • Integer-type typecasts in const initialisersCardinal(-11),
    Byte(255), SmallInt(-1), etc., with correct bit-width truncation and
    sign extension
  • Bit-op chains in const initialisersFG_BLUE or FG_GREEN, 1 shl 8,
    $FF and 15, A xor B, including mixed literal+named chains and array
    elements; folded at semantic time
  • ArcSin, ArcCos, Sinh, Cosh, Tanh math builtins with Single
    dispatch overloads
  • Extend High / Low to dynamic array types
  • Allow direct invocation of proc-typed class fields
  • Allow implicit integer-to-float assignment; reject implicit float↔integer

Semantic — fixes

  • Implicit Self member no longer shadows unit-level symbol
  • Nested procs with the same name in different outer procs no longer ambiguous
  • Clone method bodies per generic instance (was sharing mutable AST nodes)
  • Allow two instances of the same generic class in the same scope
  • Wire up parent class on generic instances
  • Walk parent chain when resolving inherited properties
  • Register var decls before analysing method bodies
  • Use overload resolution for constructor calls
  • Emit support data for generic classes declared in a unit
  • Normalise all identifier references to declared casing
  • Reject duplicate identifiers across const and var sections
  • Report missing units even when no --unit-path is given
  • Fix SizeOf(expression) to resolve the expression type

Threading

  • TThread, TCriticalSection, and full POSIX thread bindings
  • Self-referential thread lifetime under ARC (thread holds own refcount during
    Execute, preventing use-after-free on fire-and-forget patterns)
  • [Threaded] E2E test suites run as parallel subprocesses

Stdlib / Performance

  • GetText and GetCommaText rewritten to avoid O(N²) string concatenation
  • TList<T>.IndexOf added
  • TListEnumerator<T> + TList<T>.GetEnumeratorfor..in over generic lists
  • _ClassRelease elision for provably-nil slots (codegen perf)
  • Remove all dead {$IFDEF FPC} compiler directives

Developer experience

  • --debug leak reporter — shows refcount per live object at program exit
  • blaise.cfg — automatic unit-path discovery from project directory
  • Multiple --suite filters accepted by the test runner
  • --emit-ir path now exits through normal cleanup instead of Halt(0)
  • UTF-8 bytes in comments: regression tests added to lexer suite

Compiler internals / Build

  • Mark Resolved*Type AST fields [Unretained] — prevents spurious retain
    cycles on type descriptors shared across the AST
  • Remove all old FPC compiler directives no longer needed in Blaise
  • --cache-dir flag and migrate_full.py removed (dead code)

Blaise v0.8.0 (alpha) — House Cleaning🧹 & Performance

16 May 22:32

Choose a tag to compare

Blaise Compiler — Changelog for v0.8.0

Released: 2026-05-16
Previous: v0.7.0 (2026-05-13)
Commits: 62 between v0.7.0..v0.8.0

HEADLINE

v0.8.0 is the first release with a Pascal-native memory allocator.
The compiler RTL, codegen builtins, and all C runtime helpers route
through blaise_mem (_BlaiseGetMem / _BlaiseFreeMem / _BlaiseReallocMem)
instead of libc malloc.

blaise_mem beats libc malloc on three of five microbenchmark workloads
(small alloc, mixed sizes, retain+free-all) and delivers a ~10 %
real-world compile-time win on the 1934-test suite (~89 s → ~80 s).

NEW LANGUAGE / SEMANTICS

  • Dynamic arrays (array of T) — full implementation: SetLength,
    Length, Copy, indexed read/write, ARC-managed lifetime.

  • Typed constants — const Name: Type = Value with array literals
    for both fixed-range and array-of-enum forms.

  • Class-level array constants — constant arrays declared inside a
    class body, addressable via TypeName.ConstName.

  • Range-indexed array constants — array[0..N] of T = (...).

  • Abstract methods and abstract classes — virtual; abstract; syntax,
    with a runtime tombstone (_AbstractMethodError) that aborts with a
    clear message if a vtable miss reaches it.

  • Explicit ordinal values in enum definitions — (A = 1, B = 5).

  • Record methods — methods declared inside a record definition.

  • for .. in over sets — iterate the members of a set of T.

  • Supports() intrinsic for interface queries.

  • Open-array coercion — pass static-array variables directly to
    array of T parameters.

  • Named array type aliases in the type section — type TArr = array[..] of T;.

  • Length() extended to accept open-array and static-array
    parameters in addition to strings and dynamic arrays.

  • Five-type date/time model with full DateUtils unit (records-based;
    TDateTime, TDate, TTime, plus formatting / arithmetic helpers).
    Read more.

  • Generic vars at unit scope and non-identifier interface arguments
    now parse and analyse correctly.

  • Overload resolution now considers the implicit-self expression
    path, matching Delphi/FPC behaviour.

RUNTIME / STANDARD LIBRARY

  • Streams stack (read more):

    • TInputStream / TOutputStream + file and memory streams.
    • buffered wrappers with error propagation.
    • TStreamReader / TStreamWriter text wrappers.
    • TBuffer segment rope + byte-dereference fixes.
    • CopyStream and capability markers; interface ABI fixes.
  • Pascal memory allocator (blaise_mem) — mmap-backed, with size-class
    freelists for small allocations, a LIFO cache for large allocations,
    in-place mremap-based realloc for large blocks. 28 correctness
    tests in runtime/src/test/pascal/test_blaise_mem.pas.

  • Path manipulation functions ported from C to Pascal (ChangeFileExt,
    ExtractFileName, ExtractFilePath, ExtractFileDir, ExtractFileExt,
    IncludeTrailingPathDelimiter, ExcludeTrailingPathDelimiter).

  • TComponent — minimal ownership/notification class.

  • TStringList — CustomSort + CommaText property.

  • StrUtils — pure-Pascal port.

  • SysUtils — ExpandFileName.

  • Math unit — math compiler builtins (Min, Max, Abs, Sign, etc.).

  • IMap<K, V> interface — generic dictionary contract with itab-based
    dispatch for generic→interface use.

  • bcl.testing extended with AssertNotEquals, AssertContains, and a
    --verbose CLI flag.

  • RTL split into runtime/ (always-linked) and stdlib/ (opt-in).
    Testing framework renamed bcl.testing → blaise.testing.

  • LineEnding, sLineBreak, DirectorySeparator, PathSeparator moved
    from rtl.platform to system.pas.

  • Platform layer renamed bcl.platform → rtl.platform with expanded
    SysUtils.

COMPILER OPTIMISATIONS

  • Function inlining (phase 1) — small leaf functions are inlined at
    call sites when address-taken and ARC-free. Inlinability decided
    by uSemantic; codegen walks the callee body into the caller frame.

  • Function inlining (phase 2) — case statements now allowed inside
    inline candidates, and the body-statement cap raised from 8 to 24.
    This unlocked inlining of blaise_mem's SizeClassIndex and
    SizeClassBytes.

  • mem2reg promotion for tyPointer / tyPChar locals — non-address-taken
    pointer locals are promoted to QBE temps instead of alloca slots,
    yielding direct register access on every read/write.

  • Sanity checker in _StringRelease — verifies the string header looks
    plausible (refcount, length, capacity within sane bounds) before
    treating refcount==0 as a free signal. Aborts with a clear stderr
    message on corruption.

BUG FIXES

  • P[I] := Chr(N) no longer stores the low byte of a heap string
    pointer. EmitByteRhs short-circuits Chr() in byte-store contexts
    and emits the integer value directly, skipping the _Chr allocation.
    Applies to PChar subscript writes, dynamic- and static-array byte
    subscript writes, and byte-typed pointer writes.

  • Static-array element stores of PChar values no longer truncate to
    32 bits. tyPChar was missing from the storel / loadl / alloc8
    case branches and fell through to storew, silently zeroing the
    high 32 bits of every heap pointer. Fixed across five sites in
    uCodeGenQBE.pas.

  • large-alloc LIFO cache in blaise_mem — IsLarge() was reading the
    wrong field of TLargeHeader (AllocSize: Int64 overlapped with the
    Flags slot it probed). Fix made the cache reach ~100 % hit rate
    on 64 KB allocations, dropping the L workload from 33 ms to 0 ms.

  • Static-array variables now coerce correctly when passed to open-
    array parameters.

  • Stale rtl/ directory references in E2E tests updated to
    runtime/ after the directory split.

  • _POSIX_C_SOURCE bumped to 200809L to expose mkstemp() on glibc.

PERFORMANCE

Allocator microbench (median of 3 runs, milliseconds), 1 M small alloc/
free, 500 k mixed-size, 100 k 5-step realloc, 10 k × 64 KB, 100 k
retain-then-free-all:

  Workload                       | libc malloc | blaise_mem | ratio
  -------------------------------+-------------+------------+--------
  Small alloc/free  (1M × 32 B)  |      8      |     7      | 0.88x
  Mixed sizes       (500k × var) |      5      |     4      | 0.80x
  Realloc growth    (100k × 5)   |      5      |     8      | 1.60x
  Large alloc/free  (10k × 64KB) |      0      |     0      |  —
  Retain+free-all   (100k × 64B) |      5      |     4      | 0.80x

Real-world compile workload (Blaise test suite, 1934 tests):

Pre-cutover (v0.7.0 baseline): ~89 s
Post-cutover (v0.8.0 release): ~80 s (~10 % faster)

DOCUMENTATION

  • Testing strategy document — explains the unit / E2E / punit
    three-layer split and when to use each.

  • Stream I/O design rationale.

  • Generic Interfaces and Collections Framework rationale.

  • Cold-bootstrap RTL chicken-and-egg issue documented.

  • Migration analyser rule: detect TObjectListTList<TObject>.

  • Future-improvements: enhanced enumerations section.

  • benchmark.txt expanded with a long-term tracking format and entries
    for each performance milestone in the cycle.

  • Run procedure for the bench scripts updated post-cutover — a
    third bench source (bench_libc_malloc.pas) uses explicit external
    malloc / free / realloc bindings so the libc baseline can still be
    measured.

INFRASTRUCTURE / REFACTORING

  • rtl/ directory split into runtime/ + stdlib/.

  • Monolithic E2E test unit split into 12 topic units (one per
    feature area).

  • TStringList E2E coverage expanded with a shared base class.

  • Six test units re-enabled after fixes to static-array and Pos
    semantics.

  • GitHub funding configuration (Patreon + PayPal).

FIXPOINT

stage-2 IR : 135 311 lines
stage-3 IR : 135 311 lines
diff -q : (empty — byte-identical)
1934 tests : pass

Blaise v0.7.0 (alpha) — Independence Day 🎉

13 May 06:53

Choose a tag to compare

Released: 2026-05-13

This release marks a milestone: Blaise is now fully independent of
Free Pascal. FPC is no longer required anywhere in the toolchain.
The bootstrap chain is: released Blaise binary → compiles source →
new Blaise binary
. From here on, every release builds the next.

Highlights

FPC Independence

  • The compiler is fully self-hosting. releases/v0.7.0/blaise is
    the new stage-1 bootstrap binary — no FPC installation required.
  • The fixpoint script (scripts/fixpoint.sh) auto-picks the latest
    release binary as stage-1 and includes a stage-4 fallback for
    cross-release bootstrap gaps.
  • Fixpoint verified: stage-2 IR == stage-3 IR (116,462 lines).

Test Suite Now Runs Under Blaise

  • All 1,268 tests compile and run using the Blaise-compiled test
    runner. The test runner is no longer built by FPC.
  • The test framework migrated from fpcunit to bcl.testing — 57
    test units converted in a single pass.

Language & Codegen

  • Low() and High() now work on strings (in addition to arrays).
  • Fixed implicit-self method calls that pass var/out or
    open-array parameters — two symmetric paths in EmitProcCall
    and EmitExpr (uCodeGenQBE.pas).
  • Fixed global static array codegen bugs affecting for..in loops.
  • Fixed for..in promoted scalar variable handling.

RTL

  • Fixed a buffer overflow in _GetTempFileName when the directory
    is empty and TMPDIR is set.
  • process.pas renamed to lowercase for the case-sensitive unit
    loader.

Build & Infrastructure

  • README.adoc updated with FPC-free build and bootstrap
    instructions, including the BLAISE= make override needed for
    cold bootstraps from a clean checkout.
  • blaise_rtl.a included in the release archive — end users can
    now link their compiled programs without building the RTL from
    source.

Download

File Description
blaise-v0.7.0-linux-x86_64.tar.gz Compiler binary + RTL + docs (Linux x86-64)

The archive contains: blaise, blaise_rtl.a, README.adoc,
LICENSE, NOTICE.

Bootstrap

Unpack the release archive into releases/v0.7.0/ inside your
clone of the repository:

mkdir -p releases/v0.7.0
tar -xzf blaise-v0.7.0-linux-x86_64.tar.gz \
    --strip-components=1 \
    -C releases/v0.7.0

Then build:

# 1. Build the RTL (BLAISE= avoids chicken-and-egg on clean checkout)
cd rtl && make BLAISE=../releases/v0.7.0/blaise && make install && cd ..

# 2. Compile
releases/v0.7.0/blaise \
  --source compiler/src/main/pascal/Blaise.pas \
  --unit-path compiler/src/main/pascal \
  --unit-path rtl/src/main/pascal \
  --emit-ir > /tmp/blaise.ssa
vendor/qbe/qbe -o /tmp/blaise.s /tmp/blaise.ssa
gcc -o compiler/target/blaise /tmp/blaise.s compiler/target/blaise_rtl.a