← board

Interface reference counting (IInterface / ARC)

Decision (2026-06-19) — greenlit despite the earlier defer

Not a blocker (the reality-check below stands: no RTL we port depends on it). Greenlit anyway because the cost collapsed once the substrate landed — CORBA interfaces (fat-pointer + IMT + is/as/Supports + by-ref ABI, all 4 targets) and the full managed-lifetime machinery are done, and ARC interfaces are the same shape as managed strings with a different finalizer (_Release through the IMT instead of the string decref). Doing it also retires the "ARC later" caveat that keeps reappearing across interface/object/zero-init tickets. Framed as a now-cheap idiom, not an unblock.

What this is (and is NOT)

This is the language feature of automatically reference-counted interfaces — IInterface/IUnknown with compiler-inserted _AddRef/_Release, deterministic lifetime, no manual .Free. It is not Windows COM (no IUnknown binary ABI across DLLs, no registry, no CoCreateInstance, no marshalling/apartments). The "COM ARC" label on the old feature-interfaces ticket conflated the two; on Linux the binary-COM machinery has no use, but ARC interfaces are FPC/Delphi/Lazarus bread-and-butter and fully platform-independent.

Reality check — do we need it? (2026-06-19)

No, not soon. Initial claim that "the FPC RTL leans on interfaces heavily" was wrong (checked: fpjson, fgl, most fcl-* are plain class OOP with manual Free). FPC core RTL uses interfaces only narrowly: Classes (IInterface/IUnknown, TInterfacedObject, IInterfaceList, IFPObserved/IFPObserver), the variants plumbing, and the Windows-only ActiveX/COM units. The genuine interface+ARC payoff lives in the Delphi ecosystem — smart-pointer/auto-free idiom, DI containers (Spring4D) — not in anything PXX plans to port.

So the value here is one nice-to-have idiom (deterministic auto-free via interface value lifetime), not an RTL dependency. The CORBA surface already done (2026-06-19) covers polymorphic abstraction. This stays parked until a concrete need appears.

Scope

Substrate already in place (reuse, don't rebuild)

Locked decisions (2026-06-19)

These resolve the prior "open questions"; implement to these, don't re-litigate.

  1. Mode selection. {$interfaces com} opt-in directive enables ARC; PXX default stays corba (no refcounting) — zero behaviour change for existing code. {$interfaces corba} is the explicit form of the current default.
  2. Refcount location. A refcount field in TInterfacedObject (FPC way), not a hidden side-table keyed off the instance.
  3. Release path. Through the IMT _Release slot. COM-mode interfaces reserve the three leading IMT slots in declaration order: QueryInterface, _AddRef, _Release. TInterfacedObject implements all three; _AddRef/_Release bump/drop the field and Free at zero; QueryInterface reuses the existing closed-world IMT lookup.
  4. ARC insertion points. Reuse the managed-local finalizer path. _AddRef on interface-typed assign / by-value param / function-result capture; _Release on overwrite, scope exit, exception unwind, and nil-assign. Casting a class to a COM interface calls _AddRef.
  5. Threadsafe. Atomic inc/dec on the refcount field under --threadsafe (mirror the managed-string atomic path).

Default-corba means the whole feature is gated behind {$interfaces com}, so a mistake cannot regress existing CORBA-interface or class code — a useful safety rail while landing it across 4 targets.

Acceptance

TInterfacedObject-derived class managed purely through interface variables (no manual Free) frees exactly once at the last reference drop; ARC correct across assign / param / result / scope-exit / exception; self-host + cross-bootstrap byte-identical.

Log

Implementation (2026-06-19) — DONE

Landed across three commits (ce95f52 x86-64 core, 798bc4f cross targets, bb286df exception-unwind + result). All five locked decisions implemented to spec; default stays corba so existing code is untouched and the compiler's own self-host is byte-identical (it uses no COM interfaces).

How it works

LANDMINES found

Acceptance — met

test_interface_arc (single / shared-alias / nil-reassign) freed exactly once on all 4 targets; wired into test-core + i386/aarch64/arm32 cross suites. test_interface_arc_exc (result transfer / reassign-releases-old / exception unwind) green on x86-64 (test-core). self-host byte-identical.