290 lines
7.8 KiB
ObjectPascal
290 lines
7.8 KiB
ObjectPascal
{
|
|
Blaise - An Object Pascal Compiler
|
|
Copyright (c) 2026 Graeme Geldenhuys
|
|
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
|
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
|
See LICENSE file in the project root for full license terms.
|
|
}
|
|
|
|
{
|
|
Blaise RTL — Zeroing weak references (Pascal port of blaise_weak.c)
|
|
|
|
A weak reference is a slot (variable or field) pointing at a class
|
|
instance without contributing to its refcount. When the backing
|
|
object's last strong reference is released and the object is about to
|
|
be freed, every registered weak slot pointing at it is nil'd -- so a
|
|
subsequent dereference sees nil rather than dangling memory.
|
|
|
|
Design: one global open-chained hash table, keyed on the user_ptr of
|
|
the backing object. Each entry carries a linked list of slot
|
|
addresses (pointers-to-pointers) that reference it. Inserts and
|
|
removals are O(bucket-chain + slot-list) -- acceptable because weak
|
|
references are rare relative to strong references.
|
|
|
|
Thread safety: a single pthread mutex protects all table mutations.
|
|
The lock is coarse-grained but sufficient because weak references
|
|
are infrequent relative to strong ARC operations.
|
|
|
|
API:
|
|
_WeakAssign(slot, new_target) -- register slot under new_target and
|
|
store new_target at *slot; any prior
|
|
registration for *slot is removed.
|
|
new_target may be nil (just unregister).
|
|
_WeakClear(slot) -- unregister and zero *slot. Called at
|
|
scope exit and field cleanup for weak
|
|
declarations.
|
|
_WeakZeroSlots(target) -- nil every slot pointing at target and
|
|
drop target's entry from the table.
|
|
Called from _ClassRelease at refcount
|
|
zero, before the field-cleanup fn
|
|
runs and before the block is freed.
|
|
}
|
|
|
|
unit runtime.weak;
|
|
|
|
interface
|
|
|
|
procedure _WeakAssign(Slot: Pointer; NewTarget: Pointer);
|
|
procedure _WeakClear(Slot: Pointer);
|
|
procedure _WeakZeroSlots(Target: Pointer);
|
|
|
|
{ RTL startup hook — every Blaise program's main calls this once, before its
|
|
body, to run RTL initialisation that the per-unit `initialization` sections
|
|
do not: units linked from a prebuilt archive never get their _init called,
|
|
only units compiled into the program's own TU do. Currently this sets up
|
|
the weak-reference table mutex; other RTL one-time setup belongs here too
|
|
as it arises. }
|
|
procedure _BlaiseInit;
|
|
|
|
implementation
|
|
|
|
const
|
|
WEAK_BUCKETS = 256;
|
|
|
|
type
|
|
PWeakSlot = ^TWeakSlot;
|
|
TWeakSlot = record
|
|
Addr: Pointer;
|
|
Next: PWeakSlot;
|
|
end;
|
|
|
|
PWeakEntry = ^TWeakEntry;
|
|
TWeakEntry = record
|
|
Target: Pointer;
|
|
Slots: PWeakSlot;
|
|
Next: PWeakEntry;
|
|
end;
|
|
|
|
function _BlaiseGetMem(Size: Integer): Pointer; external name '_BlaiseGetMem';
|
|
procedure _BlaiseFreeMem(Ptr: Pointer); external name '_BlaiseFreeMem';
|
|
function pthread_mutex_init(Mutex: Pointer; Attr: Pointer): Integer;
|
|
external name 'pthread_mutex_init';
|
|
function pthread_mutex_lock(Mutex: Pointer): Integer;
|
|
external name 'pthread_mutex_lock';
|
|
function pthread_mutex_unlock(Mutex: Pointer): Integer;
|
|
external name 'pthread_mutex_unlock';
|
|
|
|
var
|
|
WeakTable: array[0..255] of PWeakEntry;
|
|
WeakMutex: array[0..5] of Int64;
|
|
WeakMutexReady: Boolean; { False until WeakMutex has been mutex_init'd }
|
|
|
|
{ Initialise WeakMutex exactly once. Driven by _BlaiseInit at program
|
|
startup; the WeakMutexReady guard makes it idempotent so the foot-of-unit
|
|
`initialization` block (which runs only when blaise_weak is compiled into
|
|
the program's own TU) and _BlaiseInit cannot double-init. Initialising
|
|
explicitly matters because a zero-filled mutex object is not a valid
|
|
unlocked mutex on every platform — where it happens to be, skipping the
|
|
init merely worked by luck. The guard is non-atomic, consistent with the
|
|
weak table's single-threaded-RTL assumption. }
|
|
procedure EnsureWeakMutex;
|
|
begin
|
|
if not WeakMutexReady then
|
|
begin
|
|
pthread_mutex_init(@WeakMutex, nil);
|
|
WeakMutexReady := True;
|
|
end;
|
|
end;
|
|
|
|
{ Program-startup hook (see interface comment). Runs the RTL's one-time
|
|
setup that the per-unit init dispatch misses for archive units.
|
|
Idempotent — safe even if a future caller invokes it twice. }
|
|
procedure _BlaiseInit;
|
|
begin
|
|
EnsureWeakMutex();
|
|
end;
|
|
|
|
function WeakHash(Ptr: Pointer): Integer;
|
|
begin
|
|
Result := Integer((PtrUInt(Ptr) shr 4) and PtrUInt(WEAK_BUCKETS - 1));
|
|
end;
|
|
|
|
function WeakFindOrCreate(ATarget: Pointer): PWeakEntry;
|
|
var
|
|
H: Integer;
|
|
E: PWeakEntry;
|
|
begin
|
|
H := WeakHash(ATarget);
|
|
E := WeakTable[H];
|
|
while E <> nil do
|
|
begin
|
|
if E^.Target = ATarget then
|
|
begin
|
|
Exit(E);
|
|
end;
|
|
E := E^.Next;
|
|
end;
|
|
E := PWeakEntry(_BlaiseGetMem(24));
|
|
if E = nil then
|
|
begin
|
|
Exit(nil);
|
|
end;
|
|
E^.Target := ATarget;
|
|
E^.Slots := nil;
|
|
E^.Next := WeakTable[H];
|
|
WeakTable[H] := E;
|
|
Result := E;
|
|
end;
|
|
|
|
procedure WeakUnregister(ASlot: Pointer);
|
|
var
|
|
PP: ^Pointer;
|
|
CurTarget: Pointer;
|
|
H: Integer;
|
|
E: PWeakEntry;
|
|
Prev: PWeakSlot;
|
|
Cur: PWeakSlot;
|
|
Dead: PWeakSlot;
|
|
begin
|
|
PP := ASlot;
|
|
CurTarget := PP^;
|
|
if CurTarget = nil then
|
|
Exit;
|
|
H := WeakHash(CurTarget);
|
|
E := WeakTable[H];
|
|
while E <> nil do
|
|
begin
|
|
if E^.Target = CurTarget then
|
|
begin
|
|
Prev := nil;
|
|
Cur := E^.Slots;
|
|
while Cur <> nil do
|
|
begin
|
|
if Cur^.Addr = ASlot then
|
|
begin
|
|
Dead := Cur;
|
|
if Prev = nil then
|
|
E^.Slots := Dead^.Next
|
|
else
|
|
Prev^.Next := Dead^.Next;
|
|
_BlaiseFreeMem(Dead);
|
|
Exit;
|
|
end;
|
|
Prev := Cur;
|
|
Cur := Cur^.Next;
|
|
end;
|
|
Exit;
|
|
end;
|
|
E := E^.Next;
|
|
end;
|
|
end;
|
|
|
|
procedure _WeakAssign(Slot: Pointer; NewTarget: Pointer);
|
|
var
|
|
PP: ^Pointer;
|
|
E: PWeakEntry;
|
|
S: PWeakSlot;
|
|
begin
|
|
if Slot = nil then
|
|
Exit;
|
|
pthread_mutex_lock(@WeakMutex);
|
|
WeakUnregister(Slot);
|
|
PP := Slot;
|
|
PP^ := NewTarget;
|
|
if NewTarget <> nil then
|
|
begin
|
|
E := WeakFindOrCreate(NewTarget);
|
|
if E = nil then
|
|
begin
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
Exit;
|
|
end;
|
|
S := PWeakSlot(_BlaiseGetMem(16));
|
|
if S = nil then
|
|
begin
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
Exit;
|
|
end;
|
|
S^.Addr := Slot;
|
|
S^.Next := E^.Slots;
|
|
E^.Slots := S;
|
|
end;
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
end;
|
|
|
|
procedure _WeakClear(Slot: Pointer);
|
|
var
|
|
PP: ^Pointer;
|
|
begin
|
|
if Slot = nil then
|
|
Exit;
|
|
pthread_mutex_lock(@WeakMutex);
|
|
WeakUnregister(Slot);
|
|
PP := Slot;
|
|
PP^ := nil;
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
end;
|
|
|
|
procedure _WeakZeroSlots(Target: Pointer);
|
|
var
|
|
H: Integer;
|
|
Prev: PWeakEntry;
|
|
E: PWeakEntry;
|
|
Dead: PWeakEntry;
|
|
S: PWeakSlot;
|
|
NS: PWeakSlot;
|
|
PP: ^Pointer;
|
|
begin
|
|
if Target = nil then
|
|
Exit;
|
|
pthread_mutex_lock(@WeakMutex);
|
|
H := WeakHash(Target);
|
|
Prev := nil;
|
|
E := WeakTable[H];
|
|
while E <> nil do
|
|
begin
|
|
if E^.Target = Target then
|
|
begin
|
|
Dead := E;
|
|
S := Dead^.Slots;
|
|
while S <> nil do
|
|
begin
|
|
NS := S^.Next;
|
|
PP := S^.Addr;
|
|
PP^ := nil;
|
|
_BlaiseFreeMem(S);
|
|
S := NS;
|
|
end;
|
|
if Prev = nil then
|
|
WeakTable[H] := Dead^.Next
|
|
else
|
|
Prev^.Next := Dead^.Next;
|
|
_BlaiseFreeMem(Dead);
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
Exit;
|
|
end;
|
|
Prev := E;
|
|
E := E^.Next;
|
|
end;
|
|
pthread_mutex_unlock(@WeakMutex);
|
|
end;
|
|
|
|
initialization
|
|
{ Runs only when blaise_weak is compiled into the program's own TU.
|
|
For the archive case EnsureWeakMutex() does the work lazily; setting
|
|
the flag here keeps the two paths from double-initialising. }
|
|
pthread_mutex_init(@WeakMutex, nil);
|
|
WeakMutexReady := True;
|
|
|
|
end.
|