Commit graph

21 commits

Author SHA1 Message Date
Graeme Geldenhuys a231c7d043 feat(debug): add --debug leak reporter + Pointer/PtrUInt cast pairs
Runtime leak tracker (blaise_arc.pas):
- Open-addressing hash map (1024 buckets, 16 bytes each) tracks every
  live class instance; zero overhead when disabled.
- _LeakTrackerEnable() activates tracking and registers _LeakTrackerReport
  via atexit; called from $main when --debug is passed.
- _LeakTrackerRegister(UserPtr, ClassName) inserts into the map; called
  from codegen at every constructor site (both TFieldAccessExpr and
  TMethodCallExpr paths).
- _ClassRelease unregisters on refcount-zero free via LTDelete.
- Report written to stderr lists count + class name of each survivor.

Compiler (--debug flag):
- New --debug CLI flag sets FDebugMode on TCodeGenQBE; emits
  call $_LeakTrackerEnable() in EmitMainHeader after unit init calls,
  and call $_LeakTrackerRegister(...) after every _ClassAlloc site.
- --debug-opdf remains independent (OPDF only, no leak tracking).
- SetDebugMode(Bool) added to TCodeGenQBE public API for e2e tests.

Pointer/PtrUInt cast pairs (semantic + codegen):
- Pointer(intOrPtrExpr) validated in AnalyseFuncCallExpr; accepts
  integer, pointer, class, metaclass, procedural, and nil sources.
- PtrUInt(intOrPtrExpr) same validation; resolves to tyUInt64.
- Codegen: w-to-l widening uses extuw for Pointer/PChar targets
  (zero-extend, not sign-extend) to preserve address semantics.
- Four new unit tests in cp.test.pointers.pas.

E2E test infrastructure:
- CompileAndRunWithRTLDebug added to TE2ETestCase base for in-process
  debug-mode compilation without recursive overload ambiguity.
- cp.test.e2e.leakcheck.pas: 5 tests covering no-leak silence,
  single leak, multiple leaks, reference cycles, and debug-off.

2258 tests pass; fixpoint verified.
2026-06-01 16:37:02 +01:00
Graeme Geldenhuys 7dbb981665 build(runtime): enable pipefail so compiler errors fail the build
The Pascal RTL recipes pipe $(BLAISE) --emit-ir through sed to strip
the program section before writing the .ssa file.  Without pipefail,
sed's exit code masks a failing compiler invocation: the recipe
succeeds with an empty .ssa, QBE emits an empty .s, gcc archives an
empty .o, and the first symptom is a downstream link failure with
"no symbols".

Set SHELL=/bin/bash and .SHELLFLAGS=-o pipefail -c at the top of the
Makefile so every recipe inherits pipefail.  Verified by injecting a
parse error into a build driver: make now exits with Error 1 and
prints the compiler's diagnostic, instead of silently producing an
empty archive member.
2026-05-22 15:39:08 +01:00
Graeme Geldenhuys b43a999f80 feat(types): add UInt64 / QWord type
Adds a real 64-bit unsigned integer type with two equivalent names:
UInt64 (Delphi style, matches the existing Int64) and QWord (FPC
style).  PtrUInt now aliases UInt64 too — it's the natural pointer-
sized unsigned on 64-bit.

Language semantics:
- Arithmetic on UInt64 uses udiv/urem; add/sub/mul/and/or/xor/shl/shr
  are bit-identical to their signed counterparts.
- Comparisons use unsigned QBE ops (cultl, cugtl, ...).
- Int64 <-> UInt64 mixing requires an explicit cast; the two types are
  not implicitly convertible in either direction.
- Decimal/hex literals in the (2^63, 2^64-1) range are typed as
  UInt64.  Smaller literals stay Integer/Int64.
- SizeOf(UInt64) = SizeOf(QWord) = 8.

Runtime:
- New _UInt64ToStr in blaise_str.pas plus a WriteDecimalU helper that
  uses UInt64 arithmetic.
- SysWriteUInt64 added to the platform abstraction and implemented in
  the POSIX layer.  WriteLn(UInt64) routes through it.
- IntToStr(UInt64) routes to _UInt64ToStr; explicit UInt64ToStr is
  also exposed as a builtin.

Bootstrap notes:
- Older release binaries cannot compile the runtime any more because
  rtl.platform.pas declares SysWriteUInt64.  A stage-2 rebuild from a
  fresh stage-1 is required after this commit on any worktree with an
  older stage-1, per CLAUDE.md.
- The parser stages literal Value through local var-params rather than
  writing directly to the new TIntLiteral.IsUInt64 field via class-field
  out-params.  Working around a stage-1 codegen bug where var-param
  calls that target a class field silently fail to write back.

Docs:
- docs/grammar.ebnf: built-in type list expanded with UInt64/QWord,
  integer-literal typing rules documented.
- docs/language-rationale.adoc: integer types table updated with the
  unsigned variants, Int64<->UInt64 strict conversion rule explained.

Tests:
- 15 new tests in cp.test.uint64.pas covering symbol-table
  registration, codegen instruction picking (udiv/urem/cultl/cugtl),
  literal-range typing, and e2e round-trips.
