Move the OS integer constants (O_*/S_*/SEEK_*/CLOCK_REALTIME/WNOHANG) and the
struct stat layout out of rtl.platform.posix and behind a new TPlatformLayout
port. struct stat is now an opaque sized buffer whose Size/Mtime/Mode fields are
read at target offsets via StatSize/StatMtime/StatMode, rather than a record
that bakes in one layout — FreeBSD's stat differs in both offsets and size.
The Linux adapter (rtl.platform.layout.linux, TPlatformLayoutLinuxX86_64) supplies
today's values verbatim; struct stat offsets verified against sys/stat.h
(st_mode=24, st_size=48, st_mtim=88, sizeof=144). struct tm is left shared — its
used fields are identical on Linux/FreeBSD amd64.
The POSIX unit's one per-target wire is its 'uses rtl.platform.layout.linux';
the FreeBSD RTL archive will compose its own layout. No conditional compilation.
Step 0b of docs/native-target-architecture.adoc. Behaviour-preserving:
FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK;
full suite 3761 tests green; file I/O (FileExists/DirectoryExists/FileAge/
ReadFile) exercised end-to-end through the new accessors.
The runtime Makefile assembled each Pascal unit's object via `cc -c` (gcc as a
plain assembler). Pass `--assembler internal` so Blaise's own in-process
assembler produces the .o instead — removing gcc from all twelve Pascal RTL
unit builds.
The four hand-written .s files in src/main/asm still go through `cc -c` until
Blaise can assemble a standalone .s file (or the asm moves into inline `asm`
blocks); the Pascal units no longer touch gcc.
Verified: all four fixpoints (which rebuild + install the RTL via this rule)
pass, and both QBE- and native-built test runners pass (3758 tests).
The native internal linker linked every program against system CRT startup
objects — Scrt1.o for _start plus crtbeginS.o/crtendS.o/crti.o/crtn.o —
located by scanning a hard-coded list of multiarch and versioned gcc
directories (/usr/lib/gcc/<triple>/<ver>/). That layout varies by distro, so
the scan failed with "internal linker: CRT startup objects not found" on
distributions whose triple/gcc version was not listed (issue #142, Mageia 9).
Asking gcc (cc -print-file-name) would locate them portably but reintroduces a
link-time gcc dependency, defeating the internal linker's purpose
(toolchain independence). Instead, follow FPC's approach: the runtime now
supplies its OWN _start (runtime/src/main/asm/blaise_start_x86_64.s), which
marshals argc/argv and tail-calls __libc_start_main(main, ...) — the modern
glibc convention (init/fini = NULL; glibc runs the init array itself).
Blaise consumes nothing else from the CRT set: main is a plain C-style entry,
unit init runs explicitly from main, and the emitted code references no
crtbegin/crtend symbol. So the internal linker now needs no gcc-provided
object and no versioned gcc directory; FindCrtObjects and its directory lists
are removed.
The new _start ships inside blaise_rtl.a; AddArchive includes all members
unconditionally, so the entry symbol is always present.
Tests: two TInternalAsmE2ETests cases drive the REAL compiler binary through
the internal assembler + internal linker (the only harness that exercises the
runtime _start; the e2e suite links via cc) and assert the two things _start
owns — exit-code propagation (Halt(7)) and argc/argv forwarding (ParamCount/
ParamStr). The native fixpoints already link the compiler itself via this path.
Design note: docs/self-contained-start-design.adoc.
Compiling a UNIT (top source is a `unit`, so the pipeline runs in unit-mode and
Prog stays nil) via the default incremental path segfaulted: the incremental
worker setup unconditionally read `Prog.SymbolTable` to seed each dep worker,
dereferencing nil. In unit-mode the symbol table comes from the semantic pass,
not a program node. This is exactly the invocation the runtime Makefile drives
(`blaise --source X.pas --output X.o` per RTL unit), so it broke `make` in
runtime/ once incremental became the default; it was a latent pre-existing bug
in the opt-in incremental path (reproduces at de0ea5d with --incremental).
Fix: seed the worker symbol table from Semantic.GetSymbolTable() when Prog is
nil, matching the non-incremental unit-mode path.
Also pin the runtime Makefile to --no-incremental. That Makefile is itself a
hand-managed separate-compilation system (one object per unit + an explicit
archive list); the compiler's incremental mode would additionally write
per-dependency side-effect objects and skip inlining a used unit's bodies, but
those side-effect objects are not in the archive list — so a cross-unit symbol
(e.g. typeinfo_TRtlPlatform, referenced by the derived TRtlPlatformPosix in a
different unit) was left undefined at link. Whole-program per unit keeps each
unit object self-contained.
Regression test (cp.test.e2e.sepcompile.pas): compile a unit that uses another
unit in unit-mode via the default incremental path, then build and run a
program over the emitted object.
The native x86-64 backend is now the default (bkNative). QBE remains
available via --backend qbe. Several codegen bugs that were invisible
while QBE was the default are fixed in this commit:
Native backend fixes:
- EmitLeaqGlobal helper: threadvar globals now emit the correct
fs:0 + @tpoff TLS sequence instead of bare %rip-relative leaq.
Fixes static array write (FreeLists), string subscript assignment,
EmitExprAddr, record memcpy, method-ptr cast, and open-array push
paths that all used raw leaq %s(%rip).
- EmitIncDec: unified local/global paths through VarOperand so
threadvar scalar Inc/Dec (e.g. LargeFreeCount) gets TLS addressing.
- EmitIncDec FAE.Base: Inc(P^.Field) where P is a pointer now
evaluates the base expression instead of leaq on the stack slot.
- FinalizeEmit: data section (.data/.bss/.rodata) now emits once at
the end of unit compilation via FinalizeEmit, not inside each
EmitUnit call. Fixes duplicate/missing string literals in sep-compile.
- EmitFunctionDef AExported: implementation-only helpers in monolithic
mode stay file-local (.globl suppressed) to avoid symbol collisions
with the RTL archive.
QBE backend fixes:
- AppendUnit typeinfo: export prefix added to data $typeinfo_TObject,
$typeinfo_TCustomAttribute, $vtable_TObject, $vtable_TCustomAttribute
so native-built RTL can resolve them.
- FSuppressSystemDefs: decoupled from FExportAll so CreateUnitCodeGen
can export all symbols without suppressing system defs.
Infrastructure:
- runtime/Makefile: BLAISE_FLAGS variable for passing extra compiler
flags (e.g. --backend qbe) from build scripts.
- fixpoint.sh: make clean before RTL rebuild to pick up backend change.
Extend set of <enum> from a 64-member cap to 256, Java-EnumSet style. Sets
of 64 members or fewer keep the existing single-register bitmask (QBE w/l);
sets of 65..256 members ('jumbo') become an inline byte-array bitmap of
ceil(N/8) bytes, treated as a value aggregate (passed by reference, returned
via sret, memset/memcpy), with operations performed by new RTL helpers.
Representation (uSymbolTable): TSetTypeDesc.IsJumbo (BitCount > 64) and
RawByteSize; RawSize/ByteSize/AllocAlign sized accordingly. TSymbol gains
ConstSetBytes/ConstSetQbe for jumbo constants (can't fit an Int64 mask).
RTL: new runtime/src/main/pascal/blaise_set.pas — _SetIn/_SetInclude/
_SetExclude/_SetUnion/_SetInter/_SetDiff/_SetEqual/_SetCopy over byte-array
bitmaps (overlap-safe). Wired into runtime/Makefile.
Semantic: the four >64 caps become >256. AnalyseSetConstDecl folds a jumbo
const to a byte array. The anonymous set for 'X in [a,b,c]' is sized to the
largest listed ordinal (when constant), not the full enum — keeping the
common low-ordinal membership test (incl. the compiler's own TokenKind
tests) on the fast register path. This also fixes a latent miscompile: the
old fixed-l representation silently dropped any listed ordinal >= 64.
Codegen (both backends): jumbo branches for literal, in, +/-/*, =/<>,
Include/Exclude, for-in, assignment, params (pmJumboSetValue ABI), and sret
returns. The register 'in' gained a range guard for literal-sized sets.
Native reserves two 32-byte scratch slots per frame (and .bss in main) for
set-op/literal result buffers. QBE adds IsAggregateAddrType so jumbo sets
ride the record/static-array address paths and are never promoted.
OPDF: no format change — recSet SizeInBytes (1 byte) already covers <=32.
Verified: FIXPOINT_OK and NATIVE_FIXPOINT_OK; full suite (3172 tests) green
built by the stage-2 binary, the QBE fixpoint binary, and the native
fixpoint binary; bif-coverage OK. Tests: cp.test.jumboset (12 IR) and
cp.test.e2e.jumboset (6 e2e, both backends via AssertRunsOnAll); the
>256-member rejection test in cp.test.sets updated. Docs: grammar.ebnf and
language-rationale set-type sections updated for the 256 cap and literal
sizing.
Closes the design discussion behind #81.
CodePointLength now delegates to _Utf8CountCodePoints in hand-written
x86_64 assembly. Processes 32 bytes/iteration with AVX2 (runtime-
detected via CPUID) and 16 bytes/iteration with SSE2, falling back to
scalar for tail bytes. The algorithm counts non-continuation bytes
(bytes where (b & 0xC0) != 0x80) using signed pcmpgtb against -65.
Replace plain read-modify-write refcount operations with atomic
lock xadd instructions via a new blaise_atomic_x86_64.s assembly
stub. _AtomicAddInt32 and _AtomicSubInt32 return the previous
value, so exactly one thread sees the transition to zero and
triggers destruction -- eliminating the TOCTOU race in
_ClassRelease and _StringRelease.
Affected operations:
- _ClassAddRef: atomic increment
- _ClassRelease: atomic decrement, destroy on old_rc == 1
- _StringAddRef: atomic increment (immortal check unchanged)
- _StringRelease: atomic decrement, free on old_rc == 1
Now that the compiler supports `blaise --source Unit.pas --output Unit.o`
directly, the build-driver pattern (a thin program wrapper + IR sed strip)
is no longer needed. Replace every multi-step rule with a single invocation
and delete the eight *_build_driver.pas files.
This commit must follow the merge of blaise_clean_split in git history so
that the rolling-bootstrap script can build the merge commit using the
previous binary (which still uses the old Makefile), then build this commit
using the freshly-merged binary (which understands unit-as-top-level).
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.
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).
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.
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.
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.
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).
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.
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.
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.
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.
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)