Commit graph

18 commits

Author SHA1 Message Date
Graeme Geldenhuys bb2fe2da75 refactor: rename uCodeGen/uCodeGenQBE to blaise.codegen/blaise.codegen.qbe
Align the QBE backend unit names with the dotted naming convention
already used by the native backend (blaise.codegen.native.*).

  uCodeGen.pas    → blaise.codegen.pas
  uCodeGenQBE.pas → blaise.codegen.qbe.pas

Updated all uses clauses (~70 test files, main compiler units),
documentation references (README + 5 docs/*.adoc files), and
comment references in runtime/stdlib.
2026-06-10 12:12:14 +01:00
Graeme Geldenhuys e6b8e1e532 fix(semantic): type-check indirect-call arguments; doc record-param ABI
Indirect calls through a procedural-typed variable (the 'IsIndirectCall'
path added when 'RunTest(@DoTest)' first compiled) only validated arg
count.  The argument types were never checked against the procedural
signature, so e.g. 'H("oops")' against 'procedure(N: Integer)' compiled
silently and miscompiled at the QBE level.

Both call sites — TProcCall (statement form) and TFuncCallExpr
(expression form) — now run each actual through CheckTypesMatch against
the corresponding TProcParamInfo, and reject non-L-value actuals for
var-typed parameters with the same diagnostic as the regular-call path.

Two semantic tests in cp.test.proctypes.pas pin the behaviour: one for
the statement form, one for the expression form.

Docs

- language-rationale.adoc: update the overload-mangling example so the
  emitted symbol matches what codegen actually produces ($Log_D_Si, not
  $Log$Si), and document the QBEMangle escape table for the three
  sigil characters that the QBE symbol grammar disallows mid-identifier.
- design.adoc: clarify the var-parameter ABI entry — record and static
  array value parameters share the by-pointer convention with var-params,
  which is what makes IsVarParam the right flag for codegen to key off.
2026-05-05 00:42:11 +01:00
Graeme Geldenhuys 1ea878000d feat: multi-file compilation (v0.2.0)
Implement whole-programme multi-file compilation. The compiler now
resolves `uses` clauses, locates unit source files via `--unit-path`
search directories, compiles them, and merges exported symbols into
the programme scope. Combined QBE IR is emitted in dependency order.

New components:
- uUnitLoader: post-order DFS unit loader with cycle detection
  (EUnitNotFound, ECircularDependency); skips FPC RTL builtins
- TSemanticAnalyser.AnalyseUnitForExport: promotes unit interface
  symbols to global scope; implementation symbols stay scoped
- TCodeGenQBE.AppendUnit / AppendProgram: accumulate combined IR;
  FStrLitsEmitted tracks emitted string literals to avoid duplicates
- 8 new tests in cp.test.multifile covering loader, semantic export,
  and combined codegen

Language additions:
- `out` parameter modifier (treated as var — pass by reference)
- implementation-section `uses` clause in units

Blaise.pas wired to use loader pipeline; --unit-path flag is
repeatable; -Fu<path> FPC-style flags are honoured.

Design doc updated: Phase 4 (multi-file), phases 5–8 renumbered;
--unit-path added to CLI table; build-tool-drives-compiler principle
documented in constraints.

End-to-end verified: two-unit programme compiles and executes correctly.
2026-04-27 11:35:26 +01:00
Graeme Geldenhuys 29c32344f7 Mark Phase 3 complete; document Destroy hook and RTL rewrite
Updates the implementation status table to reflect:
- ARC for interface references: done (class/interface addref/release,
  [Weak] cycle-breaking, Free rewired to _ClassRelease).
- RTL rewrite under ARC rules: done (Destroy replaces Free on
  collections, EmitFieldCleanupFn Destroy dispatch, milestone updated).

Closes out all outstanding Phase 3 follow-up items.
2026-04-23 10:53:12 +01:00
Graeme Geldenhuys 0c09b4228c Document class-ownership decision: universal ARC on TObject
Record the decision (option A) in docs/design.adoc with full pros/cons
analysis of the three options considered. Update the Phase 3 status
table so interface-ref ARC reads "Planned — option A chosen" rather
than "Deferred — design-blocked". Mirror the commitment in README.adoc
under Design Philosophy and list the TObject/TInterfacedObject split
as dropped.
2026-04-23 00:08:55 +01:00
Graeme Geldenhuys 00e44e8876 Fix try/finally and try/except exception frame size (alloc16 32 → 512)
The exception frame was allocated as alloc16 32 (32 × 16 = 512 bytes on paper,
but QBE's alloc16 N means N items of 16 bytes: so 32 × 16 = 512 is actually
correct). Wait - re-reading: alloc16 N in QBE allocates N bytes aligned to 16.
So alloc16 32 was only 32 bytes — far less than sizeof(BlaiseExcFrame) which
needs ~216 bytes on Linux x86_64 (200-byte jmp_buf + two pointer fields).

setjmp writes its full jmp_buf into the undersized slot, silently corrupting
whatever sat above it on the stack: saved registers, local variables, and the
virtual method pointer loaded for virtual dispatch. Any try block followed by
a virtual method call in expression position crashed with a bad function pointer.

Fix: alloc16 32 → alloc16 512 in both EmitTryFinallyStmt and EmitTryExceptStmt,
matching the RTL contract documented in blaise_exc.c lines 8-13.

Two new regression tests assert alloc16 512 appears in emitted IR for both
try forms. 628 tests pass (was 626).

Also adds tests/phase2_milestone.pas: linked list with virtual dispatch,
inheritance, try/finally, and 'is' type test — zero valgrind leaks.
Updates design.adoc Phase 3 status table to reflect current implementation.
2026-04-22 18:22:48 +01:00
Graeme Geldenhuys 7e2f97f9ef Add pointer type infrastructure: ^T types, dereference, GetMem/FreeMem/ReallocMem
- uSymbolTable: tyPointer kind, TPointerTypeDesc(BaseType), GetMem/FreeMem/ReallocMem builtins
- uLexer/uPasTokeniser: tkCaret token for '^'; suppress ^X string escape (not in Clean Pascal)
- uParser: ParseTypeName handles '^TypeName' prefix (uses Self. for explicit recursion);
  TDerefExpr for P^ postfix; TPointerWriteStmt for P^ := V lhs
- uAST: TDerefExpr, TPointerWriteStmt nodes
- uSemantic: FindTypeOrInstantiate creates TPointerTypeDesc on demand for '^T';
  AnalyseDerefExpr, AnalysePointerWriteStmt; GetMem/ReallocMem/type-cast handling
- uCodeGenQBE: tyPointer→'l', EmitPointerWrite (storew/storel), TDerefExpr (loadw/loadl),
  GetMem→malloc, FreeMem→free, ReallocMem→realloc, pointer arithmetic (extsw + add/sub),
  type-cast via copy
- 15 new tests (556 total), all passing
2026-04-22 07:42:55 +01:00
Graeme Geldenhuys b34bfc574d Implement Phase 3 generics monomorphization (Delphi syntax)
Parse generic class declarations (`TBox<T>`) as TGenericTypeDef nodes.
Introduce one-token parser lookahead to disambiguate `<` in type
positions vs. comparison operators.

Demand-driven instantiation: when a var declaration references
`TBox<Integer>`, InstantiateGeneric clones the class AST with type-param
substitution, resolves all field/method types, analyses method bodies
with the concrete class type in scope, and registers the instance in
TProgram.GenericInstances.  Instantiated type symbols are defined in
the global scope so they survive inner scope pops.

Codegen emits typeinfo, vtable, and method bodies for each generic
instance using QBEMangle (`TBox<Integer>` → `TBox_Integer`).

19 new tests in cp.test.generics cover parser, semantic, and codegen
paths.  All 513 tests pass.
2026-04-21 23:02:23 +01:00
Graeme Geldenhuys ffcb1a041f Update Phase 3 interface status and design notes in design doc
Mark is/as interface operators, IInterface built-in, and 2-field typeinfo
(with impllist) as Done.  Expand TYPEID entry to cover the impllist
data layout.
2026-04-21 21:51:24 +01:00
Graeme Geldenhuys c371272811 Implement interface type declarations, class implements, and interface dispatch
Parser: TInterfaceTypeDef AST node; ParseInterfaceDef; ParseClassDef extended
to parse comma-separated implements list (first name = parent class, rest =
interfaces); ParseMethodDecl body remains optional.

Symbol table: tyInterface added to TTypeKind; TInterfaceTypeDesc with unsorted
(declaration-order) method list for correct itab slot indexing; FImplements
non-owning list on TRecordTypeDesc tracks class→interface pairs; TObject
pre-registered as built-in root class.

Semantic: AnalyseTypeDecls handles TInterfaceTypeDef (register, inherit parent
methods); verifies class implements all interface methods; CheckTypesMatch
extended for class→interface assignment; AnalyseMethodCall handles tyInterface
object vars via itab dispatch path; TAssignment carries ResolvedLhsType.

Codegen: EmitInterfaceDefs emits $typeinfo_IFoo and $itab_TFoo_IFoo data blocks;
EmitVarAllocs allocates two-slot fat pointer (_obj + _itab) for interface vars;
EmitAssignment stores both obj pointer and itab address on interface assignment;
EmitMethodCall dispatches via itab on tyInterface receiver.

15 new tests, 484 total, 0 failures.
2026-04-21 18:24:55 +01:00
Graeme Geldenhuys c13901a2e2 Add Phase 3 implementation status section to design doc
Covers interfaces (10 items) and generics (6 items), each as a separate
subsection with Pending status rows and implementation notes. Records the
agreed implementation order (interfaces before generics) and the Phase 3
milestone (TList<Integer> + TDictionary<string,Integer>, zero valgrind leaks).
2026-04-21 17:56:04 +01:00
Graeme Geldenhuys 4e54c12cad Move macOS ARM64 target to Phase 5 alongside self-hosting
Cannot be tested on Linux; Darwin linking has enough quirks (libSystem,
fat-binary RTL) that shipping untested support would just break the first
macOS user. Phase 5 is the right home — LLVM backend work and CI/CD pipeline
for all platforms are already there.

Phase 2 is now fully complete on Linux x86_64.
2026-04-21 17:35:08 +01:00
Graeme Geldenhuys 5840346a98 Add separate method implementations and Free built-in; Phase 2 milestone reached
Separate method implementations: `procedure TFoo.Bar(...)` can now appear
outside the class definition. Parser detects qualified names in ParseMethodDecl
and makes the body optional for forward-only class declarations. Semantic pass
links standalone bodies back to class method declarations via LinkClassMethodImpls
before AnalyseMethodBodies runs, so all existing method analysis and codegen paths
are reused unchanged.

Free built-in: `Obj.Free` with no user-defined Free method emits `call $free(l ptr)`.
Semantic pass recognises the call and sets ResolvedMethod := nil as a signal;
codegen handles nil method as a built-in free before the normal dispatch path.

Phase 2 milestone verified: a linked list using TNode (TObject subclass) with
separate method impls, Create, and Free compiles and runs with zero valgrind errors
(3 allocs, 3 frees, 0 bytes in use at exit).

Design doc updated: new rows for is/as, ARC exception cleanup, separate method
impls, and Free built-in; Immediate Next Steps trimmed to macOS ARM64 only.

469 tests, 0 failures.
2026-04-21 17:33:39 +01:00
Graeme Geldenhuys c1d53e7be6 Implement setjmp/longjmp-based exception dispatch in QBE IR
- Add blaise_exc.c to RTL: _PushExcFrame, _PopExcFrame, _Raise,
  _CurrentException, _Reraise using thread-local setjmp/longjmp frames
- Update rtl/Makefile to compile and archive blaise_exc.c
- Rewrite EmitTryFinallyStmt: alloc16 frame on stack, push+setjmp at try
  entry, finally body emitted on both normal and exception paths, _Reraise
  propagates to enclosing handler on exception path
- Rewrite EmitTryExceptStmt: same frame setup, except handler entered via
  jnz on setjmp return, _PopExcFrame before handler body
- Add 6 codegen tests for setjmp-based dispatch patterns (460 tests total)
- Update design doc: mark try/except/finally as Done, update next steps
2026-04-21 14:07:04 +01:00
Graeme Geldenhuys bca985e8cd Update Phase 2 implementation status in design doc
Reflect completed work: class methods, var parameters, unit
interface/implementation, full ARC (compiler + RTL), and string
concatenation. Mark try/except/finally as partial. Update immediate
next steps to virtual dispatch, real exception unwinding, and macOS target.
2026-04-20 23:44:37 +01:00
Graeme Geldenhuys 0df5514623 Add Phase 2: record/class types, ARC strings, RTL stubs, and grammar doc
Symbol table:
- Added tyClass kind and NewClassType factory; TRecordTypeDesc now
  accepts an optional kind parameter so records and classes share the
  same descriptor with distinct semantics.

Semantic analyser:
- AnalyseTypeDecls runs before PushScope so type symbols land in global
  scope and survive PopScope.
- Handles TRecordTypeDef and TClassTypeDef; sets IsConstructorCall on
  TypeName.Create expressions and IsClassAccess on class-variable field
  access and assignment nodes.

Code generator (QBE IR):
- Class variables: 8-byte pointer slot, zeroed on entry.
- Constructor calls: malloc(sizeof fields), store pointer.
- Class field access/write: load pointer, add field offset, load/store.
- ARC string assignment: AddRef new value, Release old value, then store.
- Block exit: Release every string variable in scope (EmitStringCleanup).

Lexer / Parser / AST:
- Added tkClass keyword and ParseClassDef; FieldDecl parsing refactored
  into ParseFieldDecl(AFields) shared by both record and class.
- AST gains TClassTypeDef, and IsConstructorCall/IsClassAccess flags on
  TFieldAccessExpr and TFieldAssignment.

RTL:
- blaise_arc.c: Phase 2 no-op stubs for _StringAddRef / _StringRelease.
- rtl/Makefile: builds blaise_rtl.a; `make install` copies it next to
  the compiler binary for automatic discovery by FindRTL.
- Blaise.pas driver: FindRTL checks BLAISE_RTL env var then binary dir;
  links the RTL archive when present.

Tests:
- 180 unit tests (records ×23, classes ×22, ARC ×9, codegen ×9, …).
- 4 end-to-end integration tests in tests/integration/test_arc_strings.sh
  covering string assignment, two-var cleanup, reassignment cycle, and
  empty-program linkage.

Docs:
- design.adoc updated with Phase 2 implementation status table.
- docs/grammar.ebnf: new authoritative EBNF grammar for the Blaise
  language as implemented, including ARC semantic annotations.
2026-04-20 19:04:12 +01:00
Graeme Geldenhuys 3fce5f86de Phase 1 bootstrap: Blaise compiler skeleton
Renames the project from 'Clean Pascal' to Blaise (after Blaise Pascal).
Adds the Phase 1 compiler pipeline with TDD test suite:

- uPasTokeniser: general Pascal tokeniser (ported from fpGUI IDE)
- uLexer: compiler-specific adapter — filters whitespace/comments,
  maps to TTokenKind, unescapes string literals
- uAST: typed AST node hierarchy (TProgram, TBlock, TBinaryExpr, etc.)
- uParser: recursive-descent parser for the Phase 1 BNF grammar
- uCodeGenQBE: QBE IR emitter — WriteLn/Write built-ins, integer
  variables, arithmetic, string literals
- Blaise.pas: compiler driver — parses flags, shells to qbe+cc
- FPCUnit test suites for lexer, parser, and code generator
2026-04-20 15:35:50 +01:00
Graeme Geldenhuys fd79a7db98 Initial project scaffold
PasBuild multi-module layout with three modules: compiler (application),
rtl (library), and tools/migration-analyser (application). Includes root
aggregator project.xml, debug/release build profiles, BSD 3-Clause licence,
README, .gitignore, and design document. QBE vendored as the Phase 1
backend. Bootstrap requires FPC 3.2.2.
2026-04-20 14:22:10 +01:00