fix(codegen): return fixed-size static array by value via sret (#112)
Functions returning a static array >8 bytes now use the sret ABI on both QBE and native x86-64 backends: the caller passes the destination buffer as a hidden first argument, the callee writes through it, and returns void. Previously only the first 8 bytes survived (QBE segfaulted, native rejected the function outright). Key changes: - QBE: IsRecordCall, signature, prologue, epilogue, and assignment dispatch all extended to handle tyStaticArray alongside tyRecord - Native: allowed return type guard, BuildFrame FSretFunc, sret call-site dispatch, and subscript-assign Result indirection all extended for tyStaticArray
This commit is contained in:
parent
d4a066aa93
commit
cdd1d0cbe2
|
|
@ -3547,6 +3547,11 @@ begin
|
|||
FSretFunc := True;
|
||||
Self.AddSlot('Result', nil, Offset);
|
||||
end
|
||||
else if ADecl.ResolvedReturnType.Kind = tyStaticArray then
|
||||
begin
|
||||
FSretFunc := True;
|
||||
Self.AddSlot('Result', nil, Offset);
|
||||
end
|
||||
else if ADecl.ResolvedReturnType.Kind = tyRecord then
|
||||
begin
|
||||
FRecRetClass := ClassifyRecordReturn(
|
||||
|
|
@ -9610,11 +9615,13 @@ begin
|
|||
record/jumbo-set-returning call. Pass the destination buffer address as
|
||||
the hidden first arg (%rdi). }
|
||||
if (Asgn.ResolvedLhsType <> nil) and
|
||||
((Asgn.ResolvedLhsType.Kind = tyRecord) or IsJumboSet(Asgn.ResolvedLhsType)) and
|
||||
((Asgn.ResolvedLhsType.Kind in [tyRecord, tyStaticArray]) or
|
||||
IsJumboSet(Asgn.ResolvedLhsType)) and
|
||||
(Asgn.Expr is TFuncCallExpr) and
|
||||
(TFuncCallExpr(Asgn.Expr).ResolvedDecl <> nil) and
|
||||
(TMethodDecl(TFuncCallExpr(Asgn.Expr).ResolvedDecl).ResolvedReturnType <> nil) and
|
||||
((TMethodDecl(TFuncCallExpr(Asgn.Expr).ResolvedDecl).ResolvedReturnType.Kind = tyRecord) or
|
||||
((TMethodDecl(TFuncCallExpr(Asgn.Expr).ResolvedDecl).ResolvedReturnType.Kind in
|
||||
[tyRecord, tyStaticArray]) or
|
||||
IsJumboSet(TMethodDecl(TFuncCallExpr(Asgn.Expr).ResolvedDecl).ResolvedReturnType)) then
|
||||
begin
|
||||
{ For a local jumbo set the dest is leaq'd; EmitSretCall's caller passes
|
||||
|
|
@ -9651,11 +9658,13 @@ begin
|
|||
{ sret assignment: method call returning a record (or jumbo set).
|
||||
%rdi = sret ptr, %rsi = Self, %rdx.. = user args. }
|
||||
if (Asgn.ResolvedLhsType <> nil) and
|
||||
((Asgn.ResolvedLhsType.Kind = tyRecord) or IsJumboSet(Asgn.ResolvedLhsType)) and
|
||||
((Asgn.ResolvedLhsType.Kind in [tyRecord, tyStaticArray]) or
|
||||
IsJumboSet(Asgn.ResolvedLhsType)) and
|
||||
(Asgn.Expr is TMethodCallExpr) and
|
||||
(TMethodCallExpr(Asgn.Expr).ResolvedMethod <> nil) and
|
||||
(TMethodDecl(TMethodCallExpr(Asgn.Expr).ResolvedMethod).ResolvedReturnType <> nil) and
|
||||
((TMethodDecl(TMethodCallExpr(Asgn.Expr).ResolvedMethod).ResolvedReturnType.Kind = tyRecord) or
|
||||
((TMethodDecl(TMethodCallExpr(Asgn.Expr).ResolvedMethod).ResolvedReturnType.Kind in
|
||||
[tyRecord, tyStaticArray]) or
|
||||
IsJumboSet(TMethodDecl(TMethodCallExpr(Asgn.Expr).ResolvedMethod).ResolvedReturnType)) then
|
||||
begin
|
||||
if FSretFunc and SameText(Asgn.Name, 'Result') then
|
||||
|
|
@ -11480,8 +11489,8 @@ begin
|
|||
if SSA.ImplicitFieldInfo.Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
|
||||
end
|
||||
else if SSA.IsVarParam then
|
||||
{ var/out param: the slot HOLDS the caller array's address. }
|
||||
else if SSA.IsVarParam or
|
||||
(FSretFunc and SameText(SSA.ArrayName, 'Result')) then
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
else if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'leaq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
|
|
@ -11536,8 +11545,8 @@ begin
|
|||
if SSA.ImplicitFieldInfo.Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
|
||||
end
|
||||
else if SSA.IsVarParam then
|
||||
{ var/out param: the slot HOLDS the caller array's address. }
|
||||
else if SSA.IsVarParam or
|
||||
(FSretFunc and SameText(SSA.ArrayName, 'Result')) then
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
else if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'leaq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
|
|
@ -13930,9 +13939,10 @@ begin
|
|||
if (ADecl.ResolvedReturnType <> nil) and
|
||||
not IsIntFamily(ADecl.ResolvedReturnType) and
|
||||
not IsFloatFamily(ADecl.ResolvedReturnType) and
|
||||
not (ADecl.ResolvedReturnType.Kind in [tyRecord, tyString, tyPChar, tyPointer,
|
||||
tyClass, tyMetaClass, tyDynArray,
|
||||
tyInterface, tyProcedural, tySet]) then
|
||||
not (ADecl.ResolvedReturnType.Kind in [tyRecord, tyStaticArray, tyString,
|
||||
tyPChar, tyPointer, tyClass, tyMetaClass,
|
||||
tyDynArray, tyInterface, tyProcedural,
|
||||
tySet]) then
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported return type (function ' + ADecl.Name + ')');
|
||||
|
||||
|
|
|
|||
|
|
@ -4249,16 +4249,18 @@ begin
|
|||
[VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp,
|
||||
TSetTypeDesc(AAssign.ResolvedLhsType).RawSize()]));
|
||||
end
|
||||
else if (AAssign.ResolvedLhsType <> nil) and
|
||||
(AAssign.ResolvedLhsType.Kind = tyStaticArray) and
|
||||
IsRecordCall(AAssign.Expr) then
|
||||
begin
|
||||
EmitRecordCallSret(AAssign.Expr, VarRef(AAssign.Name, AAssign.IsGlobal));
|
||||
end
|
||||
else if (AAssign.ResolvedLhsType <> nil) and
|
||||
(AAssign.ResolvedLhsType.Kind = tyRecord) then
|
||||
begin
|
||||
{ Record assignment (non-var-param LHS — the var/out case is handled in the
|
||||
IsVarParam branch above): use sret (directly into dest) or field-by-field
|
||||
copy. }
|
||||
ClassRT := TRecordTypeDesc(AAssign.ResolvedLhsType);
|
||||
if IsRecordCall(AAssign.Expr) then
|
||||
begin
|
||||
{ Release old ARC fields, zero the slot, then call function with dest as sret }
|
||||
EmitRecordReleaseFields(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal));
|
||||
EmitLine(Format(' call $memset(l %s, w 0, l %d)',
|
||||
[VarRef(AAssign.Name, AAssign.IsGlobal), ClassRT.TotalSize()]));
|
||||
|
|
@ -4266,7 +4268,6 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
{ RHS is a record variable: get its address then ARC-copy field-by-field }
|
||||
ValTemp := EmitExpr(AAssign.Expr);
|
||||
EmitRecordCopy(ClassRT, VarRef(AAssign.Name, AAssign.IsGlobal), ValTemp);
|
||||
end;
|
||||
|
|
@ -4857,14 +4858,14 @@ begin
|
|||
if TFuncCallExpr(AExpr).ResolvedDecl = nil then Exit;
|
||||
MDecl := TMethodDecl(TFuncCallExpr(AExpr).ResolvedDecl);
|
||||
Result := (MDecl.ResolvedReturnType <> nil) and
|
||||
(MDecl.ResolvedReturnType.Kind = tyRecord);
|
||||
(MDecl.ResolvedReturnType.Kind in [tyRecord, tyStaticArray]);
|
||||
end
|
||||
else if AExpr is TMethodCallExpr then
|
||||
begin
|
||||
if TMethodCallExpr(AExpr).ResolvedMethod = nil then Exit;
|
||||
MDecl := TMethodDecl(TMethodCallExpr(AExpr).ResolvedMethod);
|
||||
Result := (MDecl.ResolvedReturnType <> nil) and
|
||||
(MDecl.ResolvedReturnType.Kind = tyRecord);
|
||||
(MDecl.ResolvedReturnType.Kind in [tyRecord, tyStaticArray]);
|
||||
end
|
||||
else if AExpr is TFieldAccessExpr then
|
||||
begin
|
||||
|
|
@ -4873,7 +4874,7 @@ begin
|
|||
if FldA.ResolvedMethod = nil then Exit;
|
||||
MDecl := TMethodDecl(FldA.ResolvedMethod);
|
||||
Result := (MDecl.ResolvedReturnType <> nil) and
|
||||
(MDecl.ResolvedReturnType.Kind = tyRecord);
|
||||
(MDecl.ResolvedReturnType.Kind in [tyRecord, tyStaticArray]);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -5309,7 +5310,8 @@ begin
|
|||
{ Jumbo set returns always use the plain sret path (a hidden dest pointer +
|
||||
void return); they share this call helper with records but must not go
|
||||
through ClassifyRecordReturn (which reads record-specific size fields). }
|
||||
if (ARetType <> nil) and (TTypeDesc(ARetType).Kind = tySet) then
|
||||
if (ARetType <> nil) and
|
||||
(TTypeDesc(ARetType).Kind in [tySet, tyStaticArray]) then
|
||||
begin
|
||||
if AVisibleArgs <> '' then
|
||||
ArgLine := Format('l %s, %s', [ADestAddr, AVisibleArgs])
|
||||
|
|
@ -7016,7 +7018,7 @@ begin
|
|||
RC := Self.ClassifyRecordReturn(TRecordTypeDesc(AMethod.ResolvedReturnType));
|
||||
|
||||
Sig := 'l %_par_Self';
|
||||
if IsFunc and ((AMethod.ResolvedReturnType.Kind = tyInterface) or
|
||||
if IsFunc and ((AMethod.ResolvedReturnType.Kind in [tyInterface, tyStaticArray]) or
|
||||
((AMethod.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(AMethod.ResolvedReturnType).IsJumbo())) then
|
||||
Sig := 'l %_par__sret, l %_par_Self'
|
||||
|
|
@ -7046,10 +7048,9 @@ begin
|
|||
if IsFunc then
|
||||
begin
|
||||
RetQType := QbeTypeOf(AMethod.ResolvedReturnType);
|
||||
if (AMethod.ResolvedReturnType.Kind = tyInterface) or
|
||||
if (AMethod.ResolvedReturnType.Kind in [tyInterface, tyStaticArray]) or
|
||||
((AMethod.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(AMethod.ResolvedReturnType).IsJumbo()) then
|
||||
{ sret aggregate return — void function. }
|
||||
EmitLine(Format('%sfunction %s(%s) {', [ExportPrefix(), FuncName, Sig]))
|
||||
else if AMethod.ResolvedReturnType.Kind = tyRecord then
|
||||
begin
|
||||
|
|
@ -7116,10 +7117,15 @@ begin
|
|||
begin
|
||||
if AMethod.ResolvedReturnType.Kind = tyRecord then
|
||||
EmitRecordReturnPrologue(TRecordTypeDesc(AMethod.ResolvedReturnType), RC)
|
||||
else if AMethod.ResolvedReturnType.Kind = tyStaticArray then
|
||||
begin
|
||||
EmitLine(' %_var_Result =l copy %_par__sret');
|
||||
EmitLine(Format(' call $memset(l %%_var_Result, w 0, l %d)',
|
||||
[AMethod.ResolvedReturnType.ByteSize()]));
|
||||
end
|
||||
else if (AMethod.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(AMethod.ResolvedReturnType).IsJumbo() then
|
||||
begin
|
||||
{ sret: jumbo-set Result aliases the caller's buffer; zero-init it. }
|
||||
EmitLine(' %_var_Result =l copy %_par__sret');
|
||||
EmitLine(Format(' call $memset(l %%_var_Result, w 0, l %d)',
|
||||
[TSetTypeDesc(AMethod.ResolvedReturnType).RawSize()]));
|
||||
|
|
@ -7200,10 +7206,9 @@ begin
|
|||
begin
|
||||
if AMethod.ResolvedReturnType.Kind = tyRecord then
|
||||
EmitRecordReturnEpilogue(TRecordTypeDesc(AMethod.ResolvedReturnType), RC)
|
||||
else if (AMethod.ResolvedReturnType.Kind = tyInterface) or
|
||||
else if (AMethod.ResolvedReturnType.Kind in [tyInterface, tyStaticArray]) or
|
||||
((AMethod.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(AMethod.ResolvedReturnType).IsJumbo()) then
|
||||
{ sret aggregate return — written through %_par__sret; return void. }
|
||||
EmitLine(' ret')
|
||||
else
|
||||
begin
|
||||
|
|
@ -7965,12 +7970,12 @@ begin
|
|||
if IsFunc then
|
||||
begin
|
||||
RetQType := QbeTypeOf(ADecl.ResolvedReturnType);
|
||||
if (ADecl.ResolvedReturnType.Kind = tyInterface) or
|
||||
if (ADecl.ResolvedReturnType.Kind in [tyInterface, tyStaticArray]) or
|
||||
((ADecl.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(ADecl.ResolvedReturnType).IsJumbo()) then
|
||||
begin
|
||||
{ Interface and jumbo-set returns use a hidden sret pointer (the callee
|
||||
writes the aggregate through it) and return void. }
|
||||
{ Interface, static-array, and jumbo-set returns use a hidden sret
|
||||
pointer (the callee writes the aggregate through it) and return void. }
|
||||
if Sig <> '' then Sig := 'l %_par__sret, ' + Sig
|
||||
else Sig := 'l %_par__sret';
|
||||
EmitLine(Format('%sfunction %s(%s) {', [Prefix, FuncName, Sig]));
|
||||
|
|
@ -8124,6 +8129,12 @@ begin
|
|||
begin
|
||||
if ADecl.ResolvedReturnType.Kind = tyRecord then
|
||||
EmitRecordReturnPrologue(TRecordTypeDesc(ADecl.ResolvedReturnType), RC)
|
||||
else if ADecl.ResolvedReturnType.Kind = tyStaticArray then
|
||||
begin
|
||||
EmitLine(' %_var_Result =l copy %_par__sret');
|
||||
EmitLine(Format(' call $memset(l %%_var_Result, w 0, l %d)',
|
||||
[ADecl.ResolvedReturnType.ByteSize()]));
|
||||
end
|
||||
else if (ADecl.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(ADecl.ResolvedReturnType).IsJumbo() then
|
||||
begin
|
||||
|
|
@ -8216,10 +8227,9 @@ begin
|
|||
begin
|
||||
if ADecl.ResolvedReturnType.Kind = tyRecord then
|
||||
EmitRecordReturnEpilogue(TRecordTypeDesc(ADecl.ResolvedReturnType), RC)
|
||||
else if (ADecl.ResolvedReturnType.Kind = tyInterface) or
|
||||
else if (ADecl.ResolvedReturnType.Kind in [tyInterface, tyStaticArray]) or
|
||||
((ADecl.ResolvedReturnType.Kind = tySet) and
|
||||
TSetTypeDesc(ADecl.ResolvedReturnType).IsJumbo()) then
|
||||
{ sret: the aggregate was written through %_par__sret — return void. }
|
||||
EmitLine(' ret')
|
||||
else
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ type
|
|||
{ Named-type alias array const (GitHub #113) }
|
||||
procedure TestRun_NamedArrayAlias_IntConst;
|
||||
|
||||
{ Static array return by value (GitHub #112) }
|
||||
procedure TestRun_StaticArrayReturn_12Bytes;
|
||||
procedure TestRun_StaticArrayReturn_16Bytes;
|
||||
|
||||
{ Multi-arg WriteLn }
|
||||
procedure TestRun_WriteLn_MultipleArgs_MixedTypes;
|
||||
end;
|
||||
|
|
@ -612,6 +616,52 @@ end;
|
|||
|
||||
{ ---- Multi-arg WriteLn ---- }
|
||||
|
||||
procedure TE2EGapTests.TestRun_StaticArrayReturn_12Bytes;
|
||||
const
|
||||
Src =
|
||||
'''
|
||||
program P;
|
||||
type TVec3 = array[0..2] of Integer;
|
||||
function MakeVec(A, B, C: Integer): TVec3;
|
||||
begin
|
||||
Result[0] := A;
|
||||
Result[1] := B;
|
||||
Result[2] := C
|
||||
end;
|
||||
var V: TVec3;
|
||||
begin
|
||||
V := MakeVec(10, 20, 30);
|
||||
WriteLn(V[0]);
|
||||
WriteLn(V[1]);
|
||||
WriteLn(V[2])
|
||||
end.
|
||||
''';
|
||||
begin
|
||||
AssertRunsOnAll(Src, '10' + Chr(10) + '20' + Chr(10) + '30' + Chr(10), 0);
|
||||
end;
|
||||
|
||||
procedure TE2EGapTests.TestRun_StaticArrayReturn_16Bytes;
|
||||
const
|
||||
Src =
|
||||
'''
|
||||
program P;
|
||||
type TPair = array[0..1] of Int64;
|
||||
function MakePair(A, B: Int64): TPair;
|
||||
begin
|
||||
Result[0] := A;
|
||||
Result[1] := B
|
||||
end;
|
||||
var P2: TPair;
|
||||
begin
|
||||
P2 := MakePair(111, 222);
|
||||
WriteLn(P2[0]);
|
||||
WriteLn(P2[1])
|
||||
end.
|
||||
''';
|
||||
begin
|
||||
AssertRunsOnAll(Src, '111' + Chr(10) + '222' + Chr(10), 0);
|
||||
end;
|
||||
|
||||
procedure TE2EGapTests.TestRun_WriteLn_MultipleArgs_MixedTypes;
|
||||
const Src = '''
|
||||
program T;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ type
|
|||
procedure TestCodegen_MultiDim_CommaWrite_EmitsRowOffset;
|
||||
procedure TestCodegen_MultiDim_ChainedWrite_SameAsComma;
|
||||
procedure TestCodegen_MultiDim_CommaRead_NestedSubscript;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Function returning static array by value (issue #112) }
|
||||
{ ------------------------------------------------------------------ }
|
||||
procedure TestCodegen_ReturnStaticArray_SretParam;
|
||||
procedure TestCodegen_ReturnStaticArray_VoidReturn;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -847,6 +853,37 @@ begin
|
|||
AssertTrue('emits a word load for the element', Pos('loadw', IR) >= 0);
|
||||
end;
|
||||
|
||||
const
|
||||
SrcArrayReturn =
|
||||
'''
|
||||
program P;
|
||||
type TVec3 = array[0..2] of Integer;
|
||||
function MakeVec(A, B, C: Integer): TVec3;
|
||||
begin
|
||||
Result[0] := A;
|
||||
Result[1] := B;
|
||||
Result[2] := C
|
||||
end;
|
||||
var V: TVec3;
|
||||
begin
|
||||
V := MakeVec(1, 2, 3)
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TStaticArrayTests.TestCodegen_ReturnStaticArray_SretParam;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcArrayReturn);
|
||||
AssertTrue('sret hidden param', Pos('_par__sret', IR) >= 0);
|
||||
end;
|
||||
|
||||
procedure TStaticArrayTests.TestCodegen_ReturnStaticArray_VoidReturn;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcArrayReturn);
|
||||
AssertTrue('void return', Pos('ret', IR) >= 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TStaticArrayTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue