fix(capture): nested-proc capture of outer var-param records and arrays

A nested procedure that captured a `var` parameter (record or static
array) of its enclosing proc emitted the parameter as a global symbol
reference instead of reaching it through the closure pointer, so the
program either failed to link or read/wrote the wrong storage.

Three layers:

* Semantic (uSemantic): CollectCaptures now takes the enclosing
  TMethodDecl and seeds the outer-name set from its PARAMETERS as well
  as its locals, so a captured var-param is detected.  Capture detection
  also descends through TFieldAssignment, TStaticSubscriptAssign,
  TFieldAccessExpr and TStringSubscriptExpr, so `R.F := ...`, `A[i] := ...`
  and their reads contribute the receiver/array name.  The repeated
  "add if outer and not already captured" idiom is hoisted to
  MaybeCaptureName.

* QBE backend: VarRef routes a captured name through `%_cap_<Name>`.
  The aggregate-ident read distinguishes a captured plain local (the
  `_cap_` slot IS the aggregate address) from a captured var/out param
  (the slot holds the caller's pointer, needing one extra load).

* Native backend: base resolution is unified through EmitVarBaseToReg,
  which checks IsCaptured first.  The static-array element paths and the
  aggregate-ident path now treat a captured var-param array like the
  record field paths: AWantAddress=False for a var-param capture (one
  deref to the data pointer), True for a plain-local capture.

E2E regression tests (run on both backends): capture of a var record
param, a plain local record, and a var static-array param.
This commit is contained in:
Graeme Geldenhuys 2026-06-27 20:56:11 +01:00
parent b5726091be
commit 1e3f3ccc5d
4 changed files with 321 additions and 266 deletions

View file

@ -278,6 +278,10 @@ type
{ True when AName is a captured outer-scope variable in the current
nested function (accessed via an implicit pointer param). }
function IsCaptured(const AName: string): Boolean;
{ Load a variable's base (address or stored value) into ADstReg, handling
local / global / nested-captured variables uniformly. }
procedure EmitVarBaseToReg(const AName: string; AWantAddress: Boolean;
const ADstReg: string);
{ True when AName is a slot in the current function frame. }
function IsLocal(const AName: string): Boolean;
{ The AT&T operand addressing AName: "-N(%rbp)" for a frame local,
@ -3643,6 +3647,43 @@ begin
Result := (FCapturedVars <> nil) and (FCapturedVars.IndexOf(AName) >= 0);
end;
procedure TX86_64Backend.EmitVarBaseToReg(const AName: string;
AWantAddress: Boolean; const ADstReg: string);
{ Load the base of the variable AName into ADstReg, handling locals, globals
and variables CAPTURED from an enclosing proc uniformly.
AWantAddress = True -> ADstReg receives the ADDRESS of the variable's own
storage (what `leaq` / EmitVarAddr would give for a
local; the record base for a plain-record variable).
AWantAddress = False -> ADstReg receives the VALUE stored in the variable
(one dereference; e.g. the pointer held by a
var-param or class slot).
A captured variable is reached through its hidden `_cap_<Name>` pointer slot,
which holds the ADDRESS of the enclosing variable's storage — exactly the
AWantAddress=True result. For AWantAddress=False one further load yields the
stored value, mirroring the local case. }
begin
if Self.IsCaptured(AName) then
begin
Self.Emit(Format(#9'movq %s, %s',
[Self.VarOperand('_cap_' + AName), ADstReg]));
if not AWantAddress then
Self.Emit(Format(#9'movq (%s), %s', [ADstReg, ADstReg]));
end
{ sret Result: its slot holds the caller's record buffer POINTER, so the
"address of the record" is the stored value, not a leaq of the slot.
(A captured name is never Result, so this follows the capture branch.) }
else if FSretFunc and SameText(AName, 'Result') then
Self.Emit(Format(#9'movq %s, %s', [Self.VarOperand('Result'), ADstReg]))
else if AWantAddress then
Self.EmitVarAddr(AName, ADstReg)
else if Self.IsLocal(AName) then
Self.Emit(Format(#9'movq %s, %s', [Self.VarOperand(AName), ADstReg]))
else
Self.Emit(Format(#9'movq %s(%%rip), %s', [AName, ADstReg]));
end;
function TX86_64Backend.IsLocal(const AName: string): Boolean;
begin
Result := (FFrame <> nil) and FFrame.ContainsKey(AName);
@ -4223,23 +4264,20 @@ begin
else if FAE.IsClassAccess or FAE.IsVarParam then
begin
{ Class variable / var-param: the slot holds a POINTER — load it. }
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rdx');
if FAE.IsClassAccess and FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rdx), %rdx');
end
else if Self.IsLocal(FAE.RecordName) then
{ The slot IS the record for an ordinary local, but holds a POINTER for
the sret-function Result — EmitLocalRecordBase loads vs leaq-s
accordingly. Without this, SetLength(Result.DynField, N) and other
l-value field slots in an sret function added the field offset to the
address of the pointer slot instead of to the pointed-to record. }
Self.EmitLocalRecordBase(FAE.RecordName, '%rdx')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rdx');
{ Plain value record: take the address of its storage. For an ordinary
local the slot IS the record; for the sret Result the slot holds the
buffer POINTER; for a nested-captured record the _cap_ slot holds the
record's address — EmitVarBaseToReg(AWantAddress=True) handles all
three. Without this, SetLength(Result.DynField, N) and other l-value
field slots in an sret function added the field offset to the address
of the pointer slot instead of to the pointed-to record. }
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rdx');
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rdx', [FAE.FieldInfo.Offset]));
Exit;
@ -4613,7 +4651,7 @@ begin
end
else
begin
Self.EmitVarAddr(FAE.RecordName, '%rdx');
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rdx');
end;
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rdx', [FAE.FieldInfo.Offset]));
@ -5368,6 +5406,14 @@ begin
begin
if FSretFunc and (TIdentExpr(AExpr).Name = 'Result') then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand('Result')]))
else if Self.IsCaptured(TIdentExpr(AExpr).Name) then
{ Captured outer record/static-array: the _cap_ slot holds the address
of the enclosing variable's storage. For a captured plain local that
IS the aggregate address (AWantAddress=True); for a captured var/out
param the enclosing slot holds the caller's pointer, so one extra
dereference (AWantAddress=False) yields the aggregate address. }
Self.EmitVarBaseToReg(TIdentExpr(AExpr).Name,
TIdentExpr(AExpr).ParamMode = pmNone, '%rax')
else if Self.IsLocal(TIdentExpr(AExpr).Name) and
(TIdentExpr(AExpr).ParamMode <> pmNone) then
{ Any param mode: the slot holds the record/array ADDRESS (value
@ -7041,10 +7087,7 @@ begin
if NativeExprOwnsRef(FAE.Base) then
Self.Emit(#9'pushq %rax');
end
else if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rax', [FAE.RecordName]));
else Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
Self.Emit(#9'movq (%rax), %rax');
Self.Emit(#9'movq (%rax), %rax');
Self.Emit(#9'movq 16(%rax), %rax');
@ -7069,10 +7112,7 @@ begin
if NativeExprOwnsRef(FAE.Base) then
Self.Emit(#9'pushq %rax');
end
else if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rax', [FAE.RecordName]));
else Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
Self.Emit(#9'movq (%rax), %rax');
Self.Emit(#9'movq (%rax), %rax');
if (FAE.Base <> nil) and NativeExprOwnsRef(FAE.Base) then
@ -7111,10 +7151,7 @@ begin
end
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rdi');
if FAE.IsClassAccess and FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rdi), %rdi');
@ -7172,18 +7209,12 @@ begin
end
else if FAE.IsClassAccess or FAE.IsVarParam then
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if FAE.IsClassAccess and FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if Self.IsLocal(FAE.RecordName) then
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
else Self.EmitVarBaseToReg(FAE.RecordName, True, '%rcx');
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FAE.FieldInfo.Offset]));
Self.Emit(#9'movq (%rcx), %rcx'); { string data pointer }
@ -7222,18 +7253,12 @@ begin
begin
{ Class variable / var-param: the slot holds a POINTER to the object
or record — load it. }
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if FAE.IsClassAccess and FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if Self.IsLocal(FAE.RecordName) then
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
else Self.EmitVarBaseToReg(FAE.RecordName, True, '%rcx');
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FAE.FieldInfo.Offset]));
if FAE.FieldInfo.TypeDesc.Kind = tyDynArray then
@ -7376,10 +7401,7 @@ begin
end
else if FAE.IsClassAccess then
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
@ -7392,10 +7414,7 @@ begin
end
else if FAE.IsVarParam then
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if (IsJumboSet(FAE.FieldInfo.TypeDesc) or
(FAE.FieldInfo.TypeDesc.Kind in [tyRecord, tyStaticArray])) then
Self.Emit(Format(#9'leaq %d(%%rcx), %%rax', [FAE.FieldInfo.Offset]))
@ -7403,53 +7422,35 @@ begin
Self.EmitLoadVar(Format('%d(%%rcx)', [FAE.FieldInfo.Offset]),
FAE.FieldInfo.TypeDesc);
end
else if Self.IsLocal(FAE.RecordName) then
begin
if (IsJumboSet(FAE.FieldInfo.TypeDesc) or
(FAE.FieldInfo.TypeDesc.Kind in [tyRecord, tyStaticArray])) then
begin
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx');
if FAE.FieldInfo.Offset = 0 then
Self.Emit(#9'movq %rcx, %rax')
else
Self.Emit(Format(#9'leaq %d(%%rcx), %%rax', [FAE.FieldInfo.Offset]));
end
else
begin
{ Route through EmitLocalRecordBase for BOTH offset 0 and > 0: for the
sret-function Result the slot holds the caller-buffer POINTER, so the
field must be read indirectly (movq slot -> %rcx; load N(%rcx)). The
old offset-0 fast path read the slot directly, which for sret Result
read the pointer value as the field (Result.K = const always false). }
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx');
Self.EmitLoadVar(Format('%d(%%rcx)', [FAE.FieldInfo.Offset]),
FAE.FieldInfo.TypeDesc);
end;
end
else if (not Self.IsCaptured(FAE.RecordName)) and
(not (FSretFunc and SameText(FAE.RecordName, 'Result'))) and
(not Self.IsLocal(FAE.RecordName)) and
(FAE.FieldInfo.Offset = 0) and
(not (IsJumboSet(FAE.FieldInfo.TypeDesc) or
(FAE.FieldInfo.TypeDesc.Kind in [tyRecord, tyStaticArray]))) and
(not Self.IsThreadVarGlobal(FAE.RecordName)) then
{ Non-threadvar offset-0 fast path: read the field directly off the
PC-relative global. Only applies to a genuine global (not local, not
captured, not sret Result, not threadvar). }
Self.EmitLoadVar(FAE.RecordName + '(%rip)', FAE.FieldInfo.TypeDesc)
else
begin
{ Compute the record base into %rcx, handling local / global / sret-Result
/ nested-captured uniformly, then read or address the field. Aggregate
fields (record/static array/jumbo set) yield an ADDRESS in %rax; scalar
fields are loaded by value. }
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rcx');
if (IsJumboSet(FAE.FieldInfo.TypeDesc) or
(FAE.FieldInfo.TypeDesc.Kind in [tyRecord, tyStaticArray])) then
begin
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
if FAE.FieldInfo.Offset = 0 then
Self.Emit(#9'movq %rcx, %rax')
else
Self.Emit(Format(#9'leaq %d(%%rcx), %%rax', [FAE.FieldInfo.Offset]));
end
else if (FAE.FieldInfo.Offset = 0) and
(not Self.IsThreadVarGlobal(FAE.RecordName)) then
{ Non-threadvar offset-0 fast path: read the field directly off the
PC-relative global. threadvars must not use this — the slot is
TLS-resident, so fall through to EmitLeaqGlobal (which emits the
%fs:0 + @tpoff sequence) and an indirect load. }
Self.EmitLoadVar(FAE.RecordName + '(%rip)', FAE.FieldInfo.TypeDesc)
else
begin
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
Self.EmitLoadVar(Format('%d(%%rcx)', [FAE.FieldInfo.Offset]),
FAE.FieldInfo.TypeDesc);
end;
end;
Exit;
end;
@ -7476,7 +7477,7 @@ begin
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(FAE.RecordName)]))
else if MD.IsRecordMethod then
begin
Self.EmitVarAddr(FAE.RecordName, '%rdi');
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rdi');
end
else if FAE.IsVarParam then
begin
@ -7485,10 +7486,7 @@ begin
end
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rdi');
end;
if MD.VTableSlot >= 0 then
begin
@ -7634,10 +7632,7 @@ begin
end
else if FAE.IsClassAccess then
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
@ -7646,10 +7641,7 @@ begin
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rcx');
end;
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FAE.FieldInfo.Offset]));
@ -7711,10 +7703,7 @@ begin
end
else if FAE.IsClassAccess then
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rcx');
if FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
@ -7723,10 +7712,7 @@ begin
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FAE.RecordName)]))
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.EmitLocalRecordBase(FAE.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rcx');
Self.EmitVarBaseToReg(FAE.RecordName, True, '%rcx');
end;
if (FAE.FieldInfo <> nil) and (FAE.FieldInfo.Offset > 0) then
Self.Emit(Format(#9'addq $%d, %%rcx', [FAE.FieldInfo.Offset]));
@ -8426,18 +8412,12 @@ begin
begin
{ The slot holds a POINTER (sret Result, class instance, var-param
record) — load it. }
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rax', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
if FAE.IsClassAccess and FAE.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rax), %rax');
end
else if Self.IsLocal(FAE.RecordName) then
Self.EmitLocalRecordBase(FAE.RecordName, '%rax')
else
Self.EmitLeaqGlobal(FAE.RecordName, '%rax');
else Self.EmitVarBaseToReg(FAE.RecordName, True, '%rax');
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rax', [FAE.FieldInfo.Offset]));
end
@ -10852,10 +10832,7 @@ begin
end
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rax', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
Self.Emit(#9'movq %rax, 8(%rcx)');
end;
Exit;
@ -11934,10 +11911,7 @@ begin
end
else
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rdi');
if FA.IsClassAccess and FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rdi), %rdi');
@ -11999,16 +11973,13 @@ begin
end
else if FA.IsClassAccess or FA.IsVarParam then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rdx');
if FA.IsClassAccess and FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rdx), %rdx');
end
else
Self.EmitVarAddr(FA.RecordName, '%rdx');
Self.EmitVarBaseToReg(FA.RecordName, True, '%rdx');
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rdx', [FA.FieldInfo.Offset]));
if FA.FieldInfo.TypeDesc.Kind = tyDynArray then
@ -12084,25 +12055,16 @@ begin
end
else if FA.IsClassAccess then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if FA.IsVarParam then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
end
else if Self.IsLocal(FA.RecordName) then
Self.EmitLocalRecordBase(FA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FA.RecordName, '%rcx');
else Self.EmitVarBaseToReg(FA.RecordName, True, '%rcx');
Self.EmitInterfaceToFieldSlotsAt(FA.Expr, '%rcx', FA.FieldInfo.Offset,
FA.FieldInfo.TypeDesc);
Exit;
@ -12140,25 +12102,16 @@ begin
end
else if FA.IsClassAccess then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if FA.IsVarParam then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
end
else if Self.IsLocal(FA.RecordName) then
Self.EmitLocalRecordBase(FA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FA.RecordName, '%rcx');
else Self.EmitVarBaseToReg(FA.RecordName, True, '%rcx');
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FA.FieldInfo.Offset]));
{ Store the code pointer at offset 0. }
@ -12175,10 +12128,7 @@ begin
end
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rax', [FAE.RecordName]));
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
Self.Emit(#9'movq %rax, 8(%rcx)');
end;
Exit;
@ -12210,24 +12160,18 @@ begin
end
else if FA.IsClassAccess then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rbx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rbx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rbx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rbx), %rbx');
end
else if FA.IsVarParam then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rbx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rbx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rbx');
end
else
begin
Self.EmitVarAddr(FA.RecordName, '%rbx');
Self.EmitVarBaseToReg(FA.RecordName, True, '%rbx');
end;
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rbx', [FA.FieldInfo.Offset]));
@ -12261,17 +12205,14 @@ begin
end
else if FA.IsClassAccess then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rbx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rbx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rbx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rbx), %rbx');
end
else
begin
Self.EmitVarAddr(FA.RecordName, '%rbx');
Self.EmitVarBaseToReg(FA.RecordName, True, '%rbx');
end;
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rbx', [FA.FieldInfo.Offset]));
@ -12316,10 +12257,7 @@ begin
begin
Self.Emit(#9'subq $8, %rsp');
Self.EmitStoreFloat('(%rsp)', FA.FieldInfo.TypeDesc);
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
@ -12330,19 +12268,13 @@ begin
begin
Self.Emit(#9'subq $8, %rsp');
Self.EmitStoreFloat('(%rsp)', FA.FieldInfo.TypeDesc);
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
Self.EmitLoadFloat('(%rsp)', FA.FieldInfo.TypeDesc);
Self.Emit(#9'addq $8, %rsp');
end
else
begin
if Self.IsLocal(FA.RecordName) then
Self.EmitLocalRecordBase(FA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FA.RecordName, '%rcx');
Self.EmitVarBaseToReg(FA.RecordName, True, '%rcx');
end;
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FA.FieldInfo.Offset]));
@ -12375,18 +12307,12 @@ begin
end
else if FA.IsClassAccess or FA.IsVarParam then
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.IsClassAccess and FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if Self.IsLocal(FA.RecordName) then
Self.EmitLocalRecordBase(FA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FA.RecordName, '%rcx');
else Self.EmitVarBaseToReg(FA.RecordName, True, '%rcx');
if FA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [FA.FieldInfo.Offset]));
Self.Emit(#9'pushq %rbx');
@ -12511,10 +12437,7 @@ begin
end
else
begin
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
@ -12601,10 +12524,7 @@ begin
else if FA.IsVarParam then
begin
Self.Emit(#9'pushq %rax');
if Self.IsLocal(FA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(FA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [FA.RecordName]));
Self.EmitVarBaseToReg(FA.RecordName, False, '%rcx');
if FA.FieldInfo.TypeDesc.IsString() then
begin
if not NativeExprOwnsRef(FA.Expr) then
@ -12659,10 +12579,7 @@ begin
else
begin
Self.Emit(#9'pushq %rax');
if Self.IsLocal(FA.RecordName) then
Self.EmitLocalRecordBase(FA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(FA.RecordName, '%rcx');
Self.EmitVarBaseToReg(FA.RecordName, True, '%rcx');
if FA.FieldInfo.TypeDesc.IsString() then
begin
if not NativeExprOwnsRef(FA.Expr) then
@ -12741,10 +12658,7 @@ begin
end
else
begin
if Self.IsLocal(SSA.ArrayName) then
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(SSA.ArrayName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [SSA.ArrayName]));
Self.EmitVarBaseToReg(SSA.ArrayName, False, '%rdi');
if SSA.IsVarParam then
Self.Emit(#9'movq (%rdi), %rdi'); { var-param: slot -> instance }
end;
@ -12773,7 +12687,7 @@ begin
Self.Emit(#9'pushq %rbx'); { preserve %rbx }
Self.Emit(#9'subq $8, %rsp'); { align to 16 }
{ %rbx := address of the slot holding the data pointer. }
Self.EmitVarAddr(SSA.ArrayName, '%rbx');
Self.EmitVarBaseToReg(SSA.ArrayName, True, '%rbx');
if SSA.IsVarParam then
{ var/out param: slot holds the caller variable's address; that is
where the string pointer actually lives. }
@ -12798,10 +12712,7 @@ begin
Self.EmitByteRhsToEax(SSA.ValueExpr);
Self.Emit(#9'pushq %rax');
Self.EmitExprToEax(SSA.IndexExpr);
if Self.IsLocal(SSA.ArrayName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [SSA.ArrayName]));
Self.EmitVarBaseToReg(SSA.ArrayName, False, '%rcx');
if SSA.IsVarParam then
{ var/out param: slot -> caller var -> char pointer. }
Self.Emit(#9'movq (%rcx), %rcx');
@ -13004,11 +12915,15 @@ begin
if SSA.ImplicitFieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%r14', [SSA.ImplicitFieldInfo.Offset]));
end
else if SSA.IsVarParam or
(FSretFunc and SameText(SSA.ArrayName, 'Result')) then
else if SSA.IsVarParam then
{ var-param array: the slot holds the array POINTER (AWantAddress=False).
For a captured var-param the _cap_ slot holds the address of that
pointer slot, so EmitVarBaseToReg(False) loads it and derefs once. }
Self.EmitVarBaseToReg(SSA.ArrayName, False, '%r14')
else if FSretFunc and SameText(SSA.ArrayName, 'Result') then
Self.Emit(Format(#9'movq %s, %%r14', [Self.VarOperand(SSA.ArrayName)]))
else
Self.EmitVarAddr(SSA.ArrayName, '%r14');
Self.EmitVarBaseToReg(SSA.ArrayName, True, '%r14');
Self.Emit(#9'addq %rax, %r14'); { %r14 = element address }
Self.EmitInterfaceToFieldSlotsAt(SSA.ValueExpr, '%r14', 0, DAElemType);
Self.Emit(#9'popq %r14');
@ -13030,11 +12945,12 @@ begin
if SSA.ImplicitFieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
end
else if SSA.IsVarParam or
(FSretFunc and SameText(SSA.ArrayName, 'Result')) then
else if SSA.IsVarParam then
Self.EmitVarBaseToReg(SSA.ArrayName, False, '%rcx')
else if FSretFunc and SameText(SSA.ArrayName, 'Result') then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
else
Self.EmitVarAddr(SSA.ArrayName, '%rcx');
Self.EmitVarBaseToReg(SSA.ArrayName, True, '%rcx');
Self.Emit(#9'addq %rcx, %rax');
Self.Emit(#9'movq %rax, %rbx');
Self.EmitRecordFieldReleases(TRecordTypeDesc(DAElemType), '%rbx');
@ -13084,11 +13000,12 @@ begin
if SSA.ImplicitFieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
end
else if SSA.IsVarParam or
(FSretFunc and SameText(SSA.ArrayName, 'Result')) then
else if SSA.IsVarParam then
Self.EmitVarBaseToReg(SSA.ArrayName, False, '%rcx')
else if FSretFunc and SameText(SSA.ArrayName, 'Result') then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
else
Self.EmitVarAddr(SSA.ArrayName, '%rcx');
Self.EmitVarBaseToReg(SSA.ArrayName, True, '%rcx');
Self.Emit(#9'addq %rcx, %rax');
Self.Emit(#9'movq %rax, %rcx');
if IsFloatFamily(DAElemType) then
@ -14582,25 +14499,16 @@ begin
end
else if AFA.IsClassAccess then
begin
if Self.IsLocal(AFA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(AFA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [AFA.RecordName]));
Self.EmitVarBaseToReg(AFA.RecordName, False, '%rcx');
if AFA.IsVarParam then
{ var-param class: slot -> caller var -> instance }
Self.Emit(#9'movq (%rcx), %rcx');
end
else if AFA.IsVarParam then
begin
if Self.IsLocal(AFA.RecordName) then
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(AFA.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [AFA.RecordName]));
Self.EmitVarBaseToReg(AFA.RecordName, False, '%rcx');
end
else if Self.IsLocal(AFA.RecordName) then
Self.EmitLocalRecordBase(AFA.RecordName, '%rcx')
else
Self.EmitLeaqGlobal(AFA.RecordName, '%rcx');
else Self.EmitVarBaseToReg(AFA.RecordName, True, '%rcx');
if AFA.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rcx', [AFA.FieldInfo.Offset]));
end;

View file

@ -4862,6 +4862,13 @@ begin
else
Result := '$' + AName;
end
{ A variable captured from an enclosing proc is reached through the hidden
%_cap_<Name> pointer parameter, which holds the address of the enclosing
slot — exactly what %_var_<Name> would be in the owning frame. All the
deref logic keyed off the AST flags (IsVarParam, IsClassAccess) then applies
unchanged, just rooted at %_cap_ instead of %_var_. }
else if IsCaptured(AName) then
Result := '%_cap_' + AName
else
Result := '%_var_' + AName;
end;
@ -12304,8 +12311,14 @@ begin
if (AExpr.ResolvedType <> nil) and
IsAggregateAddrType(AExpr.ResolvedType) then
begin
{ Aggregate: %_cap_Name is the storage address — return directly. }
EmitLine(Format(' %s =l copy %%_cap_%s', [T, TIdentExpr(AExpr).Name]));
{ Aggregate: %_cap_Name holds the address of the enclosing variable's
storage. For a captured plain local that IS the aggregate address.
For a captured var/out param the enclosing slot holds the caller's
pointer, so one extra load yields the aggregate address. }
if TIdentExpr(AExpr).ParamMode <> pmNone then
EmitLine(Format(' %s =l loadl %%_cap_%s', [T, TIdentExpr(AExpr).Name]))
else
EmitLine(Format(' %s =l copy %%_cap_%s', [T, TIdentExpr(AExpr).Name]));
Exit(T);
end;
case QType of

View file

@ -239,7 +239,11 @@ type
procedure AnalyseStandaloneDecls(ABlock: TBlock);
procedure AnalyseStandaloneBodies(ABlock: TBlock);
procedure AnalyseStandaloneDecl(ADecl: TMethodDecl);
procedure CollectCaptures(ADecl: TMethodDecl; AOuterBlock: TBlock);
procedure CollectCaptures(ADecl: TMethodDecl; AOuterDecl: TMethodDecl);
{ Add AName to ADecl.CapturedVars if it names an enclosing variable
(member of AOuterVars) not already recorded. }
procedure MaybeCaptureName(ADecl: TMethodDecl; AOuterVars: TStringList;
const AName: string);
{ Inlining: after bodies are analysed, mark each TMethodDecl whose body
qualifies for codegen-side inlining. Conservative: primitive params
+ return + locals only; no try/loops/raise/nested defs; small body.
@ -6275,7 +6279,7 @@ begin
{ After analysing the body, determine which outer-scope variables are
captured by any nested proc declared inside this one. }
if ADecl.EnclosingDecl <> nil then
CollectCaptures(ADecl, ADecl.EnclosingDecl.Body);
CollectCaptures(ADecl, ADecl.EnclosingDecl);
end;
finally
Dec(FScopeDepth);
@ -6302,11 +6306,24 @@ begin
end;
end;
procedure TSemanticAnalyser.CollectCaptures(ADecl: TMethodDecl; AOuterBlock: TBlock);
{ Walk ADecl's body statements/expressions to find all TIdentExpr nodes whose
name matches a variable declared in AOuterBlock (the enclosing proc's locals).
Each such variable is "captured": ADecl will receive an implicit hidden
var-by-pointer parameter, and the call site will pass the variable's address. }
procedure TSemanticAnalyser.MaybeCaptureName(ADecl: TMethodDecl;
AOuterVars: TStringList; const AName: string);
begin
if AName = '' then Exit;
if AOuterVars.IndexOf(AName) < 0 then Exit;
if (ADecl.CapturedVars <> nil) and (ADecl.CapturedVars.IndexOf(AName) >= 0) then
Exit;
if ADecl.CapturedVars = nil then
ADecl.CapturedVars := TStringList.Create();
ADecl.CapturedVars.Add(AName);
end;
procedure TSemanticAnalyser.CollectCaptures(ADecl: TMethodDecl; AOuterDecl: TMethodDecl);
{ Walk ADecl's body statements/expressions to find every reference to a variable
belonging to the enclosing proc AOuterDecl — its locals AND its parameters
(a `var` record parameter captured by a nested proc is the case that broke
before). Each such name is "captured": ADecl receives an implicit hidden
var-by-pointer parameter, and the call site passes the variable's address. }
var
OuterVars: TStringList;
I, J: Integer;
@ -6318,21 +6335,31 @@ var
CurStmt: TASTStmt;
begin
if ADecl.Body = nil then Exit;
if AOuterDecl = nil then Exit;
OuterVars := TStringList.Create();
TodoExprs := TObjectList.Create(False);
TodoStmts := TObjectList.Create(False);
try
{ Build set of outer-block local variable names }
for I := 0 to AOuterBlock.Decls.Count - 1 do
begin
VDecl := TVarDecl(AOuterBlock.Decls.Items[I]);
for J := 0 to VDecl.Names.Count - 1 do
{ Build the set of enclosing names: the outer proc's local var decls ... }
if AOuterDecl.Body <> nil then
for I := 0 to AOuterDecl.Body.Decls.Count - 1 do
begin
VName := VDecl.Names.Strings[J];
if OuterVars.IndexOf(VName) < 0 then
OuterVars.Add(VName);
VDecl := TVarDecl(AOuterDecl.Body.Decls.Items[I]);
for J := 0 to VDecl.Names.Count - 1 do
begin
VName := VDecl.Names.Strings[J];
if OuterVars.IndexOf(VName) < 0 then
OuterVars.Add(VName);
end;
end;
{ ... and the outer proc's PARAMETERS (value, var, out — all capturable;
a captured var-param carries a pointer, handled by codegen). }
for I := 0 to AOuterDecl.Params.Count - 1 do
begin
VName := TMethodParam(AOuterDecl.Params.Items[I]).ParamName;
if OuterVars.IndexOf(VName) < 0 then
OuterVars.Add(VName);
end;
if OuterVars.Count = 0 then Exit;
@ -6354,19 +6381,30 @@ begin
begin
{ LHS name — check if it's an outer var (direct assign) }
if TAssignment(CurStmt).ImplicitSelfField = nil then
begin
VName := TAssignment(CurStmt).Name;
if (OuterVars.IndexOf(VName) >= 0) and
((ADecl.CapturedVars = nil) or
(ADecl.CapturedVars.IndexOf(VName) < 0)) then
begin
if ADecl.CapturedVars = nil then
ADecl.CapturedVars := TStringList.Create();
ADecl.CapturedVars.Add(VName);
end;
end;
MaybeCaptureName(ADecl, OuterVars, TAssignment(CurStmt).Name);
TodoExprs.Add(TAssignment(CurStmt).Expr);
end
else if CurStmt is TFieldAssignment then
begin
{ 'R.Field := ...' — the receiver R may be an outer var/var-param.
(Implicit-Self writes have RecordName naming a field of Self, not an
outer var; those resolve through Self, never captured.) }
if not TFieldAssignment(CurStmt).IsImplicitSelf then
MaybeCaptureName(ADecl, OuterVars, TFieldAssignment(CurStmt).RecordName);
TodoExprs.Add(TFieldAssignment(CurStmt).ObjExpr);
TodoExprs.Add(TFieldAssignment(CurStmt).PropIndexExpr);
TodoExprs.Add(TFieldAssignment(CurStmt).Expr);
end
else if CurStmt is TStaticSubscriptAssign then
begin
{ 'A[i] := ...' — the array A may be an outer var/var-param.
(Implicit-Self array writes resolve through Self.) }
if not TStaticSubscriptAssign(CurStmt).IsImplicitSelf then
MaybeCaptureName(ADecl, OuterVars, TStaticSubscriptAssign(CurStmt).ArrayName);
TodoExprs.Add(TStaticSubscriptAssign(CurStmt).BaseExpr);
TodoExprs.Add(TStaticSubscriptAssign(CurStmt).IndexExpr);
TodoExprs.Add(TStaticSubscriptAssign(CurStmt).ValueExpr);
end
else if CurStmt is TProcCall then
begin
for J := 0 to TProcCall(CurStmt).Args.Count - 1 do
@ -6415,16 +6453,22 @@ begin
if CurExpr = nil then Continue;
if CurExpr is TIdentExpr then
MaybeCaptureName(ADecl, OuterVars, TIdentExpr(CurExpr).Name)
else if CurExpr is TFieldAccessExpr then
begin
VName := TIdentExpr(CurExpr).Name;
if (OuterVars.IndexOf(VName) >= 0) and
((ADecl.CapturedVars = nil) or
(ADecl.CapturedVars.IndexOf(VName) < 0)) then
begin
if ADecl.CapturedVars = nil then
ADecl.CapturedVars := TStringList.Create();
ADecl.CapturedVars.Add(VName);
end;
{ 'R.Field' read — the base R may be an outer var/var-param. When the
base is a sub-expression (chain), descend; when it is a bare name,
capture it. }
if TFieldAccessExpr(CurExpr).Base <> nil then
TodoExprs.Add(TFieldAccessExpr(CurExpr).Base)
else
MaybeCaptureName(ADecl, OuterVars, TFieldAccessExpr(CurExpr).RecordName);
TodoExprs.Add(TFieldAccessExpr(CurExpr).PropIndexExpr);
end
else if CurExpr is TStringSubscriptExpr then
begin
TodoExprs.Add(TStringSubscriptExpr(CurExpr).StrExpr);
TodoExprs.Add(TStringSubscriptExpr(CurExpr).IndexExpr);
end
else if CurExpr is TBinaryExpr then
begin

View file

@ -81,6 +81,17 @@ type
{ Nested procedures }
procedure TestRun_NestedProc_MutatesCapturedVar;
{ Nested proc captures an outer VAR RECORD PARAMETER (read + write through
the var-param fields). Previously the nested proc emitted the parameter
as a global symbol reference and the program failed to link / ran wrong. }
procedure TestRun_NestedProc_CapturesVarRecordParam;
{ Nested proc captures an outer plain LOCAL record (field read + write). }
procedure TestRun_NestedProc_CapturesLocalRecord;
{ Nested proc captures an outer VAR ARRAY PARAMETER (element read + write).
The captured var-param array slot is reached through the _cap_ pointer
with one extra dereference; without it the element address resolved to a
global symbol and the writes/reads hit the wrong storage. }
procedure TestRun_NestedProc_CapturesVarArrayParam;
{ Diamond operator: TFoo<> infers type args from LHS }
procedure TestRun_Diamond_SingleArg_WorksAtRuntime;
@ -996,6 +1007,85 @@ begin
'5' + LE + '15' + LE + '15' + LE, Output);
end;
procedure TE2EMiscTests.TestRun_NestedProc_CapturesVarRecordParam;
const
Src =
'''
program Prg;
type TRec = record A, B: Integer; end;
procedure Outer(var R: TRec);
procedure Inner;
var Sum: Integer;
begin
Sum := R.A + R.B;
R.A := Sum;
R.B := Sum * 2
end;
begin
Inner
end;
var Rec: TRec;
begin
Rec.A := 3; Rec.B := 4;
Outer(Rec);
WriteLn(IntToStr(Rec.A), ' ', IntToStr(Rec.B))
end.
''';
begin
AssertRunsOnAll(Src, '7 14' + LE, 0);
end;
procedure TE2EMiscTests.TestRun_NestedProc_CapturesLocalRecord;
const
Src =
'''
program Prg;
type TRec = record A, B: Integer; end;
procedure Outer;
var R: TRec;
procedure Inner;
begin
R.A := R.A + 10;
R.B := R.B + 20
end;
begin
R.A := 1; R.B := 2;
Inner;
WriteLn(IntToStr(R.A), ' ', IntToStr(R.B))
end;
begin
Outer
end.
''';
begin
AssertRunsOnAll(Src, '11 22' + LE, 0);
end;
procedure TE2EMiscTests.TestRun_NestedProc_CapturesVarArrayParam;
const
Src =
'''
program Prg;
type TArr = array[0..2] of Integer;
procedure Outer(var A: TArr);
procedure Inner;
begin
A[0] := 10; A[1] := 20; A[2] := A[0] + A[1]
end;
begin
Inner
end;
var Ar: TArr;
begin
Ar[0] := 0; Ar[1] := 0; Ar[2] := 0;
Outer(Ar);
WriteLn(IntToStr(Ar[0]), ' ', IntToStr(Ar[1]), ' ', IntToStr(Ar[2]))
end.
''';
begin
AssertRunsOnAll(Src, '10 20 30' + LE, 0);
end;
procedure TE2EMiscTests.TestRun_Diamond_SingleArg_WorksAtRuntime;
const
Src = '''