Phase 3 milestone: TList<Integer> + TDictionary<string,Integer> zero leaks

Four bugs fixed on the path to the milestone:

1. Non-generic class fields resolved with FindType instead of
   FindTypeOrInstantiate — typed pointer fields (^Integer, ^string) in
   concrete classes failed with 'Unknown type'.

2. Integer/Boolean/Int64 local variables not zero-initialised — QBE
   validator rejected 'slot read but never stored to' for variables
   written only through var-param pointers.

3. Class method var-param signature wrong — EmitMethodDef emitted the
   param type (w for Integer) rather than 'l' (pointer) for var params;
   EmitParamAllocs spilled the pointer with storew instead of storel.

4. Method call sites did not pass addresses for var-param arguments —
   EmitMethodCallExpr always called EmitExpr (value load) rather than
   forwarding %_var_Name (address) for IsVarParam params.

Milestone result (valgrind --leak-check=full):
  7 allocs, 7 frees, 0 bytes in use at exit, ERROR SUMMARY: 0 errors
This commit is contained in:
Graeme Geldenhuys 2026-04-22 16:58:36 +01:00
parent 266a42b05d
commit d49ebb4f51
3 changed files with 282 additions and 8 deletions

View file

@ -188,10 +188,16 @@ begin
VarName := Decl.Names[J];
case Decl.ResolvedType.Kind of
tyInteger, tyUInt32, tyBoolean, tyByte:
EmitLine(Format(' %%_var_%s =l alloc4 1', [VarName]));
begin
EmitLine(Format(' %%_var_%s =l alloc4 1', [VarName]));
EmitLine(Format(' storew 0, %%_var_%s', [VarName]));
end;
tyInt64:
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
begin
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
end;
tyString:
begin
@ -802,6 +808,14 @@ begin
for I := 0 to AMethod.Params.Count - 1 do
begin
Par := TMethodParam(AMethod.Params[I]);
if Par.IsVarParam then
begin
{ Var param arrives as a pointer — spill pointer into a local slot }
EmitLine(Format(' %%_var_%s =l alloc8 1', [Par.ParamName]));
EmitLine(Format(' storel %%_par_%s, %%_var_%s',
[Par.ParamName, Par.ParamName]));
end
else
case Par.ResolvedType.Kind of
tyInteger, tyUInt32, tyBoolean, tyByte:
begin
@ -842,8 +856,11 @@ begin
for I := 0 to AMethod.Params.Count - 1 do
begin
Par := TMethodParam(AMethod.Params[I]);
Sig := Sig + Format(', %s %%_par_%s',
[QbeTypeOf(Par.ResolvedType), Par.ParamName]);
if Par.IsVarParam then
Sig := Sig + Format(', l %%_par_%s', [Par.ParamName])
else
Sig := Sig + Format(', %s %%_par_%s',
[QbeTypeOf(Par.ResolvedType), Par.ParamName]);
end;
if IsFunc then
@ -1502,9 +1519,16 @@ begin
ArgLine := Format('l %s', [SelfTemp]);
for I := 0 to MCallExpr.Args.Count - 1 do
begin
Par := TMethodParam(MDecl.Params[I]);
ArgTemp := EmitExpr(TASTExpr(MCallExpr.Args[I]));
ArgLine := ArgLine + Format(', %s %s', [QbeTypeOf(Par.ResolvedType), ArgTemp]);
Par := TMethodParam(MDecl.Params[I]);
if Par.IsVarParam then
{ Pass address of the variable directly — do not load through it }
ArgLine := ArgLine + Format(', l %%_var_%s',
[TIdentExpr(TASTExpr(MCallExpr.Args[I])).Name])
else
begin
ArgTemp := EmitExpr(TASTExpr(MCallExpr.Args[I]));
ArgLine := ArgLine + Format(', %s %s', [QbeTypeOf(Par.ResolvedType), ArgTemp]);
end;
end;
T := AllocTemp;

View file