- Full suite: 2151 tests pass (up from 2132).
- Fixpoint clean at stage-3/stage-4 (expected: type-system change).
2026-05-22 08:08:35 +01:00
Graeme Geldenhuys 4969b74810 refactor(runtime): replace blaise_exc.c with Pascal + x86_64 assembly
Port all exception frame management, type identity, and runtime check
functions from C to pure Pascal (blaise_exc.pas).  Replace libc
setjmp/longjmp with a minimal custom implementation in x86_64 assembly
(blaise_setjmp_x86_64.s, ~40 lines) that saves only the 8 callee-saved
registers (64-byte jmp_buf vs libc's 200-byte one).

This eliminates the last C source file from the runtime build.  The only
non-Pascal code in the runtime is now the assembly setjmp stub.

Changes:
- runtime/src/main/asm/blaise_setjmp_x86_64.s: _blaise_setjmp/_blaise_longjmp
- runtime/src/main/pascal/blaise_exc.pas: _PushExcFrame, _PopExcFrame,
  _Raise, _Reraise, _CurrentException, _CurrentExceptionMessage,
  _IsInstance, _ImplementsInterface, _GetItab, _Raise_InvalidCast,
  _CheckNil — all ported from blaise_exc.c
- uCodeGenQBE.pas: emit call $_blaise_setjmp instead of call $setjmp
- cp.test.exceptions.pas: IR assertion updated for new symbol name
- runtime/Makefile: assembly rule, Pascal blaise_exc build, C rules removed
- .gitignore: whitelist runtime/src/main/asm/*.s
- runtime/src/main/c/blaise_exc.c: deleted

2083 tests pass, fixpoint verified (stage-3/stage-4).
2026-05-21 01:13:17 +01:00
Graeme Geldenhuys 127bc35490 refactor(runtime): port blaise_arc_class.c to pure Pascal
Move _ClassAlloc, _ClassRelease, _StringReleaseCheck, and
_AbstractMethodError from C into blaise_arc.pas. Uses
TFieldCleanupProc procedure type to call the compiler-emitted
field cleanup function pointer from the class header.

Only blaise_exc.c (setjmp/longjmp) remains in C — everything
else in the runtime is now pure Pascal.

All 2083 tests pass. Fixpoint verified.
2026-05-21 01:13:09 +01:00
Graeme Geldenhuys 1507bf9877 refactor(runtime): consolidate IO/process/sys/time into rtl.platform.posix
Merge blaise_io.pas, blaise_process.pas, blaise_sys.pas, and
blaise_time.pas into the OO platform facade (TRtlPlatformPosix).
All underscore ABI stubs now delegate through GRtlPlatform, enabling
future macOS/Windows/FreeBSD support via a single new TRtlPlatformXxx
subclass with no ifdefs or scattered platform files.

Key design decisions:
- _SetArgs creates GRtlPlatform eagerly (runs before unit _init)
- Path utilities stay in blaise_str.pas (platform-independent)
- Makefile sed strip keeps TObject typeinfo from program section
- Shared string helpers (StrAlloc, StrFromCStr) deduplicated

Deleted 8 files (4 units + 4 build drivers), replaced by the
expanded rtl.platform.posix.pas and its single build driver.

All 2083 tests pass. Fixpoint verified.
2026-05-21 01:13:08 +01:00
Graeme Geldenhuys 2896204841 feat(attributes): add custom attribute language feature
Implement Delphi-style custom attributes: TCustomAttribute base class,
[AttrName] syntax on class declarations, attribute RTTI emission, and
the HasClassAttribute(AClass, AAttrClass) builtin for runtime queries.

Pipeline changes:
- uAST: add Attributes: TStringList to TClassTypeDef
- uParser: parse [Attr] before type names (ParseTypeSection loop now
  accepts tkLBracket; ParseTypeDecl collects and copies to class def)
- uSymbolTable: register TCustomAttribute builtin; add ClassAttributeCount/
  ClassAttributeAt accessors to TRecordTypeDesc
- uSemantic: resolve attribute names using Delphi suffix convention
  ([Threaded] matches ThreadedAttribute); semantic error for unknown attrs;
  [Weak] remains compiler-intrinsic and is skipped; add HasClassAttribute
  builtin handler in AnalyseFuncCallExpr
- uCodeGenQBE: extend typeinfo from 7 to 8 l-slots (slot 7 = $attrs_T or 0);
  emit $attrs_T count+typeinfo-ptr tables; emit TCustomAttribute vtable and
  field-cleanup stubs unconditionally; emit HasClassAttribute as a call to
  $_HasClassAttribute
- blaise_arc: implement _HasClassAttribute — reads slot 7, scans the attrs
  table, walks the parent chain so inherited attributes are visible

19 tests in cp.test.attributes.pas covering parser, semantic, codegen, and
four E2E round-trips. Updated cp.test.publishedrtti.pas for 8-slot layout.
2026-05-20 11:01:12 +01:00
Graeme Geldenhuys 25c1f83cd9 feat(threading): add TThread, TCriticalSection and POSIX thread bindings
Add blaise_thread.pas runtime unit wrapping pthread_create, pthread_join,
pthread_mutex_init/lock/unlock/destroy and sysconf for GetCPUCount.

Implement TThread (Create, Start, Terminate, WaitFor, Execute virtual,
FreeOnTerminate) and TCriticalSection (Enter/Leave) in classes.pas using
ARC-safe ref counting in the thread trampoline.

Fix EmitFieldCleanupFn to walk the parent chain when emitting the Destroy
call — previously a subclass without its own Destroy would generate an
empty cleanup, causing leaked resources from parent destructors.

Link -lpthread in both the compiler driver and E2E test base.

Include 7 E2E tests covering basic execution, WaitFor blocking, Terminate
flag, Finished flag, multiple threads, mutex-protected counter, and
inherited Destroy cleanup.
2026-05-20 08:31:15 +01:00
Graeme Geldenhuys 95c07ca580 feat(runtime): port blaise_str_fmt.c to pure Pascal _StringFormatN
Replace the C variadic _StringFormat with a pure Pascal _StringFormatN
that takes a format string, a pointer to an array of tag/value pairs,
and a count. The codegen allocates the arg array on the stack via
alloc8, stores (tag:l, value:l) pairs, and calls the non-variadic
function — eliminating the last dependency on C va_list.

This required a multi-stage bootstrap: release binary compiled stage-1
(with new codegen), stage-1 compiled stage-2 (self-hosted with new ABI),
then the C file could be safely removed. Integer args are widened via
extsw when w-typed to satisfy QBE's storel type checking.

Only 2 C files remain in the runtime: blaise_exc.c (setjmp/longjmp)
and blaise_arc_class.c (function-pointer dispatch).
2026-05-19 23:58:58 +01:00
Graeme Geldenhuys 802decba69 feat(runtime): port blaise_float.c to pure Pascal Grisu1 implementation
Replace the C-based float support (_DoubleToStr, _SingleToStr,
_StrToDouble, _AbsInt, _AbsInt64) with a pure Pascal implementation
using a simplified Grisu1 algorithm (Loitsch 2010) for float-to-string
conversion and native double arithmetic for string-to-float.

The new implementation has no libc dependency for the conversion logic
itself (memcpy is still used in BlaiseAllocStr as a temporary measure).
This is a step toward full platform independence.

Also fix a codegen bug where Int64-to-Double promotion in mixed
arithmetic/comparison expressions emitted swtof (32-bit truncation)
instead of sltof (64-bit). This affected three sites in EmitBinExpr
and one in EmitAssignment.
2026-05-19 23:19:52 +01:00
Graeme Geldenhuys 86b87b6c27 feat(runtime): port blaise_weak.c to Pascal
Replace the C weak-reference table implementation with an equivalent
Pascal unit (blaise_weak.pas).  Same data structures — open-chained
hash table with linked slot lists — using _BlaiseGetMem/_BlaiseFreeMem
and ^Pointer locals for the void** dereference pattern.

Deletes blaise_weak.c; runtime C files now down to 4 (blaise_arc_class,
blaise_exc, blaise_float, blaise_str_fmt).

All 2023 tests pass including E2E weak-ref Valgrind test.  Fixpoint OK.
2026-05-19 16:11:14 +01:00
Graeme Geldenhuys d45f8196db feat(runtime): port blaise_io / blaise_process / blaise_sys / blaise_time to Pascal
Step 13 (partial): replace four C shims with self-hosted Pascal units that
bind directly to libc syscall surfaces. blaise_exc.c, blaise_arc_class.c,
blaise_weak.c, blaise_str_fmt.c, and blaise_float.c remain in C — they
either need setjmp/longjmp, function-pointer fields in records, or
variadic interfaces that QBE / Blaise cannot express today.

Ports:
- blaise_io.c → blaise_io.pas
  File I/O, env vars, working directory, ParamStr, mkstemp, sleep,
  process ID. Uses libc bindings (open/read/write/stat/...) declared in
  the interface section per the migration rule in CLAUDE.md.
- blaise_process.c → blaise_process.pas
  fork/exec/pipe/waitpid wrapped as TProcess; same libc-binding pattern.
- blaise_sys_posix.c → folded into blaise_sys.pas
  Tiny shim (just _SysWrite + _SysWriteNewline); now writes directly via
  posix_write. No separate C file needed.
- blaise_time.c → blaise_time.pas
  clock_gettime + date arithmetic. The local typed-array-const Days
  triggered the codegen bug fixed in fa69ae1.

Each unit is built via a thin build_driver.pas program (see existing
blaise_str_build_driver.pas pattern): the Makefile compiles the driver,
strips everything from the program section onward, then assembles + links
the unit-only IR into the runtime archive.

Outcome:
- C_SRCS shrinks from 9 → 5 files; runtime archive size unchanged in
  spirit but the dependency surface contracts.
- Standalone units, not yet folded into rtl.platform.posix.pas — that
  consolidation can happen later when the platform layer matures and a
  second backend (windows / darwin) appears to justify the abstraction.
- All 2023 tests pass; fixpoint OK.
2026-05-19 01:04:17 +01:00
Graeme Geldenhuys 7b24fa754d benchmark: add literal underscores for better readability in code.
Unit comment block was stale and still referenced the old values.
2026-05-17 01:05:28 +01:00
Graeme Geldenhuys da2c5d2459 docs(benchmark): post-cutover measurements and libc baseline bench
Add bench_libc_malloc.pas with explicit external malloc/free/realloc
bindings so the libc baseline can still be measured after the cutover.
The original bench_blaise_mem.pas now measures blaise_mem too (because
the GetMem builtin emits _BlaiseGetMem), so it is no longer suitable
as a baseline.

Log the post-cutover state: blaise_mem now beats libc on small, mixed,
and retain-free-all workloads.  Realloc growth remains 1.6x.  The
compile-time win on the real test suite (~10%) exceeds the microbench
gap, suggesting real workloads are dominated by short-lived small
allocations where the freelist pop/push beats glibc tcache.
2026-05-16 18:36:55 +01:00
Graeme Geldenhuys d7e9caaf86 feat(rtl,codegen): cut over to blaise_mem allocator
Replace libc malloc/free/realloc with _BlaiseGetMem/_BlaiseFreeMem/
_BlaiseReallocMem across the full string + class + dynamic-array path:

Pascal RTL:
  blaise_str.pas — StrAlloc, _IntToStr, _Int64ToStr, _DynArraySetLength
  blaise_arc.pas — _StringRelease, _StringConcat, _ClassFree

C RTL (string/class allocators only — internal scratch buffers
        remain libc-paired):
  blaise_arc_class.c — _ClassAlloc (calloc -> _BlaiseGetMem + memset)
                       _ClassRelease (free -> _BlaiseFreeMem)
  blaise_str_fmt.c   — _StringFormat result
  blaise_io.c        — io_str_alloc (ParamStr, ReadFile, etc.)
  blaise_exc.c       — exc_str_from_cstr
  blaise_float.c     — blaise_alloc_str
  blaise_process.c   — proc_str_alloc

Codegen:
  uCodeGenQBE.pas — GetMem/FreeMem/ReallocMem builtins now emit
                    $_BlaiseGetMem/$_BlaiseFreeMem/$_BlaiseReallocMem.

Tests updated to match the new IR substrings:
  cp.test.pointers, cp.test.tlist, cp.test.tstack, cp.test.tqueue,
  cp.test.tset, cp.test.collections.

All 1934 tests pass; fixpoint clean.
2026-05-16 18:01:57 +01:00
Graeme Geldenhuys a2c55be812 feat(rtl): sanity-check string headers in _StringRelease
When _StringRelease decrements the refcount, also validate that the
header looks plausible: refcount >= -1 (else double-free), length in
[0, 1 GiB], and capacity >= length and below 1 GiB.  On corruption,
write a clear message to stderr and abort instead of returning to
the wild segfault that would follow.

Added so the upcoming blaise_mem cutover investigation has a clear
failure signal when string ownership crosses an allocator boundary.
Benign on healthy programs.

All 1934 tests pass; fixpoint clean.
2026-05-16 17:47:18 +01:00
Graeme Geldenhuys aa712f1104 test(rtl): expand blaise_mem correctness coverage from 14 to 28 tests
Adds 14 new punit tests covering allocator edges that the original
suite did not exercise:

  - SizeClass_Boundaries     — every class boundary +/-1
  - Realloc_SameClass        — in-place when class is unchanged
  - Realloc_Shrink           — preserves bytes across class shrink
  - Realloc_Grow_Steps       — preserves bytes across stepwise grow
  - Realloc_SmallToLarge     — crosses LARGE_THRESHOLD upward
  - Realloc_LargeToSmall     — crosses LARGE_THRESHOLD downward
  - Realloc_Large_SamePage   — same-page-count fast path
  - Realloc_Large_GrowPages  — mremap path
  - Large_Cache_Reuse        — LIFO cache returns the freed block
  - Large_Cache_Eviction     — workload exceeds LARGE_CACHE_MAX
  - Retain_Many_Small        — pattern preservation across 200 blocks
  - Interleaved_Mixed        — small+large interleaved
  - Realloc_Churn            — repeated grow/shrink preserves bytes
  - Alignment_All_Classes    — 8-byte alignment for every class

Each grow/shrink test fills a known byte pattern and asserts every
byte survives the realloc.  All 28 tests pass against the current
blaise_mem after the IsLarge() fix.
2026-05-16 10:18:28 +01:00
Graeme Geldenhuys 9847369e71 fix(rtl): restore large-alloc LIFO cache in blaise_mem
IsLarge() was reading the small-header Flags field at offset Ptr-4,
but TLargeHeader laid its AllocSize: Int64 across Ptr-8..Ptr-1, so
the Flags probe overlapped with the high half of AllocSize and was
always zero.  Every large block therefore routed through the small
free path and the LIFO cache was never populated, forcing a fresh
mmap on each large allocation.

Restructured TLargeHeader to:
  TotalMapped: Int64    (Ptr-16..Ptr-9)
  AllocSize:   Integer  (Ptr-8..Ptr-5)
  Flags:       Integer  (Ptr-4..Ptr-1)

LargeGetMem now writes Flags := FLAG_LARGE, IsLarge() returns the
correct value, and the cache reaches ~100% hit rate on the large
alloc/free workload (32 ms -> 0 ms for 10k x 64KB).

Also adds the two benchmark programs (bench_blaise_mem.pas for the
malloc baseline and bench_blaise_mem_custom.pas for blaise_mem) and
reformats docs/benchmark.txt as a dated log so future runs can be
tracked over time.
2026-05-16 10:09:20 +01:00
Graeme Geldenhuys b1cb9d05bb feat(rtl): add Pascal memory allocator (blaise_mem) backed by mmap
Arena-based allocator with per-size-class freelists for small blocks
(up to 2048 bytes) and direct mmap/munmap for large blocks. All
returned pointers are 8-byte aligned. Self-contained: no dependency
on strings, ARC, or stdlib.

14 punit tests covering basic alloc/free, realloc, alignment, size
classes, large allocations, and freelist reuse. Allocator is in the
RTL archive but nothing depends on it yet — GetMem/FreeMem still
route to libc malloc/free.
2026-05-16 01:42:17 +01:00
Graeme Geldenhuys 48b6773eff refactor(rtl): port path manipulation functions from C to Pascal
Move seven pure-string path functions (_ChangeFileExt, _ExtractFileName,
_ExtractFilePath, _ExtractFileDir, _ExtractFileExt,
_IncludeTrailingPathDelimiter, _ExcludeTrailingPathDelimiter) from
blaise_io.c to blaise_str.pas, reducing C code by ~130 lines while
keeping all OS-level functions (rmdir, rename, chdir) in C.
2026-05-16 01:13:00 +01:00
Graeme Geldenhuys 923b94c541 refactor: split rtl/ into runtime/ + stdlib/, rename bcl.testing to blaise.testing
Separate always-linked runtime code (system.pas, ARC, strings, platform
abstraction) from opt-in standard library units (sysutils, classes, math,
dateutils, generics, etc.).  This mirrors the Go runtime/ vs stdlib, Rust
core vs std, and Swift SwiftRuntime vs SwiftCore patterns.

- runtime/ contains code linked into every binary + C shims
- stdlib/ contains units imported explicitly via uses clause
- Single platform abstraction layer (rtl.platform.*) in runtime/,
  shared by both runtime/ and stdlib/ units
- bcl.testing renamed to blaise.testing across all 95+ test files
- Updated PasBuild project.xml, fixpoint.sh, README, unit search paths
- All 1931 tests pass, fixpoint OK (stage-3/stage-4)
2026-05-16 00:51:18 +01:00