Commit graph

8 commits

Author SHA1 Message Date
Graeme Geldenhuys 6711a6cfdd fix: green the test suite — array-const link collisions and TComponent ARC
Three fixes that were causing six e2e failures:

Local array consts collided at link time. Function/block/unit-level
`const X: array[...] = (...)` were emitted as `export data $X` with no
mangling. The RTL's own `Days` const (rtl.platform.posix.pas) then
clashed with any user program declaring a `Days` const
(`multiple definition of 'Days'`). Local array consts now use a mangled,
file-local label (`__bac_N_Name`): the mangled name is set in semantic
(AnalyseArrayConstDecls -> ResolvedQbeName / ConstArrayQbe), carried to
the bare-ident read path via TIdentExpr.ConstArraySymbol, and emitted as
non-exported `data` so two separately-compiled objects cannot clash.
Class/record consts keep their exported, type-qualified label.

TComponent fought ARC and crashed. The destructor force-Freed children
it held only as raw pointers, while a caller's variable still referenced
them — a use-after-free at scope exit, surfacing as infinite
Destroy->RemoveComponent->ClassRelease recursion. Ownership is now honest
with ARC: FOwner is [Unretained]; AddComponent takes a strong ref via
_ClassAddRef; Destroy releases the owner's hold rather than Freeing, so a
child the caller still holds survives and an unheld child is destroyed;
RemoveComponent only unlinks the slot (the dying child's ref already
reached zero).

Deflaked TestRun_Thread_Terminate_Flag. The worker could finish via its
`I > 1000` break before the main thread's Terminate landed, racing the
printed flag between 0 and 1. The loop now exits only via the terminate
flag (with a high safety cap), so FTerminated is deterministically True.

Unit tests in cp.test.constants.pas updated to assert the mangled,
non-exported label.
2026-06-03 01:49:00 +01:00
Graeme Geldenhuys 576f60d807 fix(stdlib): TStringList.Text/LoadFromFile preserve verbatim lines
SplitIntoList — the sole backend for TStringList.SetText and LoadFromFile —
trimmed leading and trailing spaces from each split segment. That is correct
for CSV-style splitting but wrong for line-splitting: a TStringList must store
each line verbatim, including indentation.

Removed the trimming so each segment is added as Copy(S, Start, Len) untouched.
SplitIntoList has exactly one caller (SetText), so no trimming-dependent code
is affected.

This also fixes a latent test-infrastructure bug: the threaded test runner
parses each subprocess suite's stdout via TStringList.Text and identifies
failure-detail lines by their two-space indentation. With indentation stripped,
those lines never matched, AddFailure was never called, and the merged result
reported zero failures — so the plain summary printed OK even when [Threaded]
suites failed. With lines preserved, the summary now correctly reports failures.

Regression test: TestRun_TextSet_PreservesLeadingWhitespace asserts on raw
stdout so the spaces inside [] are checked without round-tripping through Text.

Self-hosting fixpoint verified clean.
2026-06-03 00:40:00 +01:00
Graeme Geldenhuys 99b7761bf4 refactor: use Exit(X) shorthand for Result-then-Exit early returns
Now that Exit(X) is supported (0f63626), replace the historical
'Result := X; Exit;' workaround with the Exit(X) function-result shorthand
across compiler, runtime, and stdlib (305 sites).

Only same-indentation pairs are merged: a 'Result := X;' immediately followed
by an 'Exit;' at the same indentation level. Sites where the trailing 'Exit;'
is less-indented than the assignment are left untouched — there the Exit
belongs to an enclosing for/else/if block, not to the assignment, so merging
would change control flow (e.g. the xor/and numeric-result branch in
uSemantic falling through to the Boolean-operand check). 14 such sites are
deliberately not converted.

Three converted sites sit inside a real try/finally (uSemantic overload/
generic resolution); each try body already contained bare Exit; statements
and its finally body is a plain local-list Free, so control flow is unchanged.
A new e2e test (TestRun_ExitValueThroughFinally) confirms Exit(X) from inside
a try/finally runs the finally body and returns the value.

Self-hosting fixpoint verified clean (stage-2 IR == stage-3 IR).
2026-06-03 00:33:50 +01:00
Graeme Geldenhuys 8136fcaff7 perf(stdlib): rewrite GetText and GetCommaText to avoid O(N²) concatenation
Both methods accumulated a result string with repeated `Result := Result + …`
inside a loop — each iteration allocates a fresh buffer and copies the entire
accumulator forward, quadratic in total content size.

Rewrite both using TStringBuilder (geometric buffer growth, single ToString
at the end).  On a self-compile via --output the GetText hot path drops from
~17 s to ~1.7 s (~10× speedup); GetCommaText gets the same fix proactively.

Add TestRun_TextGet_ManyLines: 500-line list with mixed empty/non-empty entries,
spot-checks total length and key substrings after the rewrite.
2026-06-01 00:11:39 +01:00
Graeme Geldenhuys 90b15afce4 feat(stdlib): add TList<T>.IndexOf
Linear scan over FData returning the index of the first equal element
or -1 when not found.  Matches the semantics of TSet<T>.IndexOf which
the rest of the unit already exposes, and saves callers from writing
the hand-rolled scan that bench_lists.pas demonstrates.

Adds two e2e tests covering the integer/found and string/not-found
paths.
2026-05-21 15:23:35 +01:00
Graeme Geldenhuys ed7323a618 feat(stdlib): add TListEnumerator<T> + TList<T>.GetEnumerator
Wires up for..in on TList<T> from generics.collections.  Two compiler
fixes were required:

- uSemantic: TClassName<T>.Method(args) inside a generic method body
  failed to resolve because AnalyseMethodCallExpr looked up the literal
  name with the unsubstituted type parameter.  Now mirrors the existing
  field-access path: if the receiver name contains '<' and lookup fails,
  apply ResolveScopeBoundTypeParams + FindTypeOrInstantiate before
  re-trying the lookup.

- uCodeGenQBE: constructor-with-args path emitted
  $_FieldCleanup_TListEnumerator<String> with literal angle brackets
  which QBE rejects.  Mangle the class name like the no-arg path
  already does.
2026-05-21 10:25:19 +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 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