@ -1045,7 +1045,7 @@ begin
for J := 0 to FieldList.Count - 1 do
begin
FDecl := TFieldDecl(FieldList[J]);
FldType := FTable.FindType(FDecl.TypeName);
FldType := FindTypeOrInstantiate(FDecl.TypeName);
if FldType = nil then
SemanticError(
Format('Unknown type ''%s'' for field', [FDecl.TypeName]),

250
tests/phase3_milestone.pas Normal file
View file

@ -0,0 +1,250 @@
program Phase3Milestone;
{ Phase 3 milestone: exercises TList<Integer> and TDictionary<string,Integer>.
Must produce correct output and zero valgrind leaks. }
type
TIntList = class
FData: ^Integer;
FCount: Integer;
FCapacity: Integer;
procedure Grow;
procedure Add(Value: Integer);
function Get(AIndex: Integer): Integer;
procedure Delete(AIndex: Integer);
procedure Free;
property Count: Integer read FCount;
end;
TStrIntDict = class
FKeys: ^string;
FValues: ^Integer;
FCount: Integer;
FCapacity: Integer;
procedure Grow;
function FindKey(Key: string): Integer;
procedure Add(Key: string; Value: Integer);
function TryGetValue(Key: string; var Value: Integer): Boolean;
function ContainsKey(Key: string): Boolean;
procedure Remove(Key: string);
procedure Free;
property Count: Integer read FCount;
end;
procedure TIntList.Grow;
var
NewCap: Integer;
begin
if Self.FCapacity = 0 then
NewCap := 4
else
NewCap := Self.FCapacity * 2;
Self.FData := ReallocMem(Self.FData, NewCap * SizeOf(Integer));
Self.FCapacity := NewCap
end;
procedure TIntList.Add(Value: Integer);
var
Dest: ^Integer;
begin
if Self.FCount = Self.FCapacity then
Self.Grow;
Dest := Self.FData + Self.FCount * SizeOf(Integer);
Dest^ := Value;
Self.FCount := Self.FCount + 1
end;
function TIntList.Get(AIndex: Integer): Integer;
var
Src: ^Integer;
begin
Src := Self.FData + AIndex * SizeOf(Integer);
Result := Src^
end;
procedure TIntList.Delete(AIndex: Integer);
var
Src: ^Integer;
Dst: ^Integer;
I: Integer;
begin
I := AIndex;
while I < Self.FCount - 1 do
begin
Dst := Self.FData + I * SizeOf(Integer);
Src := Self.FData + (I + 1) * SizeOf(Integer);
Dst^ := Src^;
I := I + 1
end;
Self.FCount := Self.FCount - 1
end;
procedure TIntList.Free;
begin
FreeMem(Self.FData);
FreeMem(Self)
end;
procedure TStrIntDict.Grow;
var
NewCap: Integer;
begin
if Self.FCapacity = 0 then
NewCap := 8
else
NewCap := Self.FCapacity * 2;
Self.FKeys := ReallocMem(Self.FKeys, NewCap * SizeOf(string));
Self.FValues := ReallocMem(Self.FValues, NewCap * SizeOf(Integer));
Self.FCapacity := NewCap
end;
function TStrIntDict.FindKey(Key: string): Integer;
var
I: Integer;
Ptr: ^string;
begin
Result := -1;
I := 0;
while I < Self.FCount do
begin
Ptr := Self.FKeys + I * SizeOf(string);
if Ptr^ = Key then
begin
Result := I;
I := Self.FCount
end
else
I := I + 1
end
end;
procedure TStrIntDict.Add(Key: string; Value: Integer);
var
Idx: Integer;
KPtr: ^string;
VPtr: ^Integer;
begin
Idx := Self.FindKey(Key);
if Idx >= 0 then
begin
VPtr := Self.FValues + Idx * SizeOf(Integer);
VPtr^ := Value
end
else
begin
if Self.FCount = Self.FCapacity then
Self.Grow;
KPtr := Self.FKeys + Self.FCount * SizeOf(string);
VPtr := Self.FValues + Self.FCount * SizeOf(Integer);
KPtr^ := Key;
VPtr^ := Value;
Self.FCount := Self.FCount + 1
end
end;
function TStrIntDict.TryGetValue(Key: string; var Value: Integer): Boolean;
var
Idx: Integer;
VPtr: ^Integer;
begin
Idx := Self.FindKey(Key);
if Idx >= 0 then
begin
VPtr := Self.FValues + Idx * SizeOf(Integer);
Value := VPtr^;
Result := True
end
else
Result := False
end;
function TStrIntDict.ContainsKey(Key: string): Boolean;
begin
Result := Self.FindKey(Key) >= 0
end;
procedure TStrIntDict.Remove(Key: string);
var
Idx: Integer;
I: Integer;
KDst: ^string;
KSrc: ^string;
VDst: ^Integer;
VSrc: ^Integer;
begin
Idx := Self.FindKey(Key);
if Idx >= 0 then
begin
I := Idx;
while I < Self.FCount - 1 do
begin
KDst := Self.FKeys + I * SizeOf(string);
KSrc := Self.FKeys + (I + 1) * SizeOf(string);
VDst := Self.FValues + I * SizeOf(Integer);
VSrc := Self.FValues + (I + 1) * SizeOf(Integer);
KDst^ := KSrc^;
VDst^ := VSrc^;
I := I + 1
end;
Self.FCount := Self.FCount - 1
end
end;
procedure TStrIntDict.Free;
begin
FreeMem(Self.FKeys);
FreeMem(Self.FValues);
FreeMem(Self)
end;
var
List: TIntList;
Dict: TStrIntDict;
V: Integer;
Found: Boolean;
begin
{ --- TList<Integer> --- }
List := TIntList.Create;
List.Add(10);
List.Add(20);
List.Add(30);
List.Add(40);
List.Add(50);
Write('list.count='); WriteLn(List.Count); { 5 }
Write('list[0]='); WriteLn(List.Get(0)); { 10 }
Write('list[4]='); WriteLn(List.Get(4)); { 50 }
List.Delete(1);
Write('count_after_delete='); WriteLn(List.Count); { 4 }
Write('list[1]_after_delete='); WriteLn(List.Get(1)); { 30 }
List.Free;
{ --- TDictionary<string,Integer> --- }
Dict := TStrIntDict.Create;
Dict.Add('alpha', 1);
Dict.Add('beta', 2);
Dict.Add('gamma', 3);
Dict.Add('delta', 4);
Write('dict.count='); WriteLn(Dict.Count); { 4 }
Found := Dict.TryGetValue('beta', V);
Write('beta='); WriteLn(V); { 2 }
Found := Dict.ContainsKey('gamma');
Write('has_gamma='); WriteLn(Found); { 1 }
Dict.Add('beta', 99);
Found := Dict.TryGetValue('beta', V);
Write('beta_after_update='); WriteLn(V); { 99 }
Dict.Remove('alpha');
Write('count_after_remove='); WriteLn(Dict.Count); { 3 }
Found := Dict.ContainsKey('alpha');
Write('has_alpha_after_remove='); WriteLn(Found); { 0 }
Dict.Free
end.