feat(units): unit-qualified var access and cross-unit var last-wins
Two used units could not export the same module-scope global var name: the second Define raised "Duplicate identifier", and even where one slot existed a qualified reference (Unit.V) was lowered by bare name, so it could not pick a specific unit's storage. Bare references also resolved through the flat table (analysis order) rather than the uses chain, disagreeing with the last-in-uses rule that consts already follow. Give vars the same cross-unit semantics as consts: - DefineGlobalLastWins detaches the prior unit's var on a cross-unit collision (keeping it alive in the per-unit cache so Unit.V still reaches it) and installs the later unit as the flat winner; same-unit redeclaration and module-name markers stay hard errors. RegisterVars mirrors this on the prebuilt-import path, as RegisterConsts does. - The analyser stamps the resolved owning unit onto each global-var reference and assignment target (the uses-chain winner for a bare ref, the named unit for a qualified one). Codegen mangles the storage symbol with that owner instead of re-looking-up the bare name, so the definition (keyed on the unit being compiled) and every reference agree. A bare reference now follows uses order like a const; a qualified reference always hits its own unit's slot. Tests: e2e last-wins (+ reversed) and qualified disambiguation across two units exporting `var V`, plus an IR-level check that qualified loads emit distinct owner-prefixed symbols.
This commit is contained in:
parent
9aa44911b0
commit
cd9af9ac7b
|
|
@ -386,8 +386,19 @@ type
|
||||||
function EmitInstancePtr(AExpr: TASTExpr): string;
|
function EmitInstancePtr(AExpr: TASTExpr): string;
|
||||||
function FieldPtr(const ARecordVar: string; AOffset: Integer; AIsGlobal: Boolean = False): string;
|
function FieldPtr(const ARecordVar: string; AOffset: Integer; AIsGlobal: Boolean = False): string;
|
||||||
{ Returns the QBE address token for a variable: '$Name' for globals,
|
{ Returns the QBE address token for a variable: '$Name' for globals,
|
||||||
'%_var_Name' for locals. }
|
'%_var_Name' for locals. The AOwner overload mangles a global's
|
||||||
function VarRef(const AName: string; AIsGlobal: Boolean): string;
|
storage symbol with an explicit owning unit (from the resolved
|
||||||
|
symbol's OwningUnit) rather than re-looking-up the bare name, so a
|
||||||
|
reference to a cross-unit last-wins var reaches the correct slot.
|
||||||
|
AOwner = '' falls back to the bare-name resolution. }
|
||||||
|
function VarRef(const AName: string; AIsGlobal: Boolean): string; overload;
|
||||||
|
function VarRef(const AName: string; AIsGlobal: Boolean;
|
||||||
|
const AOwner: string): string; overload;
|
||||||
|
{ Owning unit to mangle a global-var reference with: the semantic-stamped
|
||||||
|
ResolvedOwnerUnit, falling back to the explicit QualifierUnit (the latter
|
||||||
|
survives .bif round-trips for an inlined qualified reference where the
|
||||||
|
former, deliberately not serialised, is absent). }
|
||||||
|
function IdentVarOwner(AIdent: TIdentExpr): string;
|
||||||
{ Returns a QBE temp (or address token) that holds the address to pass as a
|
{ Returns a QBE temp (or address token) that holds the address to pass as a
|
||||||
var-param actual argument. When the actual argument is itself a var param
|
var-param actual argument. When the actual argument is itself a var param
|
||||||
its local slot contains a pointer — emit loadl to obtain the original
|
its local slot contains a pointer — emit loadl to obtain the original
|
||||||
|
|
@ -533,6 +544,12 @@ type
|
||||||
allowlist semantics as uSemantic.MangleUnitPrefix. }
|
allowlist semantics as uSemantic.MangleUnitPrefix. }
|
||||||
function ClassUnitPrefix(const AClassName: string): string;
|
function ClassUnitPrefix(const AClassName: string): string;
|
||||||
function GlobalVarUnitPrefix(const AName: string): string;
|
function GlobalVarUnitPrefix(const AName: string): string;
|
||||||
|
{ Map an owning unit to its global-var mangle prefix (program-scope and
|
||||||
|
the RTL allowlist stay bare). The shared core of GlobalVarUnitPrefix
|
||||||
|
(name-based owner resolution) and the AOwner VarRef overload, so a
|
||||||
|
definition site (keyed on the unit being compiled) and a reference
|
||||||
|
site (keyed on the resolved symbol's owner) always agree. }
|
||||||
|
function MangleGlobalOwner(const AOwner: string): string;
|
||||||
{ Compute the QBE call target for a property accessor. When AVSlot >= 0
|
{ Compute the QBE call target for a property accessor. When AVSlot >= 0
|
||||||
the accessor is virtual: emit the vptr+slot loads and return the
|
the accessor is virtual: emit the vptr+slot loads and return the
|
||||||
function-pointer temp. Otherwise return the static mangled symbol
|
function-pointer temp. Otherwise return the static mangled symbol
|
||||||
|
|
@ -2011,11 +2028,14 @@ begin
|
||||||
if Decl.IsThreadVar then
|
if Decl.IsThreadVar then
|
||||||
FThreadVarNames.Add(VarName); { keyed on the bare name — VarRef's
|
FThreadVarNames.Add(VarName); { keyed on the bare name — VarRef's
|
||||||
threadvar test sees the bare ident }
|
threadvar test sees the bare ident }
|
||||||
{ Mangle the emitted symbol with the owning unit (here always the unit
|
{ Mangle the emitted symbol with the unit currently being compiled so
|
||||||
being compiled) so same-named globals across units don't collide; VarRef
|
same-named globals across units don't collide; VarRef applies the
|
||||||
applies the identical prefix at every reference. Bare name kept above
|
identical prefix (via the resolved symbol's owner) at every reference.
|
||||||
for the threadvar lookup key. }
|
Keyed on FCurrentUnitName, not a name re-lookup, so a cross-unit
|
||||||
VarName := GlobalVarUnitPrefix(VarName) + VarName;
|
last-wins loser still defines its own slot (the flat table holds only
|
||||||
|
the winner after extraction). Bare name kept above for the threadvar
|
||||||
|
lookup key. }
|
||||||
|
VarName := MangleGlobalOwner(FCurrentUnitName) + VarName;
|
||||||
{ Initialised global: emit the folded value into the data section instead
|
{ Initialised global: emit the folded value into the data section instead
|
||||||
of a zero slot. threadvars cannot carry a non-zero static initialiser
|
of a zero slot. threadvars cannot carry a non-zero static initialiser
|
||||||
(they live in .tbss), so the parser/semantic restrict initialisers to
|
(they live in .tbss), so the parser/semantic restrict initialisers to
|
||||||
|
|
@ -4613,14 +4633,14 @@ begin
|
||||||
whole bitmap into the destination slot. }
|
whole bitmap into the destination slot. }
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitLine(Format(' call $memcpy(l %s, l %s, l %d)',
|
EmitLine(Format(' call $memcpy(l %s, l %s, l %d)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp,
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), ValTemp,
|
||||||
TSetTypeDesc(AAssign.ResolvedLhsType).RawSize()]));
|
TSetTypeDesc(AAssign.ResolvedLhsType).RawSize()]));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(AAssign.ResolvedLhsType.Kind = tyStaticArray) and
|
(AAssign.ResolvedLhsType.Kind = tyStaticArray) and
|
||||||
IsRecordCall(AAssign.Expr) then
|
IsRecordCall(AAssign.Expr) then
|
||||||
begin
|
begin
|
||||||
EmitRecordCallSret(AAssign.Expr, VarRef(AAssign.Name, AAssign.IsGlobal));
|
EmitRecordCallSret(AAssign.Expr, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(AAssign.ResolvedLhsType.Kind = tyRecord) then
|
(AAssign.ResolvedLhsType.Kind = tyRecord) then
|
||||||
|
|
@ -4642,21 +4662,21 @@ begin
|
||||||
EmitLine(Format(' call $memset(l %s, w 0, l %d)',
|
EmitLine(Format(' call $memset(l %s, w 0, l %d)',
|
||||||
[SretBuf, ClassRT.TotalSize()]));
|
[SretBuf, ClassRT.TotalSize()]));
|
||||||
EmitRecordCallSret(AAssign.Expr, SretBuf);
|
EmitRecordCallSret(AAssign.Expr, SretBuf);
|
||||||
EmitRecordReleaseFields(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal));
|
EmitRecordReleaseFields(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit));
|
||||||
EmitLine(Format(' call $memcpy(l %s, l %s, l %d)',
|
EmitLine(Format(' call $memcpy(l %s, l %s, l %d)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal), SretBuf, ClassRT.TotalSize()]));
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), SretBuf, ClassRT.TotalSize()]));
|
||||||
end
|
end
|
||||||
else if IsRecordCall(AAssign.Expr) then
|
else if IsRecordCall(AAssign.Expr) then
|
||||||
begin
|
begin
|
||||||
EmitRecordReleaseFields(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal));
|
EmitRecordReleaseFields(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit));
|
||||||
EmitLine(Format(' call $memset(l %s, w 0, l %d)',
|
EmitLine(Format(' call $memset(l %s, w 0, l %d)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal), ClassRT.TotalSize()]));
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), ClassRT.TotalSize()]));
|
||||||
EmitRecordCallSret(AAssign.Expr, VarRef(AAssign.Name, AAssign.IsGlobal));
|
EmitRecordCallSret(AAssign.Expr, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit));
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitRecordCopy(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp);
|
EmitRecordCopy(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), ValTemp);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
|
|
@ -4668,7 +4688,7 @@ begin
|
||||||
another method-pointer var or a TMethod record (same layout). }
|
another method-pointer var or a TMethod record (same layout). }
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitLine(Format(' call $memcpy(l %s, l %s, l 16)',
|
EmitLine(Format(' call $memcpy(l %s, l %s, l 16)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp]));
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), ValTemp]));
|
||||||
end
|
end
|
||||||
else if AAssign.Expr.ResolvedType.IsString() then
|
else if AAssign.Expr.ResolvedType.IsString() then
|
||||||
begin
|
begin
|
||||||
|
|
@ -4677,7 +4697,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
if not ExprOwnsRef(AAssign.Expr) then
|
if not ExprOwnsRef(AAssign.Expr) then
|
||||||
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
||||||
|
|
@ -4685,7 +4705,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(AAssign.ResolvedLhsType.Kind = tyDynArray) then
|
(AAssign.ResolvedLhsType.Kind = tyDynArray) then
|
||||||
|
|
@ -4697,7 +4717,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
if not ExprOwnsRef(AAssign.Expr) then
|
if not ExprOwnsRef(AAssign.Expr) then
|
||||||
EmitLine(Format(' call $_DynArrayAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_DynArrayAddRef(l %s)', [ValTemp]));
|
||||||
|
|
@ -4705,7 +4725,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(AAssign.ResolvedLhsType.Kind = tyClass) and
|
(AAssign.ResolvedLhsType.Kind = tyClass) and
|
||||||
|
|
@ -4713,19 +4733,19 @@ begin
|
||||||
begin
|
begin
|
||||||
if AAssign.IsWeakLhs then
|
if AAssign.IsWeakLhs then
|
||||||
EmitLine(Format(' call $_WeakClear(l %s)',
|
EmitLine(Format(' call $_WeakClear(l %s)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal)]))
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]))
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
OldTemp := AllocTemp();
|
OldTemp := AllocTemp();
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy 0', [AAssign.Name]))
|
EmitLine(Format(' %%_var_%s =l copy 0', [AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel 0, %s', [VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel 0, %s', [VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if AAssign.IsWeakLhs and (AAssign.Expr.ResolvedType.Kind = tyClass) then
|
else if AAssign.IsWeakLhs and (AAssign.Expr.ResolvedType.Kind = tyClass) then
|
||||||
|
|
@ -4736,7 +4756,7 @@ begin
|
||||||
any prior registration for this slot. }
|
any prior registration for this slot. }
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitLine(Format(' call $_WeakAssign(l %s, l %s)',
|
EmitLine(Format(' call $_WeakAssign(l %s, l %s)',
|
||||||
[VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp]));
|
[VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit), ValTemp]));
|
||||||
end
|
end
|
||||||
else if AAssign.Expr.ResolvedType.Kind = tyClass then
|
else if AAssign.Expr.ResolvedType.Kind = tyClass then
|
||||||
begin
|
begin
|
||||||
|
|
@ -4758,7 +4778,7 @@ begin
|
||||||
if not ExprOwnsRef(AAssign.Expr) then
|
if not ExprOwnsRef(AAssign.Expr) then
|
||||||
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
||||||
EmitLine(Format(' storel %s, %s',
|
EmitLine(Format(' storel %s, %s',
|
||||||
[ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
[ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
MarkArcSlotWritten(AAssign.Name);
|
MarkArcSlotWritten(AAssign.Name);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -4767,7 +4787,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
if not ExprOwnsRef(AAssign.Expr) then
|
if not ExprOwnsRef(AAssign.Expr) then
|
||||||
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
||||||
|
|
@ -4775,7 +4795,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
MarkArcSlotWritten(AAssign.Name);
|
MarkArcSlotWritten(AAssign.Name);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
@ -4797,7 +4817,7 @@ begin
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
||||||
EmitLine(Format(' storel %s, %s',
|
EmitLine(Format(' storel %s, %s',
|
||||||
[ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
[ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
MarkArcSlotWritten(AAssign.Name);
|
MarkArcSlotWritten(AAssign.Name);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -4806,14 +4826,14 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
EmitLine(Format(' %s =l copy %%_var_%s', [OldTemp, AAssign.Name]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
||||||
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
MarkArcSlotWritten(AAssign.Name);
|
MarkArcSlotWritten(AAssign.Name);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
@ -4851,7 +4871,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =d copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =d copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' stored %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' stored %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(AAssign.ResolvedLhsType.Kind = tySingle) then
|
(AAssign.ResolvedLhsType.Kind = tySingle) then
|
||||||
|
|
@ -4883,7 +4903,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =s copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =s copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' stores %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' stores %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end
|
end
|
||||||
else if (AAssign.ResolvedLhsType <> nil) and
|
else if (AAssign.ResolvedLhsType <> nil) and
|
||||||
(QbeTypeOf(AAssign.ResolvedLhsType) = 'l') then
|
(QbeTypeOf(AAssign.ResolvedLhsType) = 'l') then
|
||||||
|
|
@ -4898,7 +4918,7 @@ begin
|
||||||
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
if not AAssign.IsGlobal and IsPromoted(AAssign.Name) then
|
||||||
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
EmitLine(Format(' %%_var_%s =l copy %s', [AAssign.Name, ValTemp]))
|
||||||
else
|
else
|
||||||
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' storel %s, %s', [ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
|
|
@ -4916,7 +4936,7 @@ begin
|
||||||
's': StoreInstr := 'stores';
|
's': StoreInstr := 'stores';
|
||||||
else StoreInstr := 'storel';
|
else StoreInstr := 'storel';
|
||||||
end;
|
end;
|
||||||
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal)]));
|
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, VarRef(AAssign.Name, AAssign.IsGlobal, AAssign.ResolvedOwnerUnit)]));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
@ -4954,7 +4974,7 @@ begin
|
||||||
if (AExpr.ResolvedType <> nil) and (AExpr.ResolvedType.Kind = tyClass) then
|
if (AExpr.ResolvedType <> nil) and (AExpr.ResolvedType.Kind = tyClass) then
|
||||||
begin
|
begin
|
||||||
Loaded := AllocTemp();
|
Loaded := AllocTemp();
|
||||||
EmitLine(Format(' %s =l loadl %s', [Loaded, VarRef(Id.Name, Id.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [Loaded, VarRef(Id.Name, Id.IsGlobal, IdentVarOwner(Id))]));
|
||||||
if Id.ParamMode <> pmNone then
|
if Id.ParamMode <> pmNone then
|
||||||
begin
|
begin
|
||||||
{ var-param class ident: slot -> caller var -> instance. }
|
{ var-param class ident: slot -> caller var -> instance. }
|
||||||
|
|
@ -4969,11 +4989,11 @@ begin
|
||||||
{ Var-record param: dereference the param slot to get the actual record
|
{ Var-record param: dereference the param slot to get the actual record
|
||||||
address. }
|
address. }
|
||||||
Loaded := AllocTemp();
|
Loaded := AllocTemp();
|
||||||
EmitLine(Format(' %s =l loadl %s', [Loaded, VarRef(Id.Name, Id.IsGlobal)]));
|
EmitLine(Format(' %s =l loadl %s', [Loaded, VarRef(Id.Name, Id.IsGlobal, IdentVarOwner(Id))]));
|
||||||
Result := Loaded;
|
Result := Loaded;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Result := VarRef(Id.Name, Id.IsGlobal); { inline record }
|
Result := VarRef(Id.Name, Id.IsGlobal, IdentVarOwner(Id)); { inline record }
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
@ -5128,28 +5148,52 @@ begin
|
||||||
Owner := FInlineSrcUnits.Strings[FInlineDepth - 1]
|
Owner := FInlineSrcUnits.Strings[FInlineDepth - 1]
|
||||||
else
|
else
|
||||||
Owner := FCurrentUnitName;
|
Owner := FCurrentUnitName;
|
||||||
if Owner = '' then Exit;
|
Result := MangleGlobalOwner(Owner);
|
||||||
if (FProgramName <> '') and SameText(Owner, FProgramName) then Exit;
|
end;
|
||||||
Result := MangleUnitPrefix(Owner);
|
|
||||||
{ Idempotence guard: a static class-var (e.g. RegModU.TReg.FCount) is emitted
|
function TCodeGenQBE.MangleGlobalOwner(const AOwner: string): string;
|
||||||
under its already unit+class-qualified symbol RegModU_TReg_FCount. Such a
|
begin
|
||||||
name is passed here verbatim and Lookup does not find it, so Owner falls back
|
|
||||||
to the current unit and the prefix would be applied a SECOND time
|
|
||||||
(RegModU_RegModU_TReg_FCount), breaking the def/ref match at link. If AName
|
|
||||||
already starts with the prefix, it is carried — don't re-prefix it. }
|
|
||||||
if (Result <> '') and (Length(AName) >= Length(Result)) and
|
|
||||||
SameText(Copy(AName, 0, Length(Result)), Result) then
|
|
||||||
Result := '';
|
Result := '';
|
||||||
|
if AOwner = '' then Exit;
|
||||||
|
{ Sentinel: the global's name is ALREADY a fully-mangled symbol (e.g. a static
|
||||||
|
class var's ClassVarEmitName, which master computes as
|
||||||
|
CurrentUnitPrefix()+Class+'_'+Field — its class qualifier is more
|
||||||
|
comprehensive than the module-var prefix, so use it verbatim and add no
|
||||||
|
further prefix). }
|
||||||
|
if AOwner = PreMangledGlobalOwner then Exit;
|
||||||
|
if (FProgramName <> '') and SameText(AOwner, FProgramName) then Exit;
|
||||||
|
Result := MangleUnitPrefix(AOwner);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCodeGenQBE.VarRef(const AName: string; AIsGlobal: Boolean): string;
|
function TCodeGenQBE.VarRef(const AName: string; AIsGlobal: Boolean): string;
|
||||||
|
overload;
|
||||||
|
begin
|
||||||
|
Result := VarRef(AName, AIsGlobal, '');
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCodeGenQBE.IdentVarOwner(AIdent: TIdentExpr): string;
|
||||||
|
begin
|
||||||
|
Result := AIdent.ResolvedOwnerUnit;
|
||||||
|
if Result = '' then Result := AIdent.QualifierUnit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCodeGenQBE.VarRef(const AName: string; AIsGlobal: Boolean;
|
||||||
|
const AOwner: string): string; overload;
|
||||||
|
var
|
||||||
|
Prefix: string;
|
||||||
begin
|
begin
|
||||||
if AIsGlobal then
|
if AIsGlobal then
|
||||||
begin
|
begin
|
||||||
if FThreadVarNames.IndexOf(AName) >= 0 then
|
{ An explicit owner (the resolved symbol's OwningUnit) mangles directly;
|
||||||
Result := 'thread $' + GlobalVarUnitPrefix(AName) + AName
|
otherwise fall back to resolving the owner from the bare name. }
|
||||||
|
if AOwner <> '' then
|
||||||
|
Prefix := MangleGlobalOwner(AOwner)
|
||||||
else
|
else
|
||||||
Result := '$' + GlobalVarUnitPrefix(AName) + AName;
|
Prefix := GlobalVarUnitPrefix(AName);
|
||||||
|
if FThreadVarNames.IndexOf(AName) >= 0 then
|
||||||
|
Result := 'thread $' + Prefix + AName
|
||||||
|
else
|
||||||
|
Result := '$' + Prefix + AName;
|
||||||
end
|
end
|
||||||
{ A variable captured from an enclosing proc is reached through the hidden
|
{ A variable captured from an enclosing proc is reached through the hidden
|
||||||
%_cap_<Name> pointer parameter, which holds the address of the enclosing
|
%_cap_<Name> pointer parameter, which holds the address of the enclosing
|
||||||
|
|
@ -5180,7 +5224,7 @@ begin
|
||||||
{ Interface variable: the fat pointer lives in one contiguous 16-byte
|
{ Interface variable: the fat pointer lives in one contiguous 16-byte
|
||||||
block (locals: one alloc; globals: one 16-byte data item) whose base
|
block (locals: one alloc; globals: one 16-byte data item) whose base
|
||||||
is the _obj slot — that base IS the var-arg address. }
|
is the _obj slot — that base IS the var-arg address. }
|
||||||
Result := VarRef(AIdent.Name, AIdent.IsGlobal) + '_obj';
|
Result := VarRef(AIdent.Name, AIdent.IsGlobal, IdentVarOwner(AIdent)) + '_obj';
|
||||||
end
|
end
|
||||||
else if AIdent.IsImplicitSelf then
|
else if AIdent.IsImplicitSelf then
|
||||||
begin
|
begin
|
||||||
|
|
@ -5197,7 +5241,7 @@ begin
|
||||||
Result := SelfT;
|
Result := SelfT;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Result := VarRef(AIdent.Name, AIdent.IsGlobal);
|
Result := VarRef(AIdent.Name, AIdent.IsGlobal, IdentVarOwner(AIdent));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCodeGenQBE.EmitLValueAddr(AExpr: TASTExpr): string;
|
function TCodeGenQBE.EmitLValueAddr(AExpr: TASTExpr): string;
|
||||||
|
|
@ -6470,6 +6514,9 @@ begin
|
||||||
try
|
try
|
||||||
CVStore.Name := AAssign.ClassVarEmitName;
|
CVStore.Name := AAssign.ClassVarEmitName;
|
||||||
CVStore.IsGlobal := True;
|
CVStore.IsGlobal := True;
|
||||||
|
{ ClassVarEmitName is already the fully-mangled static-var symbol; flag it
|
||||||
|
so VarRef's module-var unit-prefixing does not double-apply. }
|
||||||
|
CVStore.ResolvedOwnerUnit := PreMangledGlobalOwner;
|
||||||
CVStore.ResolvedLhsType := AAssign.ClassVarLhsType;
|
CVStore.ResolvedLhsType := AAssign.ClassVarLhsType;
|
||||||
CVStore.Expr := AAssign.Expr;
|
CVStore.Expr := AAssign.Expr;
|
||||||
EmitAssignment(CVStore);
|
EmitAssignment(CVStore);
|
||||||
|
|
@ -13088,7 +13135,7 @@ begin
|
||||||
{ Jumbo set value param: the QBE backend spills it into a local inline
|
{ Jumbo set value param: the QBE backend spills it into a local inline
|
||||||
bitmap slot (value semantics), so %_var_X IS the bitmap address —
|
bitmap slot (value semantics), so %_var_X IS the bitmap address —
|
||||||
return it directly without a load. }
|
return it directly without a load. }
|
||||||
Exit(VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal));
|
Exit(VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr))));
|
||||||
end
|
end
|
||||||
else if (TIdentExpr(AExpr).ParamMode <> pmNone) and
|
else if (TIdentExpr(AExpr).ParamMode <> pmNone) and
|
||||||
(AExpr.ResolvedType <> nil) and
|
(AExpr.ResolvedType <> nil) and
|
||||||
|
|
@ -13120,7 +13167,7 @@ begin
|
||||||
IsAggregateAddrType(AExpr.ResolvedType) then
|
IsAggregateAddrType(AExpr.ResolvedType) then
|
||||||
begin
|
begin
|
||||||
{ Aggregate variable — return its storage address directly (no load). }
|
{ Aggregate variable — return its storage address directly (no load). }
|
||||||
Exit(VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal));
|
Exit(VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr))));
|
||||||
end
|
end
|
||||||
else if (AExpr.ResolvedType <> nil) and
|
else if (AExpr.ResolvedType <> nil) and
|
||||||
(AExpr.ResolvedType.Kind = tyInterface) then
|
(AExpr.ResolvedType.Kind = tyInterface) then
|
||||||
|
|
@ -13145,14 +13192,14 @@ begin
|
||||||
QType := QbeTypeOf(AExpr.ResolvedType);
|
QType := QbeTypeOf(AExpr.ResolvedType);
|
||||||
case QType of
|
case QType of
|
||||||
'w': EmitLine(Format(' %s =w loadw %s',
|
'w': EmitLine(Format(' %s =w loadw %s',
|
||||||
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr)))]));
|
||||||
'd': EmitLine(Format(' %s =d loadd %s',
|
'd': EmitLine(Format(' %s =d loadd %s',
|
||||||
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr)))]));
|
||||||
's': EmitLine(Format(' %s =s loads %s',
|
's': EmitLine(Format(' %s =s loads %s',
|
||||||
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr)))]));
|
||||||
else
|
else
|
||||||
EmitLine(Format(' %s =l loadl %s',
|
EmitLine(Format(' %s =l loadl %s',
|
||||||
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
[T, VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal, IdentVarOwner(TIdentExpr(AExpr)))]));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
Result := T;
|
Result := T;
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,14 @@ type
|
||||||
resolves the name against this specific unit's
|
resolves the name against this specific unit's
|
||||||
exports (per-unit cache) instead of the uses chain,
|
exports (per-unit cache) instead of the uses chain,
|
||||||
so a same-named const in another unit can't shadow it. }
|
so a same-named const in another unit can't shadow it. }
|
||||||
|
ResolvedOwnerUnit: string; { set by uSemantic — for a module-scope global var,
|
||||||
|
the owning unit of the symbol actually resolved (the
|
||||||
|
uses-chain last-wins winner for a bare ref, or the
|
||||||
|
named unit for a qualified ref). Codegen mangles the
|
||||||
|
global's storage symbol with this owner so two used
|
||||||
|
units exporting the same var name reference distinct
|
||||||
|
slots. Not serialised — recomputed at analysis time;
|
||||||
|
empty falls back to the bare-name resolution. }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TFieldAccessExpr = class(TASTExpr)
|
TFieldAccessExpr = class(TASTExpr)
|
||||||
|
|
@ -247,6 +255,10 @@ type
|
||||||
_WeakAssign in place of the strong
|
_WeakAssign in place of the strong
|
||||||
addref/release pattern. }
|
addref/release pattern. }
|
||||||
ImplicitSelfField: TObject; { TFieldInfo — non-nil when LHS is bare field (implicit Self) }
|
ImplicitSelfField: TObject; { TFieldInfo — non-nil when LHS is bare field (implicit Self) }
|
||||||
|
ResolvedOwnerUnit: string; { set by uSemantic — owning unit of a module-scope global
|
||||||
|
var target; codegen mangles the store address with it so
|
||||||
|
same-named cross-unit globals write distinct slots. See
|
||||||
|
the matching field on TIdentExpr. }
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -504,6 +504,15 @@ type
|
||||||
harvested into the cache. }
|
harvested into the cache. }
|
||||||
function ResolveQualified(const AUnit, AName: string): TSymbol;
|
function ResolveQualified(const AUnit, AName: string): TSymbol;
|
||||||
|
|
||||||
|
{ Define a module-scope global (interface var) with cross-unit
|
||||||
|
last-in-uses-wins semantics, mirroring AnalyseConstDecls. On a
|
||||||
|
clean Define the symbol is registered in the per-unit cache; on a
|
||||||
|
cross-unit collision the prior unit's symbol is detached (kept alive
|
||||||
|
in the per-unit cache so a qualified Unit.Var reference still reaches
|
||||||
|
it) and this unit's symbol is installed as the flat winner. Same-unit
|
||||||
|
redeclaration and module-name markers stay hard errors. }
|
||||||
|
procedure DefineGlobalLastWins(ASym: TSymbol; ALine, ACol: Integer);
|
||||||
|
|
||||||
{ Record one enum member in the reverse index (FEnumMemberIndex).
|
{ Record one enum member in the reverse index (FEnumMemberIndex).
|
||||||
Called once per member as its enum type is analysed. Enum members
|
Called once per member as its enum type is analysed. Enum members
|
||||||
are NOT registered as bare global symbols any more — a bare member
|
are NOT registered as bare global symbols any more — a bare member
|
||||||
|
|
@ -1991,12 +2000,7 @@ begin
|
||||||
Sym := TSymbol.Create(VDecl.Names.Strings[J], skVariable, ParType);
|
Sym := TSymbol.Create(VDecl.Names.Strings[J], skVariable, ParType);
|
||||||
Sym.IsGlobal := True;
|
Sym.IsGlobal := True;
|
||||||
Sym.IsThreadVar := VDecl.IsThreadVar;
|
Sym.IsThreadVar := VDecl.IsThreadVar;
|
||||||
if not FTable.Define(Sym) then
|
DefineGlobalLastWins(Sym, VDecl.Line, VDecl.Col);
|
||||||
begin
|
|
||||||
Sym.Free();
|
|
||||||
SemanticError(Format('Duplicate identifier ''%s''',
|
|
||||||
[VDecl.Names.Strings[J]]), VDecl.Line, VDecl.Col);
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
@ -5272,6 +5276,44 @@ begin
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSemanticAnalyser.DefineGlobalLastWins(ASym: TSymbol;
|
||||||
|
ALine, ACol: Integer);
|
||||||
|
var
|
||||||
|
RefSym: TSymbol;
|
||||||
|
Prev: TSymbol;
|
||||||
|
begin
|
||||||
|
if not FTable.Define(ASym) then
|
||||||
|
begin
|
||||||
|
RefSym := FTable.CurrentScope.LookupLocal(ASym.Name);
|
||||||
|
{ A module-name marker (issue #84), a non-unit symbol (builtin /
|
||||||
|
program-scope, OwningUnit = ''), or a same-unit redeclaration
|
||||||
|
blocking the Define is a hard error — only a genuine cross-unit
|
||||||
|
collision (two used units exporting the same name) gets last-wins. }
|
||||||
|
if (RefSym = nil) or (RefSym.Kind = skModule) or
|
||||||
|
(RefSym.OwningUnit = '') or
|
||||||
|
SameText(RefSym.OwningUnit, ASym.OwningUnit) then
|
||||||
|
begin
|
||||||
|
ASym.Free();
|
||||||
|
SemanticError(Format('Duplicate identifier ''%s''', [ASym.Name]),
|
||||||
|
ALine, ACol);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
{ Cross-unit collision: last-in-uses wins. Detach the prior unit's
|
||||||
|
var and stash it in the per-unit cache so a qualified reference
|
||||||
|
(Unit.Var) can still reach the shadowed slot; install this unit's
|
||||||
|
var as the flat winner. Mirrors AnalyseConstDecls and RegisterVars. }
|
||||||
|
Prev := FTable.ExtractLocal(ASym.Name);
|
||||||
|
if (Prev <> nil) and (Prev.OwningUnit <> '') then
|
||||||
|
RegisterUnitSymbol(Prev.OwningUnit, Prev);
|
||||||
|
FTable.Define(ASym);
|
||||||
|
end;
|
||||||
|
{ Register every interface var in the per-unit cache keyed by its owning
|
||||||
|
unit, so a qualified reference resolves against the declaring unit's own
|
||||||
|
slot regardless of which unit won the bare (flat) slot. }
|
||||||
|
if ASym.OwningUnit <> '' then
|
||||||
|
RegisterUnitSymbol(ASym.OwningUnit, ASym);
|
||||||
|
end;
|
||||||
|
|
||||||
function TSemanticAnalyser.NewArrayConstLabel(const AName: string): string;
|
function TSemanticAnalyser.NewArrayConstLabel(const AName: string): string;
|
||||||
begin
|
begin
|
||||||
Inc(FArrayConstCounter);
|
Inc(FArrayConstCounter);
|
||||||
|
|
@ -8629,6 +8671,18 @@ begin
|
||||||
AAssign.IsWeakLhs := VarSym.IsWeak;
|
AAssign.IsWeakLhs := VarSym.IsWeak;
|
||||||
AAssign.IsGlobal := VarSym.IsGlobal;
|
AAssign.IsGlobal := VarSym.IsGlobal;
|
||||||
AAssign.IsThreadVar := VarSym.IsThreadVar;
|
AAssign.IsThreadVar := VarSym.IsThreadVar;
|
||||||
|
{ Owning unit of a module-scope global target, so the store address is
|
||||||
|
mangled with the unit that won resolution — keeps a bare write to a
|
||||||
|
cross-unit last-wins var hitting the same slot a bare read sees. A static
|
||||||
|
class/record var's name is its already fully-mangled GlobalEmitName, so flag
|
||||||
|
it pre-mangled to keep codegen's module-var prefixing from double-applying. }
|
||||||
|
if VarSym.IsGlobal and (VarSym.Kind = skVariable) then
|
||||||
|
begin
|
||||||
|
if VarSym.IsClassVar then
|
||||||
|
AAssign.ResolvedOwnerUnit := PreMangledGlobalOwner
|
||||||
|
else
|
||||||
|
AAssign.ResolvedOwnerUnit := VarSym.OwningUnit;
|
||||||
|
end;
|
||||||
|
|
||||||
ResolveDiamond(AAssign.Expr, VarSym.TypeDesc);
|
ResolveDiamond(AAssign.Expr, VarSym.TypeDesc);
|
||||||
|
|
||||||
|
|
@ -11742,6 +11796,22 @@ begin
|
||||||
TIdentExpr(AExpr).ParamMode := pmNone;
|
TIdentExpr(AExpr).ParamMode := pmNone;
|
||||||
TIdentExpr(AExpr).IsGlobal := Sym.IsGlobal;
|
TIdentExpr(AExpr).IsGlobal := Sym.IsGlobal;
|
||||||
TIdentExpr(AExpr).IsThreadVar := Sym.IsThreadVar;
|
TIdentExpr(AExpr).IsThreadVar := Sym.IsThreadVar;
|
||||||
|
{ Record the owning unit of a module-scope global so codegen mangles
|
||||||
|
the storage symbol with the unit that actually won resolution (the
|
||||||
|
uses-chain last-wins winner for a bare ref, the named unit for a
|
||||||
|
qualified ref). Without this, two used units exporting the same var
|
||||||
|
name both resolve to one slot. }
|
||||||
|
if Sym.IsGlobal and (Sym.Kind = skVariable) then
|
||||||
|
begin
|
||||||
|
{ A static class/record var's Name was rewritten above to its already
|
||||||
|
fully-mangled GlobalEmitName (Unit_Class_Field); flag it so codegen's
|
||||||
|
module-var prefixing does not double-apply. Plain module globals carry
|
||||||
|
their owning unit for owner-based mangling. }
|
||||||
|
if Sym.IsClassVar then
|
||||||
|
TIdentExpr(AExpr).ResolvedOwnerUnit := PreMangledGlobalOwner
|
||||||
|
else
|
||||||
|
TIdentExpr(AExpr).ResolvedOwnerUnit := Sym.OwningUnit;
|
||||||
|
end;
|
||||||
if Sym.Kind = skConstant then
|
if Sym.Kind = skConstant then
|
||||||
begin
|
begin
|
||||||
TIdentExpr(AExpr).IsConstant := True;
|
TIdentExpr(AExpr).IsConstant := True;
|
||||||
|
|
|
||||||
|
|
@ -928,7 +928,14 @@ begin
|
||||||
Sym.IsThreadVar := Entry.IsThreadVar;
|
Sym.IsThreadVar := Entry.IsThreadVar;
|
||||||
Sym.OwningUnit := AIface.Name;
|
Sym.OwningUnit := AIface.Name;
|
||||||
if not ATable.Define(Sym) then
|
if not ATable.Define(Sym) then
|
||||||
Sym.Free();
|
begin
|
||||||
|
{ Cross-unit collision (last-wins): detach the slot the table already
|
||||||
|
holds — kept alive via the per-unit cache (RegisterUnitIface) for
|
||||||
|
qualified access — and store this later unit's symbol instead.
|
||||||
|
Mirrors RegisterConsts and the source-path DefineGlobalLastWins. }
|
||||||
|
ATable.ExtractLocal(Entry.Name);
|
||||||
|
ATable.Define(Sym);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -711,6 +711,14 @@ function IsUnmangledUnit(const AUnitName: string): Boolean;
|
||||||
agree on the QBE global name. }
|
agree on the QBE global name. }
|
||||||
function MangleUnitPrefix(const AUnitName: string): string;
|
function MangleUnitPrefix(const AUnitName: string): string;
|
||||||
|
|
||||||
|
const
|
||||||
|
{ Sentinel owning-unit value meaning "this global's emit name is ALREADY a
|
||||||
|
fully-mangled symbol — codegen must add no further unit prefix". Used for
|
||||||
|
static class/record vars, whose label (Unit_Class_Field) is class-qualified
|
||||||
|
and complete; the module-var prefixing must not double-apply. The leading
|
||||||
|
control byte can never collide with a real unit name. }
|
||||||
|
PreMangledGlobalOwner = #1'premangled';
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,15 @@ type
|
||||||
unit-prefix mangling on module-scope globals they no longer collide at
|
unit-prefix mangling on module-scope globals they no longer collide at
|
||||||
link, so a program using both links and runs. }
|
link, so a program using both links and runs. }
|
||||||
procedure TestRun_SameNamedGlobals_NoLinkCollision;
|
procedure TestRun_SameNamedGlobals_NoLinkCollision;
|
||||||
|
{ Cross-unit interface VAR shadowing: two used units export the same var
|
||||||
|
name; the unit later in `uses` wins a bare reference (last-in-uses), and
|
||||||
|
reversing the order flips the winner — mirrors the const last-wins rule. }
|
||||||
|
procedure TestRun_CrossUnitVar_LastWins;
|
||||||
|
procedure TestRun_CrossUnitVar_LastWins_Reversed;
|
||||||
|
{ Unit-qualified VAR disambiguation: 'Unit.V' references that specific
|
||||||
|
unit's own slot (distinct storage), independent of the bare last-wins
|
||||||
|
winner, so both values are readable side by side. }
|
||||||
|
procedure TestRun_CrossUnitVar_QualifiedDisambig;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -370,6 +379,82 @@ begin
|
||||||
AssertEquals('GetOne + GetTwo', '33' + LE, Output);
|
AssertEquals('GetOne + GetTwo', '33' + LE, Output);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
const
|
||||||
|
UVA_Var = '''
|
||||||
|
unit uva;
|
||||||
|
interface
|
||||||
|
var V: Integer = 7;
|
||||||
|
implementation
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
UVB_Var = '''
|
||||||
|
unit uvb;
|
||||||
|
interface
|
||||||
|
var V: Integer = 9;
|
||||||
|
implementation
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
|
||||||
|
procedure TE2EUsesChainTests.TestRun_CrossUnitVar_LastWins;
|
||||||
|
const
|
||||||
|
DrvSrc = '''
|
||||||
|
program P;
|
||||||
|
uses uva, uvb;
|
||||||
|
begin
|
||||||
|
WriteLn(V)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
{ Both uva and uvb export `V`; uvb is later in `uses`, so bare V = 9.
|
||||||
|
The shadowed uva.V keeps its own slot (no link collision). }
|
||||||
|
AssertTrue('compile+link+run',
|
||||||
|
CompileAndRunWithUnits(UVA_Var, UVB_Var, DrvSrc, Output, RCode));
|
||||||
|
AssertEquals('exit 0', 0, RCode);
|
||||||
|
AssertEquals('last-in-uses (uvb) wins', '9' + LE, Output);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TE2EUsesChainTests.TestRun_CrossUnitVar_LastWins_Reversed;
|
||||||
|
const
|
||||||
|
DrvSrc = '''
|
||||||
|
program P;
|
||||||
|
uses uvb, uva;
|
||||||
|
begin
|
||||||
|
WriteLn(V)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
{ Reversed `uses` order: uva is now later, so bare V = 7. }
|
||||||
|
AssertTrue('compile+link+run',
|
||||||
|
CompileAndRunWithUnits(UVA_Var, UVB_Var, DrvSrc, Output, RCode));
|
||||||
|
AssertEquals('exit 0', 0, RCode);
|
||||||
|
AssertEquals('last-in-uses (uva) wins', '7' + LE, Output);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TE2EUsesChainTests.TestRun_CrossUnitVar_QualifiedDisambig;
|
||||||
|
const
|
||||||
|
DrvSrc = '''
|
||||||
|
program P;
|
||||||
|
uses uva, uvb;
|
||||||
|
begin
|
||||||
|
WriteLn(uva.V);
|
||||||
|
WriteLn(uvb.V)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
{ Qualified references pick each unit's own slot (uva.V = 7, uvb.V = 9)
|
||||||
|
regardless of the bare last-wins rule — distinct storage per unit. }
|
||||||
|
AssertTrue('compile+link+run',
|
||||||
|
CompileAndRunWithUnits(UVA_Var, UVB_Var, DrvSrc, Output, RCode));
|
||||||
|
AssertEquals('exit 0', 0, RCode);
|
||||||
|
AssertEquals('uva.V then uvb.V', '7' + LE + '9' + LE, Output);
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TE2EUsesChainTests);
|
RegisterTest(TE2EUsesChainTests);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,10 @@ type
|
||||||
{ ------------------------------------------------------------------ }
|
{ ------------------------------------------------------------------ }
|
||||||
procedure TestCodegen_TwoFileCompile_UnitFuncExported;
|
procedure TestCodegen_TwoFileCompile_UnitFuncExported;
|
||||||
procedure TestCodegen_TwoFileCompile_MainPresent;
|
procedure TestCodegen_TwoFileCompile_MainPresent;
|
||||||
|
{ Two used units export the same global var name; a qualified reference
|
||||||
|
'Unit.V' must emit each unit's own owner-prefixed storage symbol so the
|
||||||
|
two refer to distinct slots rather than colliding on a bare '$V'. }
|
||||||
|
procedure TestCodegen_CrossUnitQualifiedVar_DistinctSymbols;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -733,6 +737,67 @@ begin
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMultifileTests.TestCodegen_CrossUnitQualifiedVar_DistinctSymbols;
|
||||||
|
const
|
||||||
|
UnitA =
|
||||||
|
'''
|
||||||
|
unit uva;
|
||||||
|
interface
|
||||||
|
var V: Integer = 7;
|
||||||
|
implementation
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
UnitB =
|
||||||
|
'''
|
||||||
|
unit uvb;
|
||||||
|
interface
|
||||||
|
var V: Integer = 9;
|
||||||
|
implementation
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
ProgSrc =
|
||||||
|
'''
|
||||||
|
program TestP;
|
||||||
|
uses uva, uvb;
|
||||||
|
var r: Integer;
|
||||||
|
begin
|
||||||
|
r := uva.V;
|
||||||
|
r := uvb.V
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var
|
||||||
|
UA, UB: TUnit;
|
||||||
|
Prog: TProgram;
|
||||||
|
SA: TSemanticAnalyser;
|
||||||
|
CG: TCodeGenQBE;
|
||||||
|
IR: string;
|
||||||
|
begin
|
||||||
|
UA := ParseUnitSrc(UnitA);
|
||||||
|
UB := ParseUnitSrc(UnitB);
|
||||||
|
Prog := ParseProg(ProgSrc);
|
||||||
|
SA := TSemanticAnalyser.Create();
|
||||||
|
CG := TCodeGenQBE.Create();
|
||||||
|
try
|
||||||
|
SA.AnalyseUnitForExport(UA);
|
||||||
|
SA.AnalyseUnitForExport(UB);
|
||||||
|
SA.Analyse(Prog);
|
||||||
|
CG.AppendUnit(UA);
|
||||||
|
CG.AppendUnit(UB);
|
||||||
|
CG.AppendProgram(Prog);
|
||||||
|
IR := CG.GetOutput();
|
||||||
|
{ Each unit defines its own owner-prefixed slot, and the qualified loads
|
||||||
|
reference both — proving the two same-named vars do not collapse. }
|
||||||
|
AssertTrue('uva.V slot referenced', Pos('$uva_V', IR) > 0);
|
||||||
|
AssertTrue('uvb.V slot referenced', Pos('$uvb_V', IR) > 0);
|
||||||
|
finally
|
||||||
|
CG.Free();
|
||||||
|
SA.Free();
|
||||||
|
Prog.Free();
|
||||||
|
UB.Free();
|
||||||
|
UA.Free();
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TMultifileTests);
|
RegisterTest(TMultifileTests);
|
||||||
end.
|
end.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue