Constructor calls with arguments: TMethodCallExpr.IsConstructorCall flag; AnalyseMethodCallExpr handles TypeName.Create(args) → allocate via _ClassAlloc then call user-defined Create method; EmitCaseStmt generates dispatch, EmitExpr handles the new constructor path. const block: TConstDecl AST node; TBlock.ConstDecls list; ParseConstBlock; AnalyseConstDecls registers each constant as skConstant in the symbol table. tests/blaise-compiler.pas: initial self-hosting source stub — program header, CHR_* character constants, Exception hierarchy, and Classes.pas RTL content. The file is built incrementally; this commit has sections 1-2. 834 tests pass.
2842 lines
90 KiB
ObjectPascal
2842 lines
90 KiB
ObjectPascal
{
|
|
Blaise - An Object Pascal Compiler
|
|
Copyright (c) 2026 Graeme Geldenhuys
|
|
SPDX-License-Identifier: BSD-3-Clause
|
|
See LICENSE file in the project root for full license terms.
|
|
}
|
|
|
|
unit uCodeGenQBE;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{ QBE IR emitter for Blaise.
|
|
String layout: Phase 1 uses raw NUL-terminated bytes (no ARC header).
|
|
WriteLn/Write are built-ins resolved directly to libc printf calls.
|
|
Records are stack-allocated; field access uses pointer arithmetic. }
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, StrUtils, Classes, uAST, uSymbolTable;
|
|
|
|
type
|
|
ECodeGenError = class(Exception);
|
|
|
|
TCodeGenQBE = class
|
|
private
|
|
FOutput: TStringList;
|
|
FStrLits: TStringList; { index → raw value; label = $__s<index> }
|
|
FTempCount: Integer;
|
|
FLabelCount: Integer;
|
|
FCurrentBlock: TBlock; { block currently being emitted; set by EmitBlock }
|
|
FBreakLabels: TStringList; { stack of active loop-end labels; top = innermost }
|
|
FExitLabel: string; { label to jmp to for 'exit'; '' = main program }
|
|
|
|
function AllocTemp: string;
|
|
function AllocLabel(const APrefix: string): string;
|
|
function EmitStrLit(const AValue: string): string;
|
|
procedure EmitLine(const ALine: string);
|
|
procedure EmitDataSection;
|
|
procedure EmitMainHeader;
|
|
procedure EmitMainFooter;
|
|
procedure EmitTypeInfoDefs(AProg: TProgram);
|
|
procedure EmitVTableDefs(AProg: TProgram);
|
|
procedure EmitMethodDefs(AProg: TProgram);
|
|
procedure EmitInterfaceDefs(AProg: TProgram);
|
|
procedure EmitFieldCleanupDefs(AProg: TProgram);
|
|
procedure EmitFieldCleanupFn(const AMangledName: string;
|
|
ARec: TRecordTypeDesc);
|
|
procedure EmitMethodDef(const ATypeName: string; AMethod: TMethodDecl);
|
|
procedure EmitStandaloneDefs(AProg: TProgram);
|
|
procedure EmitStandaloneDef(ADecl: TMethodDecl);
|
|
procedure EmitFuncDef(ADecl: TMethodDecl; AExported: Boolean);
|
|
procedure EmitBlock(ABlock: TBlock);
|
|
procedure EmitVarAllocs(ABlock: TBlock);
|
|
procedure EmitParamAllocs(AMethod: TMethodDecl; AClassType: TRecordTypeDesc);
|
|
procedure EmitArcCleanup(ABlock: TBlock);
|
|
procedure EmitExcPathArcCleanup(ABlock: TBlock);
|
|
procedure EmitStmt(AStmt: TASTStmt);
|
|
procedure EmitIfStmt(AStmt: TIfStmt);
|
|
procedure EmitWhileStmt(AStmt: TWhileStmt);
|
|
procedure EmitForStmt(AStmt: TForStmt);
|
|
procedure EmitTryFinallyStmt(AStmt: TTryFinallyStmt);
|
|
procedure EmitTryExceptStmt(AStmt: TTryExceptStmt);
|
|
procedure EmitRaiseStmt(AStmt: TRaiseStmt);
|
|
procedure EmitCompoundStmt(AStmt: TCompoundStmt);
|
|
procedure EmitAssignment(AAssign: TAssignment);
|
|
procedure EmitFieldAssignment(AAssign: TFieldAssignment);
|
|
procedure EmitMethodCall(ACall: TMethodCallStmt);
|
|
procedure EmitInheritedCall(ACall: TInheritedCallStmt);
|
|
procedure EmitCaseStmt(AStmt: TCaseStmt);
|
|
procedure EmitProcCall(ACall: TProcCall);
|
|
procedure EmitPointerWrite(AStmt: TPointerWriteStmt);
|
|
procedure EmitWrite(ACall: TProcCall; ANewline: Boolean);
|
|
function EmitExpr(AExpr: TASTExpr): string;
|
|
function EmitIsExpr(AExpr: TIsExpr): string;
|
|
function EmitAsExpr(AExpr: TAsExpr): string;
|
|
{ Returns a QBE temp holding a pointer to the storage of a record or class
|
|
instance referenced by AExpr. Used by chained field access to traverse
|
|
base nodes without loading record aggregates as scalars. }
|
|
function EmitInstancePtr(AExpr: TASTExpr): string;
|
|
function FieldPtr(const ARecordVar: string; AOffset: Integer): string;
|
|
function QbeTypeOf(AType: TTypeDesc): string;
|
|
function QbeEscapeString(const AStr: string): string;
|
|
{ Mangle a type name for use in QBE symbols: '<' → '_', '>' → '', ',' → '_' }
|
|
function QBEMangle(const AName: string): string;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Generate(AProg: TProgram);
|
|
procedure GenerateUnit(AUnit: TUnit);
|
|
function GetOutput: string;
|
|
end;
|
|
|
|
implementation
|
|
|
|
constructor TCodeGenQBE.Create;
|
|
begin
|
|
inherited Create;
|
|
FOutput := TStringList.Create;
|
|
FStrLits := TStringList.Create;
|
|
FBreakLabels := TStringList.Create;
|
|
FTempCount := 0;
|
|
end;
|
|
|
|
destructor TCodeGenQBE.Destroy;
|
|
begin
|
|
FBreakLabels.Free;
|
|
FOutput.Free;
|
|
FStrLits.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCodeGenQBE.AllocTemp: string;
|
|
begin
|
|
Result := Format('%%_t%d', [FTempCount]);
|
|
Inc(FTempCount);
|
|
end;
|
|
|
|
function TCodeGenQBE.AllocLabel(const APrefix: string): string;
|
|
begin
|
|
Result := Format('%s_%d', [APrefix, FLabelCount]);
|
|
Inc(FLabelCount);
|
|
end;
|
|
|
|
function TCodeGenQBE.EmitStrLit(const AValue: string): string;
|
|
var
|
|
Idx: Integer;
|
|
begin
|
|
Idx := FStrLits.IndexOf(AValue);
|
|
if Idx < 0 then
|
|
Idx := FStrLits.Add(AValue);
|
|
Result := Format('$__s%d', [Idx]);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitLine(const ALine: string);
|
|
begin
|
|
FOutput.Add(ALine);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitDataSection;
|
|
var
|
|
I: Integer;
|
|
StrLen: Integer;
|
|
begin
|
|
{ Each literal has a 12-byte ARC header: refcnt=-1 (immortal), length, capacity.
|
|
The string pointer IS the header pointer; char data begins at ptr+12. }
|
|
if FStrLits.Count > 0 then
|
|
begin
|
|
EmitLine('# String literals');
|
|
for I := 0 to FStrLits.Count - 1 do
|
|
begin
|
|
StrLen := Length(FStrLits[I]);
|
|
EmitLine(Format('data $__s%d = { w -1, w %d, w %d, b "%s", b 0 }',
|
|
[I, StrLen, StrLen, QbeEscapeString(FStrLits[I])]));
|
|
end;
|
|
end;
|
|
{ printf format strings. Always emitted: a program with no string literals
|
|
can still call WriteLn(Integer) or a bare WriteLn, and those reference
|
|
$__fmt_d_nl / $__fmt_nl unconditionally. Omitting these definitions
|
|
produces a link-time "undefined reference" failure that the IR-only test
|
|
harness cannot see. }
|
|
EmitLine('data $__fmt_s_nl = { b "%s\n", b 0 }');
|
|
EmitLine('data $__fmt_s = { b "%s", b 0 }');
|
|
EmitLine('data $__fmt_d_nl = { b "%d\n", b 0 }');
|
|
EmitLine('data $__fmt_d = { b "%d", b 0 }');
|
|
EmitLine('data $__fmt_nl = { b "\n", b 0 }');
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitMainHeader;
|
|
begin
|
|
EmitLine('export function w $main(w %argc, l %argv) {');
|
|
EmitLine('@start');
|
|
EmitLine(' call $_SetArgs(w %argc, l %argv)');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitMainFooter;
|
|
begin
|
|
EmitLine(' ret 0');
|
|
EmitLine('}');
|
|
end;
|
|
|
|
function TCodeGenQBE.QbeTypeOf(AType: TTypeDesc): string;
|
|
begin
|
|
case AType.Kind of
|
|
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum: Result := 'w';
|
|
tyInt64, tyString: Result := 'l';
|
|
tyRecord: Result := 'l'; { pointer to aggregate }
|
|
tyClass: Result := 'l'; { heap pointer }
|
|
tyPointer: Result := 'l'; { pointer (typed or untyped) }
|
|
else
|
|
Result := 'w';
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitVarAllocs(ABlock: TBlock);
|
|
var
|
|
I, J: Integer;
|
|
Decl: TVarDecl;
|
|
VarName: string;
|
|
RT: TRecordTypeDesc;
|
|
RecSize: Integer;
|
|
RecAlign: Integer;
|
|
begin
|
|
for I := 0 to ABlock.Decls.Count - 1 do
|
|
begin
|
|
Decl := TVarDecl(ABlock.Decls[I]);
|
|
if Decl.ResolvedType = nil then
|
|
raise ECodeGenError.CreateFmt(
|
|
'Variable ''%s'' has no resolved type — semantic pass required',
|
|
[Decl.Names[0]]);
|
|
|
|
for J := 0 to Decl.Names.Count - 1 do
|
|
begin
|
|
VarName := Decl.Names[J];
|
|
case Decl.ResolvedType.Kind of
|
|
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc4 1', [VarName]));
|
|
EmitLine(Format(' storew 0, %%_var_%s', [VarName]));
|
|
end;
|
|
|
|
tyInt64:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
|
|
end;
|
|
|
|
tyString:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
|
|
end;
|
|
|
|
tyRecord:
|
|
begin
|
|
RT := TRecordTypeDesc(Decl.ResolvedType);
|
|
RecSize := RT.TotalSize;
|
|
RecAlign := RT.MaxAlign;
|
|
if RecAlign >= 8 then
|
|
EmitLine(Format(' %%_var_%s =l alloc8 %d', [VarName, RecSize]))
|
|
else
|
|
EmitLine(Format(' %%_var_%s =l alloc4 %d', [VarName, RecSize]));
|
|
{ Zero-initialise record storage: Pascal records default to 0 and
|
|
QBE's SSA checker requires every loaded slot to have at least
|
|
one prior store. }
|
|
if RecSize > 0 then
|
|
EmitLine(Format(' call $memset(l %%_var_%s, w 0, l %d)',
|
|
[VarName, RecSize]));
|
|
end;
|
|
|
|
tyClass:
|
|
begin
|
|
{ Class var holds a heap pointer — allocate one pointer slot, nil-init }
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
|
|
end;
|
|
|
|
tyPointer:
|
|
begin
|
|
{ Pointer var (typed or untyped) — allocate one pointer slot, nil-init }
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
|
|
end;
|
|
|
|
tyInterface:
|
|
begin
|
|
{ Interface var = fat pointer: obj slot + itab slot, both nil-init }
|
|
EmitLine(Format(' %%_var_%s_obj =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s_obj', [VarName]));
|
|
EmitLine(Format(' %%_var_%s_itab =l alloc8 1', [VarName]));
|
|
EmitLine(Format(' storel 0, %%_var_%s_itab', [VarName]));
|
|
end;
|
|
|
|
else
|
|
raise ECodeGenError.CreateFmt(
|
|
'Unsupported type kind %d for variable ''%s''',
|
|
[Ord(Decl.ResolvedType.Kind), VarName]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitArcCleanup(ABlock: TBlock);
|
|
{ Release every ARC-managed local variable (string, class, or interface) at
|
|
block exit. Mirrors the insertion pattern used at assignment sites: each
|
|
slot holds one retained reference from its first assignment; scope exit
|
|
must balance that with one release. Interface vars carry a fat pointer
|
|
(obj + itab); only the obj slot is refcounted. Weak vars use _WeakClear
|
|
against the slot address rather than a strong release on the slot value. }
|
|
var
|
|
I, J: Integer;
|
|
Decl: TVarDecl;
|
|
VarName: string;
|
|
ValTemp: string;
|
|
RelFn: string;
|
|
IsIntf: Boolean;
|
|
begin
|
|
for I := 0 to ABlock.Decls.Count - 1 do
|
|
begin
|
|
Decl := TVarDecl(ABlock.Decls[I]);
|
|
if Decl.ResolvedType = nil then Continue;
|
|
IsIntf := Decl.ResolvedType.Kind = tyInterface;
|
|
if Decl.IsWeak then
|
|
begin
|
|
{ Weak class or interface local — unregister from the weak table
|
|
without touching refcounts. The zero-out happens automatically
|
|
as _WeakClear writes 0 to *slot. }
|
|
for J := 0 to Decl.Names.Count - 1 do
|
|
begin
|
|
VarName := Decl.Names[J];
|
|
if IsIntf then
|
|
EmitLine(Format(' call $_WeakClear(l %%_var_%s_obj)', [VarName]))
|
|
else
|
|
EmitLine(Format(' call $_WeakClear(l %%_var_%s)', [VarName]));
|
|
end;
|
|
Continue;
|
|
end;
|
|
if Decl.ResolvedType.IsString then
|
|
RelFn := '$_StringRelease'
|
|
else if Decl.ResolvedType.Kind = tyClass then
|
|
RelFn := '$_ClassRelease'
|
|
else if IsIntf then
|
|
RelFn := '$_ClassRelease' { obj slot release; itab is static }
|
|
else
|
|
Continue;
|
|
for J := 0 to Decl.Names.Count - 1 do
|
|
begin
|
|
VarName := Decl.Names[J];
|
|
ValTemp := AllocTemp;
|
|
if IsIntf then
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [ValTemp, VarName]))
|
|
else
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, VarName]));
|
|
EmitLine(Format(' call %s(l %s)', [RelFn, ValTemp]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitBlock(ABlock: TBlock);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
FCurrentBlock := ABlock;
|
|
EmitVarAllocs(ABlock);
|
|
for I := 0 to ABlock.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(ABlock.Stmts[I]));
|
|
{ Fall-through to exit label so 'exit' and normal flow share cleanup. }
|
|
if FExitLabel <> '' then
|
|
begin
|
|
EmitLine(Format(' jmp @%s', [FExitLabel]));
|
|
EmitLine('@' + FExitLabel);
|
|
end;
|
|
EmitArcCleanup(ABlock);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitExcPathArcCleanup(ABlock: TBlock);
|
|
var
|
|
I, J: Integer;
|
|
Decl: TVarDecl;
|
|
VarName: string;
|
|
ValTemp: string;
|
|
RelFn: string;
|
|
IsIntf: Boolean;
|
|
begin
|
|
if ABlock = nil then Exit;
|
|
for I := 0 to ABlock.Decls.Count - 1 do
|
|
begin
|
|
Decl := TVarDecl(ABlock.Decls[I]);
|
|
if Decl.ResolvedType = nil then Continue;
|
|
IsIntf := Decl.ResolvedType.Kind = tyInterface;
|
|
if Decl.IsWeak then
|
|
begin
|
|
{ Weak locals on an exception path: unregister and zero the slot
|
|
so a subsequent nested handler's cleanup sees nil. }
|
|
for J := 0 to Decl.Names.Count - 1 do
|
|
begin
|
|
VarName := Decl.Names[J];
|
|
if IsIntf then
|
|
EmitLine(Format(' call $_WeakClear(l %%_var_%s_obj)', [VarName]))
|
|
else
|
|
EmitLine(Format(' call $_WeakClear(l %%_var_%s)', [VarName]));
|
|
end;
|
|
Continue;
|
|
end;
|
|
if Decl.ResolvedType.IsString then
|
|
RelFn := '$_StringRelease'
|
|
else if Decl.ResolvedType.Kind = tyClass then
|
|
RelFn := '$_ClassRelease'
|
|
else if IsIntf then
|
|
RelFn := '$_ClassRelease'
|
|
else
|
|
Continue;
|
|
for J := 0 to Decl.Names.Count - 1 do
|
|
begin
|
|
VarName := Decl.Names[J];
|
|
ValTemp := AllocTemp;
|
|
if IsIntf then
|
|
begin
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [ValTemp, VarName]));
|
|
EmitLine(Format(' call %s(l %s)', [RelFn, ValTemp]));
|
|
{ Zero only the obj slot; the itab slot holds a static pointer
|
|
and nilling it would break a subsequent method call on this
|
|
variable if it survives the unwind. }
|
|
EmitLine(Format(' storel 0, %%_var_%s_obj', [VarName]));
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, VarName]));
|
|
EmitLine(Format(' call %s(l %s)', [RelFn, ValTemp]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [VarName]));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitStmt(AStmt: TASTStmt);
|
|
var
|
|
DeadLbl: string;
|
|
begin
|
|
if AStmt is TTryFinallyStmt then
|
|
EmitTryFinallyStmt(TTryFinallyStmt(AStmt))
|
|
else if AStmt is TTryExceptStmt then
|
|
EmitTryExceptStmt(TTryExceptStmt(AStmt))
|
|
else if AStmt is TRaiseStmt then
|
|
EmitRaiseStmt(TRaiseStmt(AStmt))
|
|
else if AStmt is TForStmt then
|
|
EmitForStmt(TForStmt(AStmt))
|
|
else if AStmt is TWhileStmt then
|
|
EmitWhileStmt(TWhileStmt(AStmt))
|
|
else if AStmt is TIfStmt then
|
|
EmitIfStmt(TIfStmt(AStmt))
|
|
else if AStmt is TCompoundStmt then
|
|
EmitCompoundStmt(TCompoundStmt(AStmt))
|
|
else if AStmt is TFieldAssignment then
|
|
EmitFieldAssignment(TFieldAssignment(AStmt))
|
|
else if AStmt is TPointerWriteStmt then
|
|
EmitPointerWrite(TPointerWriteStmt(AStmt))
|
|
else if AStmt is TAssignment then
|
|
EmitAssignment(TAssignment(AStmt))
|
|
else if AStmt is TMethodCallStmt then
|
|
EmitMethodCall(TMethodCallStmt(AStmt))
|
|
else if AStmt is TInheritedCallStmt then
|
|
EmitInheritedCall(TInheritedCallStmt(AStmt))
|
|
else if AStmt is TCaseStmt then
|
|
EmitCaseStmt(TCaseStmt(AStmt))
|
|
else if AStmt is TProcCall then
|
|
EmitProcCall(TProcCall(AStmt))
|
|
else if AStmt is TExitStmt then
|
|
begin
|
|
if FExitLabel <> '' then
|
|
EmitLine(Format(' jmp @%s', [FExitLabel]))
|
|
else
|
|
EmitLine(' ret 0');
|
|
{ QBE basic blocks must follow a terminator with a new labelled block. }
|
|
DeadLbl := AllocLabel('after_exit');
|
|
EmitLine('@' + DeadLbl);
|
|
end
|
|
else if AStmt is TBreakStmt then
|
|
begin
|
|
if FBreakLabels.Count = 0 then
|
|
raise ECodeGenError.Create('break outside loop');
|
|
EmitLine(Format(' jmp @%s',
|
|
[FBreakLabels[FBreakLabels.Count - 1]]));
|
|
DeadLbl := AllocLabel('after_break');
|
|
EmitLine('@' + DeadLbl);
|
|
end
|
|
else
|
|
raise ECodeGenError.Create('Unknown statement node type');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitIfStmt(AStmt: TIfStmt);
|
|
var
|
|
CondTemp: string;
|
|
LblThen: string;
|
|
LblElse: string;
|
|
LblEnd: string;
|
|
begin
|
|
LblThen := AllocLabel('if_then');
|
|
LblEnd := AllocLabel('if_end');
|
|
|
|
CondTemp := EmitExpr(AStmt.Condition);
|
|
|
|
if AStmt.ElseStmt <> nil then
|
|
begin
|
|
LblElse := AllocLabel('if_else');
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CondTemp, LblThen, LblElse]));
|
|
EmitLine('@' + LblThen);
|
|
EmitStmt(AStmt.ThenStmt);
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
EmitLine('@' + LblElse);
|
|
EmitStmt(AStmt.ElseStmt);
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CondTemp, LblThen, LblEnd]));
|
|
EmitLine('@' + LblThen);
|
|
EmitStmt(AStmt.ThenStmt);
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
end;
|
|
|
|
EmitLine('@' + LblEnd);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitTryFinallyStmt(AStmt: TTryFinallyStmt);
|
|
var
|
|
LblTry: string;
|
|
LblFinExc: string;
|
|
LblEnd: string;
|
|
FrameTemp: string;
|
|
SjrTemp: string;
|
|
ExcTemp: string;
|
|
I: Integer;
|
|
begin
|
|
LblTry := AllocLabel('try_body');
|
|
LblFinExc := AllocLabel('fin_exc');
|
|
LblEnd := AllocLabel('fin_end');
|
|
|
|
{ Stack-allocate exception frame (512 bytes, 16-byte aligned).
|
|
jbuf is at offset 0 so frame ptr can be passed directly to setjmp.
|
|
512 bytes accommodates jmp_buf on Linux x86_64 (200 B) and macOS ARM64 (~312 B). }
|
|
FrameTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l alloc16 512', [FrameTemp]));
|
|
EmitLine(Format(' call $_PushExcFrame(l %s)', [FrameTemp]));
|
|
|
|
SjrTemp := AllocTemp;
|
|
EmitLine(Format(' %s =w call $setjmp(l %s)', [SjrTemp, FrameTemp]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [SjrTemp, LblFinExc, LblTry]));
|
|
|
|
{ Normal path: run try body, pop frame, run finally body }
|
|
EmitLine('@' + LblTry);
|
|
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
|
|
EmitLine(' call $_PopExcFrame()');
|
|
for I := 0 to AStmt.FinallyBody.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.FinallyBody.Stmts[I]));
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
|
|
{ Exception path: capture exception, pop frame, run finally body, release
|
|
in-scope ARC vars (prevent leaks on unwind), then re-raise }
|
|
EmitLine('@' + LblFinExc);
|
|
ExcTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_CurrentException()', [ExcTemp]));
|
|
EmitLine(' call $_PopExcFrame()');
|
|
for I := 0 to AStmt.FinallyBody.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.FinallyBody.Stmts[I]));
|
|
EmitExcPathArcCleanup(FCurrentBlock);
|
|
EmitLine(Format(' call $_Reraise(l %s)', [ExcTemp]));
|
|
EmitLine(Format(' jmp @%s', [LblEnd])); { unreachable — satisfies QBE block exit }
|
|
|
|
EmitLine('@' + LblEnd);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitTryExceptStmt(AStmt: TTryExceptStmt);
|
|
var
|
|
LblTry: string;
|
|
LblExcept: string;
|
|
LblEnd: string;
|
|
FrameTemp: string;
|
|
SjrTemp: string;
|
|
I: Integer;
|
|
begin
|
|
LblTry := AllocLabel('try_body');
|
|
LblExcept := AllocLabel('except_handler');
|
|
LblEnd := AllocLabel('except_end');
|
|
|
|
{ Stack-allocate exception frame (512 bytes, 16-byte aligned).
|
|
Matches the size contract in blaise_exc.c — must hold jmp_buf (200 B on
|
|
Linux x86_64, ~312 B on macOS ARM64) plus two pointer fields. }
|
|
FrameTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l alloc16 512', [FrameTemp]));
|
|
EmitLine(Format(' call $_PushExcFrame(l %s)', [FrameTemp]));
|
|
|
|
SjrTemp := AllocTemp;
|
|
EmitLine(Format(' %s =w call $setjmp(l %s)', [SjrTemp, FrameTemp]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [SjrTemp, LblExcept, LblTry]));
|
|
|
|
{ Normal path: run try body, pop frame on clean exit }
|
|
EmitLine('@' + LblTry);
|
|
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
|
|
EmitLine(' call $_PopExcFrame()');
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
|
|
{ Exception path: frame still at top (exception set), pop then handle }
|
|
EmitLine('@' + LblExcept);
|
|
EmitLine(' call $_PopExcFrame()');
|
|
for I := 0 to AStmt.ExceptBody.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.ExceptBody.Stmts[I]));
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
|
|
EmitLine('@' + LblEnd);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitRaiseStmt(AStmt: TRaiseStmt);
|
|
var
|
|
ObjTemp: string;
|
|
begin
|
|
if AStmt.Expr <> nil then
|
|
begin
|
|
ObjTemp := EmitExpr(AStmt.Expr);
|
|
EmitLine(Format(' call $_Raise(l %s)', [ObjTemp]));
|
|
end
|
|
else
|
|
{ Bare re-raise: pass null; RTL retrieves current exception }
|
|
EmitLine(' call $_Raise(l 0)');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitForStmt(AStmt: TForStmt);
|
|
var
|
|
LblCond: string;
|
|
LblBody: string;
|
|
LblEnd: string;
|
|
StartT: string;
|
|
EndT: string;
|
|
CurT: string;
|
|
CmpT: string;
|
|
StepT: string;
|
|
CmpOp: string;
|
|
StepOp: string;
|
|
begin
|
|
LblCond := AllocLabel('for_cond');
|
|
LblBody := AllocLabel('for_body');
|
|
LblEnd := AllocLabel('for_end');
|
|
|
|
{ Evaluate start and store into loop variable }
|
|
StartT := EmitExpr(AStmt.StartExpr);
|
|
EmitLine(Format(' storew %s, %%_var_%s', [StartT, AStmt.VarName]));
|
|
|
|
{ Evaluate end value once into a temp }
|
|
EndT := EmitExpr(AStmt.EndExpr);
|
|
|
|
{ Jump to condition (terminates current block) }
|
|
EmitLine(Format(' jmp @%s', [LblCond]));
|
|
|
|
{ Condition block: test loop variable against end value }
|
|
EmitLine('@' + LblCond);
|
|
CurT := AllocTemp;
|
|
EmitLine(Format(' %s =w loadw %%_var_%s', [CurT, AStmt.VarName]));
|
|
CmpT := AllocTemp;
|
|
if AStmt.IsDownTo then
|
|
CmpOp := 'csgew' { I >= End }
|
|
else
|
|
CmpOp := 'cslew'; { I <= End }
|
|
EmitLine(Format(' %s =w %s %s, %s', [CmpT, CmpOp, CurT, EndT]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CmpT, LblBody, LblEnd]));
|
|
|
|
{ Body block }
|
|
EmitLine('@' + LblBody);
|
|
FBreakLabels.Add(LblEnd);
|
|
try
|
|
EmitStmt(AStmt.Body);
|
|
finally
|
|
FBreakLabels.Delete(FBreakLabels.Count - 1);
|
|
end;
|
|
|
|
{ Increment or decrement loop variable }
|
|
CurT := AllocTemp;
|
|
StepT := AllocTemp;
|
|
EmitLine(Format(' %s =w loadw %%_var_%s', [CurT, AStmt.VarName]));
|
|
if AStmt.IsDownTo then
|
|
StepOp := 'sub'
|
|
else
|
|
StepOp := 'add';
|
|
EmitLine(Format(' %s =w %s %s, 1', [StepT, StepOp, CurT]));
|
|
EmitLine(Format(' storew %s, %%_var_%s', [StepT, AStmt.VarName]));
|
|
EmitLine(Format(' jmp @%s', [LblCond]));
|
|
|
|
{ Continuation block }
|
|
EmitLine('@' + LblEnd);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitWhileStmt(AStmt: TWhileStmt);
|
|
var
|
|
LblCond: string;
|
|
LblBody: string;
|
|
LblEnd: string;
|
|
CondTemp: string;
|
|
begin
|
|
LblCond := AllocLabel('while_cond');
|
|
LblBody := AllocLabel('while_body');
|
|
LblEnd := AllocLabel('while_end');
|
|
|
|
{ Jump into the condition block (terminates current block) }
|
|
EmitLine(Format(' jmp @%s', [LblCond]));
|
|
|
|
{ Condition evaluation block }
|
|
EmitLine('@' + LblCond);
|
|
CondTemp := EmitExpr(AStmt.Condition);
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CondTemp, LblBody, LblEnd]));
|
|
|
|
{ Loop body block }
|
|
EmitLine('@' + LblBody);
|
|
FBreakLabels.Add(LblEnd);
|
|
try
|
|
EmitStmt(AStmt.Body);
|
|
finally
|
|
FBreakLabels.Delete(FBreakLabels.Count - 1);
|
|
end;
|
|
EmitLine(Format(' jmp @%s', [LblCond]));
|
|
|
|
{ Continuation block }
|
|
EmitLine('@' + LblEnd);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitCompoundStmt(AStmt: TCompoundStmt);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to AStmt.Stmts.Count - 1 do
|
|
EmitStmt(TASTStmt(AStmt.Stmts[I]));
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitAssignment(AAssign: TAssignment);
|
|
var
|
|
ValTemp, OldTemp, QType, StoreInstr, PtrTemp: string;
|
|
IntfDesc: TInterfaceTypeDesc;
|
|
ClassRT: TRecordTypeDesc;
|
|
ItabName: string;
|
|
AE: TAsExpr;
|
|
ObjTemp: string;
|
|
ItabTemp: string;
|
|
CheckTemp: string;
|
|
LblOk: string;
|
|
LblFail: string;
|
|
LblEnd: string;
|
|
begin
|
|
if AAssign.Expr.ResolvedType = nil then
|
|
raise ECodeGenError.CreateFmt(
|
|
'Expression in assignment to ''%s'' has no resolved type', [AAssign.Name]);
|
|
|
|
{ Interface as-cast: F := T as IFoo — use _GetItab for runtime itab lookup.
|
|
ARC: the obj slot holds a strong reference to the backing class instance,
|
|
so retain the new obj and release the prior contents of F's obj slot
|
|
before storing. The itab slot is a pointer to static rodata and is not
|
|
refcounted. }
|
|
if (AAssign.ResolvedLhsType <> nil) and
|
|
(AAssign.ResolvedLhsType.Kind = tyInterface) and
|
|
(AAssign.Expr is TAsExpr) and
|
|
(AAssign.Expr.ResolvedType.Kind = tyInterface) then
|
|
begin
|
|
AE := TAsExpr(AAssign.Expr);
|
|
IntfDesc := TInterfaceTypeDesc(AAssign.ResolvedLhsType);
|
|
ObjTemp := EmitExpr(AE.Obj);
|
|
ItabTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_GetItab(l %s, l $typeinfo_%s)',
|
|
[ItabTemp, ObjTemp, AE.TypeName]));
|
|
CheckTemp := AllocTemp;
|
|
LblOk := AllocLabel('as_ok');
|
|
LblFail := AllocLabel('as_fail');
|
|
LblEnd := AllocLabel('as_end');
|
|
EmitLine(Format(' %s =w cnel %s, 0', [CheckTemp, ItabTemp]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CheckTemp, LblOk, LblFail]));
|
|
EmitLine('@' + LblFail);
|
|
EmitLine(' call $_Raise_InvalidCast()');
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
EmitLine('@' + LblOk);
|
|
if AAssign.IsWeakLhs then
|
|
EmitLine(Format(' call $_WeakAssign(l %%_var_%s_obj, l %s)',
|
|
[AAssign.Name, ObjTemp]))
|
|
else
|
|
begin
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [OldTemp, AAssign.Name]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ObjTemp]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s_obj', [ObjTemp, AAssign.Name]));
|
|
end;
|
|
EmitLine(Format(' storel %s, %%_var_%s_itab', [ItabTemp, AAssign.Name]));
|
|
EmitLine('@' + LblEnd);
|
|
Exit;
|
|
end;
|
|
|
|
{ Interface direct assignment: F := T where T is a class implementing the
|
|
interface. Under ARC, the obj slot co-owns the backing class instance
|
|
and must be retained on store / released when overwritten — or, for
|
|
weak interface references, routed through _WeakAssign. }
|
|
if (AAssign.ResolvedLhsType <> nil) and
|
|
(AAssign.ResolvedLhsType.Kind = tyInterface) and
|
|
(AAssign.Expr.ResolvedType.Kind = tyClass) then
|
|
begin
|
|
IntfDesc := TInterfaceTypeDesc(AAssign.ResolvedLhsType);
|
|
ClassRT := TRecordTypeDesc(AAssign.Expr.ResolvedType);
|
|
ItabName := '$itab_' + ClassRT.Name + '_' + IntfDesc.Name;
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
if AAssign.IsWeakLhs then
|
|
EmitLine(Format(' call $_WeakAssign(l %%_var_%s_obj, l %s)',
|
|
[AAssign.Name, ValTemp]))
|
|
else
|
|
begin
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [OldTemp, AAssign.Name]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s_obj', [ValTemp, AAssign.Name]));
|
|
end;
|
|
EmitLine(Format(' storel %s, %%_var_%s_itab', [ItabName, AAssign.Name]));
|
|
Exit;
|
|
end;
|
|
|
|
{ Interface-to-interface direct assignment: F := G where both sides are
|
|
interface-typed. Copy obj and itab from G's fat pointer to F's; for
|
|
strong F, retain the backing object and release F's prior obj ref;
|
|
for weak F, route the obj through _WeakAssign. }
|
|
if (AAssign.ResolvedLhsType <> nil) and
|
|
(AAssign.ResolvedLhsType.Kind = tyInterface) and
|
|
(AAssign.Expr.ResolvedType.Kind = tyInterface) and
|
|
(AAssign.Expr is TIdentExpr) then
|
|
begin
|
|
ObjTemp := AllocTemp;
|
|
ItabTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj',
|
|
[ObjTemp, TIdentExpr(AAssign.Expr).Name]));
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_itab',
|
|
[ItabTemp, TIdentExpr(AAssign.Expr).Name]));
|
|
if AAssign.IsWeakLhs then
|
|
begin
|
|
EmitLine(Format(' call $_WeakAssign(l %%_var_%s_obj, l %s)',
|
|
[AAssign.Name, ObjTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s_itab', [ItabTemp, AAssign.Name]));
|
|
Exit;
|
|
end;
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [OldTemp, AAssign.Name]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ObjTemp]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s_obj', [ObjTemp, AAssign.Name]));
|
|
EmitLine(Format(' storel %s, %%_var_%s_itab', [ItabTemp, AAssign.Name]));
|
|
Exit;
|
|
end;
|
|
|
|
if AAssign.IsVarParam then
|
|
begin
|
|
{ Var param: load the stored pointer, then store the value through it }
|
|
QType := QbeTypeOf(AAssign.Expr.ResolvedType);
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
PtrTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [PtrTemp, AAssign.Name]));
|
|
if QType = 'w' then StoreInstr := 'storew'
|
|
else StoreInstr := 'storel';
|
|
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, PtrTemp]));
|
|
end
|
|
else if AAssign.Expr.ResolvedType.IsString then
|
|
begin
|
|
{ ARC: load old, compute new, retain new, release old, store new }
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [OldTemp, AAssign.Name]));
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_StringRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s', [ValTemp, AAssign.Name]));
|
|
end
|
|
else if AAssign.IsWeakLhs and (AAssign.Expr.ResolvedType.Kind = tyClass) then
|
|
begin
|
|
{ Weak class-typed assignment: bypass the strong refcount entirely.
|
|
_WeakAssign takes the slot *address* (so it can zero it later when
|
|
the target is released) and the new value; it handles unregistering
|
|
any prior registration for this slot. }
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
EmitLine(Format(' call $_WeakAssign(l %%_var_%s, l %s)',
|
|
[AAssign.Name, ValTemp]));
|
|
end
|
|
else if AAssign.Expr.ResolvedType.Kind = tyClass then
|
|
begin
|
|
{ ARC: load old class reference, evaluate new, retain new, release old,
|
|
store new. Matches the string ARC idiom one-for-one. }
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [OldTemp, AAssign.Name]));
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %%_var_%s', [ValTemp, AAssign.Name]));
|
|
end
|
|
else
|
|
begin
|
|
QType := QbeTypeOf(AAssign.Expr.ResolvedType);
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
if QType = 'w' then StoreInstr := 'storew'
|
|
else StoreInstr := 'storel';
|
|
EmitLine(Format(' %s %s, %%_var_%s', [StoreInstr, ValTemp, AAssign.Name]));
|
|
end;
|
|
end;
|
|
|
|
function TCodeGenQBE.EmitInstancePtr(AExpr: TASTExpr): string;
|
|
var
|
|
Id: TIdentExpr;
|
|
Fld: TFieldAccessExpr;
|
|
Base: string;
|
|
Ptr: string;
|
|
Loaded: string;
|
|
begin
|
|
if AExpr is TIdentExpr then
|
|
begin
|
|
Id := TIdentExpr(AExpr);
|
|
if (AExpr.ResolvedType <> nil) and (AExpr.ResolvedType.Kind = tyClass) then
|
|
begin
|
|
Loaded := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [Loaded, Id.Name]));
|
|
Result := Loaded;
|
|
end
|
|
else
|
|
Result := Format('%%_var_%s', [Id.Name]); { inline record }
|
|
Exit;
|
|
end;
|
|
|
|
if AExpr is TFieldAccessExpr then
|
|
begin
|
|
Fld := TFieldAccessExpr(AExpr);
|
|
if Fld.Base <> nil then
|
|
Base := EmitInstancePtr(Fld.Base)
|
|
else
|
|
begin
|
|
{ Leaf: RecordName-based access, same rules as for TIdentExpr. }
|
|
if (Fld.ResolvedType = nil) then
|
|
raise ECodeGenError.Create('Chained base has no resolved type');
|
|
if Fld.IsClassAccess then
|
|
begin
|
|
Loaded := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [Loaded, Fld.RecordName]));
|
|
Base := Loaded;
|
|
end
|
|
else
|
|
Base := Format('%%_var_%s', [Fld.RecordName]);
|
|
end;
|
|
if Fld.FieldInfo = nil then
|
|
raise ECodeGenError.Create(
|
|
'Chained field access ''' + Fld.FieldName + ''' has no resolved field info');
|
|
if Fld.FieldInfo.Offset > 0 then
|
|
begin
|
|
Ptr := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d',
|
|
[Ptr, Base, Fld.FieldInfo.Offset]));
|
|
end
|
|
else
|
|
Ptr := Base;
|
|
{ If this field is a class pointer, load it to get the heap object pointer.
|
|
If it is an inline record, the pointer itself points to the storage. }
|
|
if Fld.FieldInfo.TypeDesc.Kind = tyClass then
|
|
begin
|
|
Loaded := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [Loaded, Ptr]));
|
|
Result := Loaded;
|
|
end
|
|
else
|
|
Result := Ptr;
|
|
Exit;
|
|
end;
|
|
|
|
raise ECodeGenError.Create('EmitInstancePtr: unsupported base expression');
|
|
end;
|
|
|
|
function TCodeGenQBE.FieldPtr(const ARecordVar: string; AOffset: Integer): string;
|
|
var
|
|
PtrTemp: string;
|
|
begin
|
|
if AOffset = 0 then
|
|
begin
|
|
Result := Format('%%_var_%s', [ARecordVar]);
|
|
end
|
|
else
|
|
begin
|
|
PtrTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l add %%_var_%s, %d', [PtrTemp, ARecordVar, AOffset]));
|
|
Result := PtrTemp;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitFieldAssignment(AAssign: TFieldAssignment);
|
|
var
|
|
Ptr, PtrTemp, ValTemp, OldTemp, QType, StoreInstr: string;
|
|
IsArc: Boolean;
|
|
IsStr: Boolean;
|
|
begin
|
|
if AAssign.FieldInfo = nil then
|
|
raise ECodeGenError.CreateFmt(
|
|
'Field assignment ''%s.%s'' has no resolved field info',
|
|
[AAssign.RecordName, AAssign.FieldName]);
|
|
|
|
ValTemp := EmitExpr(AAssign.Expr);
|
|
|
|
if AAssign.IsClassAccess then
|
|
begin
|
|
{ Load the heap pointer stored in the class variable }
|
|
PtrTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [PtrTemp, AAssign.RecordName]));
|
|
if AAssign.FieldInfo.Offset > 0 then
|
|
begin
|
|
Ptr := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [Ptr, PtrTemp, AAssign.FieldInfo.Offset]));
|
|
end
|
|
else
|
|
Ptr := PtrTemp;
|
|
end
|
|
else
|
|
Ptr := FieldPtr(AAssign.RecordName, AAssign.FieldInfo.Offset);
|
|
|
|
IsStr := AAssign.FieldInfo.TypeDesc.IsString;
|
|
IsArc := IsStr or (AAssign.FieldInfo.TypeDesc.Kind = tyClass);
|
|
if AAssign.FieldInfo.IsWeak then
|
|
begin
|
|
{ Weak class field: store through _WeakAssign so the runtime can zero
|
|
the field slot if the target is freed while the weak ref is live. }
|
|
EmitLine(Format(' call $_WeakAssign(l %s, l %s)', [Ptr, ValTemp]));
|
|
end
|
|
else if IsArc then
|
|
begin
|
|
{ ARC for ARC-managed field storage: retain the new value and release the
|
|
old field contents before overwriting, so neither reference leaks. }
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, Ptr]));
|
|
if IsStr then
|
|
begin
|
|
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_StringRelease(l %s)', [OldTemp]));
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
|
end;
|
|
EmitLine(Format(' storel %s, %s', [ValTemp, Ptr]));
|
|
end
|
|
else
|
|
begin
|
|
QType := QbeTypeOf(AAssign.FieldInfo.TypeDesc);
|
|
if QType = 'w' then StoreInstr := 'storew'
|
|
else StoreInstr := 'storel';
|
|
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, Ptr]));
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitMethodCall(ACall: TMethodCallStmt);
|
|
var
|
|
RT: TRecordTypeDesc;
|
|
MDecl: TMethodDecl;
|
|
SelfTemp: string;
|
|
Par: TMethodParam;
|
|
ArgTemp: string;
|
|
ArgLine: string;
|
|
I: Integer;
|
|
QType: string;
|
|
FuncName: string;
|
|
VTblTemp: string;
|
|
FPtrTemp: string;
|
|
SlotOff: Integer;
|
|
IntfDesc: TInterfaceTypeDesc;
|
|
begin
|
|
{ Interface method dispatch: load obj + itab, index by method slot }
|
|
if (ACall.ResolvedClassType <> nil) and
|
|
(ACall.ResolvedClassType.Kind = tyInterface) then
|
|
begin
|
|
IntfDesc := TInterfaceTypeDesc(ACall.ResolvedClassType);
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj', [SelfTemp, ACall.ObjectName]));
|
|
VTblTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_itab', [VTblTemp, ACall.ObjectName]));
|
|
SlotOff := IntfDesc.MethodIndex(ACall.Name) * 8;
|
|
FPtrTemp := AllocTemp;
|
|
if SlotOff = 0 then
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, VTblTemp]))
|
|
else
|
|
begin
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
|
end;
|
|
EmitLine(Format(' call %s(l %s)', [FPtrTemp, SelfTemp]));
|
|
Exit;
|
|
end;
|
|
|
|
{ Built-in Free: release the instance (decrement refcount; free at zero)
|
|
and nil out the slot. Under universal ARC, Free is a sanctioned synonym
|
|
for immediate release — if other references remain, the block survives
|
|
until their scope exits release them too. Zeroing the slot makes a
|
|
subsequent scope-exit release a safe no-op. }
|
|
if (ACall.ResolvedMethod = nil) and SameText(ACall.Name, 'Free') then
|
|
begin
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [SelfTemp, ACall.ObjectName]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [SelfTemp]));
|
|
EmitLine(Format(' storel 0, %%_var_%s', [ACall.ObjectName]));
|
|
Exit;
|
|
end;
|
|
|
|
RT := TRecordTypeDesc(ACall.ResolvedClassType);
|
|
MDecl := TMethodDecl(ACall.ResolvedMethod);
|
|
|
|
{ Load the object pointer (Self) from the caller's variable slot }
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [SelfTemp, ACall.ObjectName]));
|
|
|
|
{ Build argument string: l Self, then each explicit arg }
|
|
ArgLine := Format('l %s', [SelfTemp]);
|
|
for I := 0 to ACall.Args.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(MDecl.Params[I]);
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[I]));
|
|
QType := QbeTypeOf(Par.ResolvedType);
|
|
ArgLine := ArgLine + Format(', %s %s', [QType, ArgTemp]);
|
|
end;
|
|
|
|
if MDecl.VTableSlot >= 0 then
|
|
begin
|
|
{ Virtual dispatch: load vptr from instance[0], then load fptr from vtable.
|
|
Slot 0 of vtable is typeinfo, so method N is at offset (N+1)*8. }
|
|
VTblTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [VTblTemp, SelfTemp]));
|
|
FPtrTemp := AllocTemp;
|
|
SlotOff := (MDecl.VTableSlot + 1) * 8;
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
|
EmitLine(Format(' call %s(%s)', [FPtrTemp, ArgLine]));
|
|
end
|
|
else
|
|
begin
|
|
{ Static dispatch }
|
|
if MDecl.OwnerTypeName <> '' then
|
|
FuncName := '$' + MDecl.OwnerTypeName + '_' + ACall.Name
|
|
else
|
|
FuncName := '$' + RT.Name + '_' + ACall.Name;
|
|
EmitLine(Format(' call %s(%s)', [FuncName, ArgLine]));
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitCaseStmt(AStmt: TCaseStmt);
|
|
var
|
|
SelTemp: string;
|
|
ValTemp: string;
|
|
CmpTemp: string;
|
|
NextLbl: string;
|
|
BranchLbl: string;
|
|
ElseLbl: string;
|
|
EndLbl: string;
|
|
Branch: TCaseBranch;
|
|
I, J: Integer;
|
|
BranchLabels: TStringList;
|
|
begin
|
|
SelTemp := EmitExpr(AStmt.Selector);
|
|
EndLbl := AllocLabel('case_end');
|
|
ElseLbl := AllocLabel('case_else');
|
|
|
|
BranchLabels := TStringList.Create;
|
|
try
|
|
for I := 0 to AStmt.Branches.Count - 1 do
|
|
BranchLabels.Add(AllocLabel('case_br'));
|
|
|
|
{ Dispatch block: for each branch test all its values;
|
|
on no match fall through to next branch test or else }
|
|
for I := 0 to AStmt.Branches.Count - 1 do
|
|
begin
|
|
Branch := TCaseBranch(AStmt.Branches[I]);
|
|
BranchLbl := BranchLabels[I];
|
|
for J := 0 to Branch.Values.Count - 1 do
|
|
begin
|
|
ValTemp := EmitExpr(TASTExpr(Branch.Values[J]));
|
|
CmpTemp := AllocTemp;
|
|
NextLbl := AllocLabel('case_next');
|
|
EmitLine(Format(' %s =w ceqw %s, %s', [CmpTemp, SelTemp, ValTemp]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [CmpTemp, BranchLbl, NextLbl]));
|
|
EmitLine('@' + NextLbl);
|
|
end;
|
|
end;
|
|
EmitLine(Format(' jmp @%s', [ElseLbl]));
|
|
|
|
{ Branch bodies }
|
|
for I := 0 to AStmt.Branches.Count - 1 do
|
|
begin
|
|
Branch := TCaseBranch(AStmt.Branches[I]);
|
|
BranchLbl := BranchLabels[I];
|
|
EmitLine('@' + BranchLbl);
|
|
EmitStmt(Branch.Stmt);
|
|
EmitLine(Format(' jmp @%s', [EndLbl]));
|
|
end;
|
|
|
|
EmitLine('@' + ElseLbl);
|
|
if AStmt.ElseStmt <> nil then
|
|
EmitStmt(AStmt.ElseStmt);
|
|
EmitLine(Format(' jmp @%s', [EndLbl]));
|
|
|
|
EmitLine('@' + EndLbl);
|
|
finally
|
|
BranchLabels.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitInheritedCall(ACall: TInheritedCallStmt);
|
|
var
|
|
MDecl: TMethodDecl;
|
|
SelfTemp: string;
|
|
ArgLine: string;
|
|
ArgTemp: string;
|
|
Par: TMethodParam;
|
|
QType: string;
|
|
I: Integer;
|
|
begin
|
|
MDecl := TMethodDecl(ACall.ResolvedMethod);
|
|
|
|
{ Load Self from the current method's local slot }
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_Self', [SelfTemp]));
|
|
|
|
{ Build arg string: l Self, then each explicit arg }
|
|
ArgLine := Format('l %s', [SelfTemp]);
|
|
for I := 0 to ACall.Args.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(MDecl.Params[I]);
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[I]));
|
|
QType := QbeTypeOf(Par.ResolvedType);
|
|
ArgLine := ArgLine + Format(', %s %s', [QType, ArgTemp]);
|
|
end;
|
|
|
|
{ Always a direct (static) call — inherited bypasses vtable dispatch }
|
|
EmitLine(Format(' call $%s_%s(%s)',
|
|
[MDecl.OwnerTypeName, ACall.Name, ArgLine]));
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitParamAllocs(AMethod: TMethodDecl;
|
|
AClassType: TRecordTypeDesc);
|
|
var
|
|
I: Integer;
|
|
Par: TMethodParam;
|
|
begin
|
|
{ Self: store incoming pointer into a local slot }
|
|
EmitLine(' %_var_Self =l alloc8 1');
|
|
EmitLine(' storel %_par_Self, %_var_Self');
|
|
|
|
{ Explicit params }
|
|
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, tyEnum:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc4 1', [Par.ParamName]));
|
|
EmitLine(Format(' storew %%_par_%s, %%_var_%s',
|
|
[Par.ParamName, Par.ParamName]));
|
|
end;
|
|
tyInt64, tyString, tyClass:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [Par.ParamName]));
|
|
EmitLine(Format(' storel %%_par_%s, %%_var_%s',
|
|
[Par.ParamName, Par.ParamName]));
|
|
end;
|
|
else
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [Par.ParamName]));
|
|
EmitLine(Format(' storel %%_par_%s, %%_var_%s',
|
|
[Par.ParamName, Par.ParamName]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitMethodDef(const ATypeName: string;
|
|
AMethod: TMethodDecl);
|
|
var
|
|
Sig: string;
|
|
I: Integer;
|
|
Par: TMethodParam;
|
|
FuncName: string;
|
|
IsFunc: Boolean;
|
|
RetQType: string;
|
|
RetTemp: string;
|
|
SavedExitLbl: string;
|
|
ValTemp: string;
|
|
begin
|
|
FuncName := '$' + ATypeName + '_' + AMethod.Name;
|
|
IsFunc := AMethod.ResolvedReturnType <> nil;
|
|
|
|
{ Build parameter signature: l %_par_Self [, qtype %_par_Name ...] }
|
|
Sig := 'l %_par_Self';
|
|
for I := 0 to AMethod.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(AMethod.Params[I]);
|
|
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
|
|
begin
|
|
RetQType := QbeTypeOf(AMethod.ResolvedReturnType);
|
|
EmitLine(Format('function %s %s(%s) {', [RetQType, FuncName, Sig]));
|
|
end
|
|
else
|
|
EmitLine(Format('function %s(%s) {', [FuncName, Sig]));
|
|
|
|
EmitLine('@start');
|
|
EmitParamAllocs(AMethod, nil);
|
|
|
|
{ ARC: addref class value params on entry — balances the release pass at
|
|
method exit. Strings in method params are not ARC-managed yet (existing
|
|
gap); classes are covered here because the whole Phase 3 follow-up is
|
|
specifically about class ARC parity. }
|
|
for I := 0 to AMethod.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(AMethod.Params[I]);
|
|
if Par.IsVarParam then Continue;
|
|
if Par.ResolvedType.Kind = tyClass then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
|
end;
|
|
end;
|
|
|
|
{ For function methods, allocate a zero-initialised Result slot }
|
|
if IsFunc then
|
|
begin
|
|
if RetQType = 'w' then
|
|
begin
|
|
EmitLine(' %_var_Result =l alloc4 1');
|
|
EmitLine(' storew 0, %_var_Result');
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(' %_var_Result =l alloc8 1');
|
|
EmitLine(' storel 0, %_var_Result');
|
|
end;
|
|
end;
|
|
|
|
SavedExitLbl := FExitLabel;
|
|
FExitLabel := AllocLabel('method_exit');
|
|
try
|
|
EmitBlock(AMethod.Body);
|
|
finally
|
|
FExitLabel := SavedExitLbl;
|
|
end;
|
|
|
|
{ ARC: release class value params on exit. }
|
|
for I := 0 to AMethod.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(AMethod.Params[I]);
|
|
if Par.IsVarParam then Continue;
|
|
if Par.ResolvedType.Kind = tyClass then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [ValTemp]));
|
|
end;
|
|
end;
|
|
|
|
if IsFunc then
|
|
begin
|
|
RetTemp := AllocTemp;
|
|
if RetQType = 'w' then
|
|
EmitLine(Format(' %s =w loadw %%_var_Result', [RetTemp]))
|
|
else
|
|
EmitLine(Format(' %s =l loadl %%_var_Result', [RetTemp]));
|
|
EmitLine(Format(' ret %s', [RetTemp]));
|
|
end
|
|
else
|
|
EmitLine(' ret');
|
|
|
|
EmitLine('}');
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitTypeInfoDefs(AProg: TProgram);
|
|
{ Emit one $typeinfo_T data item per class type.
|
|
Layout: { l parent_typeinfo_or_zero, l impllist_or_zero }
|
|
TypeInfo is at vtable slot 0; _IsInstance walks parent chain.
|
|
impllist is NULL-terminated array of {typeinfo_intf, itab} pairs for _ImplementsInterface.
|
|
|
|
TObject is the built-in root class; any user class with an explicit
|
|
`class(TObject, IFoo)` parent list resolves Parent to TObject's
|
|
TRecordTypeDesc, so we emit a typeinfo stub for TObject unconditionally
|
|
to satisfy the linker. Its parent slot is nil and its impllist slot
|
|
is nil because TObject implements no interfaces. }
|
|
var
|
|
I: Integer;
|
|
TD: TTypeDecl;
|
|
TDesc: TTypeDesc;
|
|
RT: TRecordTypeDesc;
|
|
GI: TGenericInstance;
|
|
ParentStr: string;
|
|
ImplStr: string;
|
|
MName: string;
|
|
begin
|
|
EmitLine('data $typeinfo_TObject = { l 0, l 0 }');
|
|
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TClassTypeDef) then Continue;
|
|
TDesc := AProg.SymbolTable.FindType(TD.Name);
|
|
if (TDesc = nil) or not (TDesc is TRecordTypeDesc) then Continue;
|
|
RT := TRecordTypeDesc(TDesc);
|
|
if RT.Parent <> nil then
|
|
ParentStr := '$typeinfo_' + RT.Parent.Name
|
|
else
|
|
ParentStr := '0';
|
|
if RT.ImplementsCount > 0 then
|
|
ImplStr := '$impllist_' + TD.Name
|
|
else
|
|
ImplStr := '0';
|
|
EmitLine('data $typeinfo_' + TD.Name +
|
|
' = { l ' + ParentStr + ', l ' + ImplStr + ' }');
|
|
end;
|
|
|
|
{ Generic instances }
|
|
for I := 0 to AProg.GenericInstances.Count - 1 do
|
|
begin
|
|
GI := TGenericInstance(AProg.GenericInstances[I]);
|
|
RT := TRecordTypeDesc(GI.TypeDesc);
|
|
MName := QBEMangle(GI.TypeName);
|
|
if RT.Parent <> nil then
|
|
ParentStr := '$typeinfo_' + QBEMangle(RT.Parent.Name)
|
|
else
|
|
ParentStr := '0';
|
|
ImplStr := '0';
|
|
EmitLine('data $typeinfo_' + MName + ' = { l ' + ParentStr + ', l ' + ImplStr + ' }');
|
|
end;
|
|
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitVTableDefs(AProg: TProgram);
|
|
{ Vtable layout: slot 0 = $typeinfo_T pointer, slots 1..N = virtual method ptrs.
|
|
Dispatch uses (VTableSlot + 1) * 8 to skip the typeinfo slot. }
|
|
var
|
|
I, S: Integer;
|
|
TD: TTypeDecl;
|
|
TDesc: TTypeDesc;
|
|
RT: TRecordTypeDesc;
|
|
GI: TGenericInstance;
|
|
E: TVTableEntry;
|
|
Line: string;
|
|
MName: string;
|
|
begin
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TClassTypeDef) then Continue;
|
|
TDesc := AProg.SymbolTable.FindType(TD.Name);
|
|
if (TDesc = nil) or not (TDesc is TRecordTypeDesc) then Continue;
|
|
RT := TRecordTypeDesc(TDesc);
|
|
if not RT.HasVTable then Continue;
|
|
{ TypeInfo pointer is always the first vtable entry }
|
|
Line := 'data $vtable_' + TD.Name + ' = { l $typeinfo_' + TD.Name;
|
|
for S := 0 to RT.VTableCount - 1 do
|
|
begin
|
|
E := RT.VTableEntryAt(S);
|
|
Line := Line + ', l ' + E.ImplName;
|
|
end;
|
|
Line := Line + ' }';
|
|
EmitLine(Line);
|
|
end;
|
|
|
|
{ Generic instances }
|
|
for I := 0 to AProg.GenericInstances.Count - 1 do
|
|
begin
|
|
GI := TGenericInstance(AProg.GenericInstances[I]);
|
|
RT := TRecordTypeDesc(GI.TypeDesc);
|
|
if not RT.HasVTable then Continue;
|
|
MName := QBEMangle(GI.TypeName);
|
|
Line := 'data $vtable_' + MName + ' = { l $typeinfo_' + MName;
|
|
for S := 0 to RT.VTableCount - 1 do
|
|
begin
|
|
E := RT.VTableEntryAt(S);
|
|
Line := Line + ', l ' + QBEMangle(E.ImplName);
|
|
end;
|
|
Line := Line + ' }';
|
|
EmitLine(Line);
|
|
end;
|
|
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitInterfaceDefs(AProg: TProgram);
|
|
{ Emit typeinfo blocks for interfaces and itab/impllist blocks for class-interface pairs.
|
|
Interface typeinfo: data $typeinfo_IFoo = { l 0 } (address IS the identity token)
|
|
Itab: data $itab_TFoo_IFoo = { l $TFoo_DoIt, l $TFoo_GetVal }
|
|
Impllist: data $impllist_TFoo = { l $typeinfo_IFoo, l $itab_TFoo_IFoo, l 0 }
|
|
Methods in declaration order; impllist is NULL-terminated {ti, itab} pair array. }
|
|
var
|
|
I, J, K: Integer;
|
|
TD: TTypeDecl;
|
|
TDesc: TTypeDesc;
|
|
IntfDesc: TInterfaceTypeDesc;
|
|
ClassRT: TRecordTypeDesc;
|
|
ItabLine: string;
|
|
ImplLine: string;
|
|
MethName: string;
|
|
IntfMangle: string;
|
|
GII: TGenericInterfaceInstance;
|
|
begin
|
|
{ Typeinfo blocks for every plain interface }
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TInterfaceTypeDef) then Continue;
|
|
EmitLine('data $typeinfo_' + TD.Name + ' = { l 0 }');
|
|
end;
|
|
|
|
{ Typeinfo blocks for generic interface instantiations }
|
|
for I := 0 to AProg.GenericIntfInstances.Count - 1 do
|
|
begin
|
|
GII := TGenericInterfaceInstance(AProg.GenericIntfInstances[I]);
|
|
EmitLine('data $typeinfo_' + GII.InstName + ' = { l 0 }');
|
|
end;
|
|
|
|
{ Itab and impllist blocks for each implementing class }
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TClassTypeDef) then Continue;
|
|
TDesc := AProg.SymbolTable.FindType(TD.Name);
|
|
if (TDesc = nil) or not (TDesc is TRecordTypeDesc) then Continue;
|
|
ClassRT := TRecordTypeDesc(TDesc);
|
|
if ClassRT.ImplementsCount = 0 then Continue;
|
|
|
|
{ One itab per interface }
|
|
for J := 0 to ClassRT.ImplementsCount - 1 do
|
|
begin
|
|
IntfDesc := ClassRT.ImplementsIntfAt(J);
|
|
IntfMangle := QBEMangle(IntfDesc.Name);
|
|
ItabLine := 'data $itab_' + TD.Name + '_' + IntfMangle + ' = {';
|
|
for K := 0 to IntfDesc.MethodCount - 1 do
|
|
begin
|
|
MethName := IntfDesc.MethodName(K);
|
|
if K = 0 then
|
|
ItabLine := ItabLine + ' l $' + TD.Name + '_' + MethName
|
|
else
|
|
ItabLine := ItabLine + ', l $' + TD.Name + '_' + MethName;
|
|
end;
|
|
ItabLine := ItabLine + ' }';
|
|
EmitLine(ItabLine);
|
|
end;
|
|
|
|
{ One impllist per class: NULL-terminated {typeinfo_intf, itab} pairs }
|
|
ImplLine := 'data $impllist_' + TD.Name + ' = {';
|
|
for J := 0 to ClassRT.ImplementsCount - 1 do
|
|
begin
|
|
IntfDesc := ClassRT.ImplementsIntfAt(J);
|
|
IntfMangle := QBEMangle(IntfDesc.Name);
|
|
if J = 0 then
|
|
ImplLine := ImplLine + ' l $typeinfo_' + IntfMangle +
|
|
', l $itab_' + TD.Name + '_' + IntfMangle
|
|
else
|
|
ImplLine := ImplLine + ', l $typeinfo_' + IntfMangle +
|
|
', l $itab_' + TD.Name + '_' + IntfMangle;
|
|
end;
|
|
ImplLine := ImplLine + ', l 0 }';
|
|
EmitLine(ImplLine);
|
|
end;
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitFieldCleanupFn(const AMangledName: string;
|
|
ARec: TRecordTypeDesc);
|
|
{ Emit a QBE function $_FieldCleanup_<Name>(l %self) that releases every
|
|
ARC-managed field the instance holds. The function is invoked from
|
|
_ClassRelease at refcount zero, before the backing block is freed.
|
|
|
|
The most-derived class's cleanup is authoritative: inherited fields are
|
|
already merged into this class's Fields list by the semantic analyser
|
|
(see uSemantic.pas — parent fields are copied into the derived
|
|
TRecordTypeDesc during Pass 2), so iterating Fields here covers own and
|
|
inherited fields uniformly. We therefore do not chain to a parent
|
|
cleanup — doing so would release each inherited field twice.
|
|
|
|
A cleanup function is emitted for every class even when it has no
|
|
ARC-managed fields — the no-op call keeps the constructor call site
|
|
uniform and is negligible at runtime. }
|
|
var
|
|
I: Integer;
|
|
F: TFieldInfo;
|
|
Temp: string;
|
|
PtrT: string;
|
|
begin
|
|
EmitLine(Format('function $_FieldCleanup_%s(l %%self) {', [AMangledName]));
|
|
EmitLine('@start');
|
|
{ If the class declares a Destroy method, invoke it first so it can release
|
|
raw resources (e.g. FreeMem of internal buffers) before ARC field cleanup. }
|
|
if ARec.HasDestroyMethod then
|
|
EmitLine(Format(' call $%s_Destroy(l %%self)', [AMangledName]));
|
|
for I := 0 to ARec.Fields.Count - 1 do
|
|
begin
|
|
F := TFieldInfo(ARec.Fields[I]);
|
|
if F.TypeDesc = nil then Continue;
|
|
if not (F.TypeDesc.IsString or (F.TypeDesc.Kind = tyClass)) then
|
|
Continue;
|
|
if F.Offset > 0 then
|
|
begin
|
|
PtrT := AllocTemp;
|
|
EmitLine(Format(' %s =l add %%self, %d', [PtrT, F.Offset]));
|
|
end
|
|
else
|
|
PtrT := '%self';
|
|
if F.IsWeak then
|
|
begin
|
|
{ Weak field: unregister from the weak table without decrementing
|
|
any refcount. _WeakClear zeros *Ptr for us. }
|
|
EmitLine(Format(' call $_WeakClear(l %s)', [PtrT]));
|
|
Continue;
|
|
end;
|
|
Temp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [Temp, PtrT]));
|
|
if F.TypeDesc.IsString then
|
|
EmitLine(Format(' call $_StringRelease(l %s)', [Temp]))
|
|
else
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [Temp]));
|
|
end;
|
|
EmitLine(' ret');
|
|
EmitLine('}');
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitFieldCleanupDefs(AProg: TProgram);
|
|
{ Emit a _FieldCleanup_<T> function for every declared class and every
|
|
generic class instantiation. The constructor lowering references these
|
|
functions by name; see the _ClassAlloc call in EmitExpr. }
|
|
var
|
|
I: Integer;
|
|
TD: TTypeDecl;
|
|
TDesc: TTypeDesc;
|
|
RT: TRecordTypeDesc;
|
|
GI: TGenericInstance;
|
|
begin
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TClassTypeDef) then Continue;
|
|
TDesc := AProg.SymbolTable.FindType(TD.Name);
|
|
if (TDesc = nil) or not (TDesc is TRecordTypeDesc) then Continue;
|
|
RT := TRecordTypeDesc(TDesc);
|
|
EmitFieldCleanupFn(TD.Name, RT);
|
|
end;
|
|
for I := 0 to AProg.GenericInstances.Count - 1 do
|
|
begin
|
|
GI := TGenericInstance(AProg.GenericInstances[I]);
|
|
RT := TRecordTypeDesc(GI.TypeDesc);
|
|
EmitFieldCleanupFn(QBEMangle(GI.TypeName), RT);
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitMethodDefs(AProg: TProgram);
|
|
var
|
|
I, J: Integer;
|
|
TD: TTypeDecl;
|
|
CD: TClassTypeDef;
|
|
GI: TGenericInstance;
|
|
MDecl: TMethodDecl;
|
|
begin
|
|
for I := 0 to AProg.Block.TypeDecls.Count - 1 do
|
|
begin
|
|
TD := TTypeDecl(AProg.Block.TypeDecls[I]);
|
|
if not (TD.Def is TClassTypeDef) then
|
|
Continue;
|
|
CD := TClassTypeDef(TD.Def);
|
|
for J := 0 to CD.Methods.Count - 1 do
|
|
if TMethodDecl(CD.Methods[J]).Body <> nil then
|
|
EmitMethodDef(TD.Name, TMethodDecl(CD.Methods[J]));
|
|
end;
|
|
|
|
{ Generic instances — emit with mangled type name }
|
|
for I := 0 to AProg.GenericInstances.Count - 1 do
|
|
begin
|
|
GI := TGenericInstance(AProg.GenericInstances[I]);
|
|
for J := 0 to GI.ClassDef.Methods.Count - 1 do
|
|
begin
|
|
MDecl := TMethodDecl(GI.ClassDef.Methods[J]);
|
|
if MDecl.Body <> nil then
|
|
EmitMethodDef(QBEMangle(GI.TypeName), MDecl);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitFuncDef(ADecl: TMethodDecl; AExported: Boolean);
|
|
var
|
|
Sig: string;
|
|
I: Integer;
|
|
Par: TMethodParam;
|
|
FuncName: string;
|
|
IsFunc: Boolean;
|
|
RetQType: string;
|
|
RetTemp: string;
|
|
ValTemp: string;
|
|
Prefix: string;
|
|
SavedExitLbl: string;
|
|
begin
|
|
FuncName := '$' + QBEMangle(ADecl.Name);
|
|
IsFunc := ADecl.ResolvedReturnType <> nil;
|
|
if AExported then Prefix := 'export ' else Prefix := '';
|
|
|
|
Sig := '';
|
|
for I := 0 to ADecl.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(ADecl.Params[I]);
|
|
if Sig <> '' then Sig := Sig + ', ';
|
|
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
|
|
begin
|
|
RetQType := QbeTypeOf(ADecl.ResolvedReturnType);
|
|
EmitLine(Format('%sfunction %s %s(%s) {', [Prefix, RetQType, FuncName, Sig]));
|
|
end
|
|
else
|
|
EmitLine(Format('%sfunction %s(%s) {', [Prefix, FuncName, Sig]));
|
|
|
|
EmitLine('@start');
|
|
|
|
for I := 0 to ADecl.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(ADecl.Params[I]);
|
|
if Par.IsVarParam then
|
|
begin
|
|
{ Var param: spill the 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
|
|
begin
|
|
case Par.ResolvedType.Kind of
|
|
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
|
begin
|
|
EmitLine(Format(' %%_var_%s =l alloc4 1', [Par.ParamName]));
|
|
EmitLine(Format(' storew %%_par_%s, %%_var_%s',
|
|
[Par.ParamName, Par.ParamName]));
|
|
end;
|
|
else
|
|
EmitLine(Format(' %%_var_%s =l alloc8 1', [Par.ParamName]));
|
|
EmitLine(Format(' storel %%_par_%s, %%_var_%s',
|
|
[Par.ParamName, Par.ParamName]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ ARC: addref string and class value params on entry (callee owns a
|
|
retained copy that is balanced by the release pass at function exit). }
|
|
for I := 0 to ADecl.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(ADecl.Params[I]);
|
|
if Par.IsVarParam then Continue;
|
|
if Par.ResolvedType.Kind = tyString then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
|
end
|
|
else if Par.ResolvedType.Kind = tyClass then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ValTemp]));
|
|
end;
|
|
end;
|
|
|
|
if IsFunc then
|
|
begin
|
|
if RetQType = 'w' then
|
|
begin
|
|
EmitLine(' %_var_Result =l alloc4 1');
|
|
EmitLine(' storew 0, %_var_Result');
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(' %_var_Result =l alloc8 1');
|
|
EmitLine(' storel 0, %_var_Result');
|
|
end;
|
|
end;
|
|
|
|
SavedExitLbl := FExitLabel;
|
|
FExitLabel := AllocLabel('func_exit');
|
|
try
|
|
EmitBlock(ADecl.Body);
|
|
finally
|
|
FExitLabel := SavedExitLbl;
|
|
end;
|
|
|
|
{ ARC: release string and class value params on exit (balances the
|
|
addref inserted at function entry). }
|
|
for I := 0 to ADecl.Params.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(ADecl.Params[I]);
|
|
if Par.IsVarParam then Continue;
|
|
if Par.ResolvedType.Kind = tyString then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_StringRelease(l %s)', [ValTemp]));
|
|
end
|
|
else if Par.ResolvedType.Kind = tyClass then
|
|
begin
|
|
ValTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, Par.ParamName]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [ValTemp]));
|
|
end;
|
|
end;
|
|
|
|
if IsFunc then
|
|
begin
|
|
RetTemp := AllocTemp;
|
|
if RetQType = 'w' then
|
|
EmitLine(Format(' %s =w loadw %%_var_Result', [RetTemp]))
|
|
else
|
|
EmitLine(Format(' %s =l loadl %%_var_Result', [RetTemp]));
|
|
EmitLine(Format(' ret %s', [RetTemp]));
|
|
end
|
|
else
|
|
EmitLine(' ret');
|
|
|
|
EmitLine('}');
|
|
EmitLine('');
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitStandaloneDef(ADecl: TMethodDecl);
|
|
begin
|
|
EmitFuncDef(ADecl, False);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitStandaloneDefs(AProg: TProgram);
|
|
var
|
|
I: Integer;
|
|
Decl: TMethodDecl;
|
|
begin
|
|
for I := 0 to AProg.Block.ProcDecls.Count - 1 do
|
|
begin
|
|
Decl := TMethodDecl(AProg.Block.ProcDecls[I]);
|
|
{ Class method impls had their body transferred — skip here. }
|
|
if Decl.OwnerTypeName <> '' then Continue;
|
|
{ Generic templates — concrete instances are emitted via GenericFuncInstances. }
|
|
if Decl.TypeParams <> nil then Continue;
|
|
EmitStandaloneDef(Decl);
|
|
end;
|
|
{ Emit each concrete generic function instance }
|
|
for I := 0 to AProg.GenericFuncInstances.Count - 1 do
|
|
EmitStandaloneDef(
|
|
TGenericFuncInstance(AProg.GenericFuncInstances[I]).MethodDecl);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitProcCall(ACall: TProcCall);
|
|
var
|
|
UCaseName: string;
|
|
MDecl: TMethodDecl;
|
|
Par: TMethodParam;
|
|
ArgTemp: string;
|
|
ArgTemp2: string;
|
|
SizeTemp: string;
|
|
ArgLine: string;
|
|
I: Integer;
|
|
begin
|
|
{ User-defined procedure }
|
|
if ACall.ResolvedDecl <> nil then
|
|
begin
|
|
MDecl := TMethodDecl(ACall.ResolvedDecl);
|
|
ArgLine := '';
|
|
for I := 0 to ACall.Args.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(MDecl.Params[I]);
|
|
if ArgLine <> '' then ArgLine := ArgLine + ', ';
|
|
if Par.IsVarParam then
|
|
{ Pass address of the variable — skip the load }
|
|
ArgLine := ArgLine + Format('l %%_var_%s',
|
|
[TIdentExpr(TASTExpr(ACall.Args[I])).Name])
|
|
else
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[I]));
|
|
ArgLine := ArgLine + Format('%s %s', [QbeTypeOf(Par.ResolvedType), ArgTemp]);
|
|
end;
|
|
end;
|
|
EmitLine(Format(' call $%s(%s)', [QBEMangle(ACall.Name), ArgLine]));
|
|
Exit;
|
|
end;
|
|
|
|
{ Built-in }
|
|
UCaseName := UpperCase(ACall.Name);
|
|
if UCaseName = 'WRITELN' then
|
|
EmitWrite(ACall, True)
|
|
else if UCaseName = 'WRITE' then
|
|
EmitWrite(ACall, False)
|
|
else if UCaseName = 'FREEMEM' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
EmitLine(Format(' call $free(l %s)', [ArgTemp]));
|
|
end
|
|
else if UCaseName = 'ZEROMEM' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
ArgTemp2 := EmitExpr(TASTExpr(ACall.Args[1]));
|
|
SizeTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l extsw %s', [SizeTemp, ArgTemp2]));
|
|
EmitLine(Format(' call $memset(l %s, w 0, l %s)', [ArgTemp, SizeTemp]));
|
|
end
|
|
else if UCaseName = '_CLASSADDREF' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [ArgTemp]));
|
|
end
|
|
else if UCaseName = '_CLASSRELEASE' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
EmitLine(Format(' call $_ClassRelease(l %s)', [ArgTemp]));
|
|
end
|
|
else if UCaseName = 'WRITEFILE' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
ArgTemp2 := EmitExpr(TASTExpr(ACall.Args[1]));
|
|
EmitLine(Format(' call $_WriteFile(l %s, l %s)', [ArgTemp, ArgTemp2]));
|
|
end
|
|
else if UCaseName = 'APPENDFILE' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
ArgTemp2 := EmitExpr(TASTExpr(ACall.Args[1]));
|
|
EmitLine(Format(' call $_AppendFile(l %s, l %s)', [ArgTemp, ArgTemp2]));
|
|
end
|
|
else if UCaseName = 'HALT' then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(ACall.Args[0]));
|
|
EmitLine(Format(' call $exit(w %s)', [ArgTemp]));
|
|
end
|
|
else
|
|
raise ECodeGenError.CreateFmt(
|
|
'Unknown procedure ''%s'' at line %d', [ACall.Name, ACall.Line]);
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitPointerWrite(AStmt: TPointerWriteStmt);
|
|
var
|
|
PtrTemp: string;
|
|
ValTemp: string;
|
|
OldTemp: string;
|
|
QType: string;
|
|
StoreInstr: string;
|
|
begin
|
|
PtrTemp := EmitExpr(AStmt.PtrExpr);
|
|
{ ARC: string stored through a typed pointer needs retain/release }
|
|
if (AStmt.BaseTy <> nil) and AStmt.BaseTy.IsString then
|
|
begin
|
|
OldTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [OldTemp, PtrTemp]));
|
|
ValTemp := EmitExpr(AStmt.ValExpr);
|
|
EmitLine(Format(' call $_StringAddRef(l %s)', [ValTemp]));
|
|
EmitLine(Format(' call $_StringRelease(l %s)', [OldTemp]));
|
|
EmitLine(Format(' storel %s, %s', [ValTemp, PtrTemp]));
|
|
Exit;
|
|
end;
|
|
ValTemp := EmitExpr(AStmt.ValExpr);
|
|
QType := QbeTypeOf(AStmt.BaseTy);
|
|
if QType = 'w' then StoreInstr := 'storew'
|
|
else StoreInstr := 'storel';
|
|
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, PtrTemp]));
|
|
end;
|
|
|
|
procedure TCodeGenQBE.EmitWrite(ACall: TProcCall; ANewline: Boolean);
|
|
var
|
|
ArgExpr: TASTExpr;
|
|
ArgTemp: string;
|
|
CharPtr: string;
|
|
IsString: Boolean;
|
|
I: Integer;
|
|
begin
|
|
if ACall.Args.Count = 0 then
|
|
begin
|
|
if ANewline then
|
|
EmitLine(' call $printf(l $__fmt_nl)');
|
|
Exit;
|
|
end;
|
|
|
|
{ Emit one printf call per argument. All formatting is plain ("%d" or "%s")
|
|
with no trailing newline; a final "\n" is emitted after the last arg when
|
|
ANewline is set (i.e. WriteLn rather than Write). }
|
|
for I := 0 to ACall.Args.Count - 1 do
|
|
begin
|
|
ArgExpr := TASTExpr(ACall.Args[I]);
|
|
IsString := (ArgExpr.ResolvedType <> nil) and ArgExpr.ResolvedType.IsString;
|
|
ArgTemp := EmitExpr(ArgExpr);
|
|
if IsString then
|
|
begin
|
|
{ String pointer is the header pointer; char data starts at ptr+12. }
|
|
CharPtr := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, 12', [CharPtr, ArgTemp]));
|
|
EmitLine(Format(' call $printf(l $__fmt_s, ..., l %s)', [CharPtr]));
|
|
end
|
|
else
|
|
EmitLine(Format(' call $printf(l $__fmt_d, ..., w %s)', [ArgTemp]));
|
|
end;
|
|
|
|
if ANewline then
|
|
EmitLine(' call $printf(l $__fmt_nl)');
|
|
end;
|
|
|
|
function TCodeGenQBE.EmitExpr(AExpr: TASTExpr): string;
|
|
var
|
|
T, L, R: string;
|
|
Op: string;
|
|
BinExpr: TBinaryExpr;
|
|
FldAccess: TFieldAccessExpr;
|
|
MCallExpr: TMethodCallExpr;
|
|
Ptr: string;
|
|
QType: string;
|
|
LoadInstr: string;
|
|
SelfTemp: string;
|
|
ArgLine: string;
|
|
ArgTemp: string;
|
|
Par: TMethodParam;
|
|
MDecl: TMethodDecl;
|
|
RT: TRecordTypeDesc;
|
|
FuncName: string;
|
|
I: Integer;
|
|
IntfDesc: TInterfaceTypeDesc;
|
|
VTblTemp: string;
|
|
FPtrTemp: string;
|
|
SlotOff: Integer;
|
|
NoArgCall: TFuncCallExpr;
|
|
begin
|
|
if AExpr is TFuncCallExpr then
|
|
begin
|
|
{ Standalone function call expression }
|
|
with TFuncCallExpr(AExpr) do
|
|
begin
|
|
{ SizeOf(TypeName) → integer literal = byte size of the type }
|
|
if SameText(Name, 'SizeOf') then
|
|
begin
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w copy %d',
|
|
[T, TASTExpr(Args[0]).ResolvedType.ByteSize]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ GetMem(N) → malloc(N) → pointer }
|
|
if SameText(Name, 'GetMem') then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
{ Extend arg to l for malloc }
|
|
L := AllocTemp;
|
|
EmitLine(Format(' %s =l extsw %s', [L, ArgTemp]));
|
|
EmitLine(Format(' %s =l call $malloc(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ ReallocMem(P, N) → realloc(P, N) → pointer }
|
|
if SameText(Name, 'ReallocMem') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l extsw %s', [ArgTemp, R]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $realloc(l %s, l %s)', [T, L, ArgTemp]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ Built-in string operations — delegate to RTL functions }
|
|
if SameText(Name, 'Length') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StringLength(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'Pos') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StringPos(l %s, l %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'Copy') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
ArgTemp := EmitExpr(TASTExpr(Args[2]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_StringCopy(l %s, w %s, w %s)',
|
|
[T, L, R, ArgTemp]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'UpperCase') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_StringUpperCase(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'LowerCase') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_StringLowerCase(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'SameText') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StringSameText(l %s, l %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'IntToStr') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_IntToStr(w %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'StrToInt') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StrToInt(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ Format(fmt, arg0, arg1, ...) → $_StringFormat(l fmt, ..., tag val, ...)
|
|
Each arg is emitted as a (w tag, w/l value) pair after the variadic
|
|
marker. tag=0 for integer types, tag=1 for string/pointer types. }
|
|
if SameText(Name, 'Format') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
{ Build variadic arg pairs: "..., w tag, w/l val, ..." }
|
|
ArgLine := Format('l %s, ...', [L]);
|
|
for I := 1 to Args.Count - 1 do
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(Args[I]));
|
|
if TASTExpr(Args[I]).ResolvedType.Kind in
|
|
[tyInteger, tyBoolean, tyByte, tyUInt32, tyInt64, tyEnum] then
|
|
ArgLine := ArgLine + Format(', w 0, w %s', [ArgTemp])
|
|
else
|
|
ArgLine := ArgLine + Format(', w 1, l %s', [ArgTemp]);
|
|
end;
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_StringFormat(%s)', [T, ArgLine]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'OrdAt') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_OrdAt(l %s, w %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'CompareStr') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StringCompare(l %s, l %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'CompareText') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
R := EmitExpr(TASTExpr(Args[1]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_StringCompareText(l %s, l %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ CLI arguments }
|
|
if SameText(Name, 'ParamCount') then
|
|
begin
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_ParamCount()', [T]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'ParamStr') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_ParamStr(w %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ File I/O functions }
|
|
if SameText(Name, 'ReadFile') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_ReadFile(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'FileExists') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_FileExists(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ Environment and process }
|
|
if SameText(Name, 'GetEnvVar') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_GetEnvVar(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if SameText(Name, 'Exec') then
|
|
begin
|
|
L := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call $_Exec(l %s)', [T, L]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
{ Type cast TypeName(Expr) — ResolvedDecl is nil; just copy with target QBE type }
|
|
if ResolvedDecl = nil then
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(Args[0]));
|
|
T := AllocTemp;
|
|
QType := QbeTypeOf(ResolvedType);
|
|
if QType = 'w' then
|
|
EmitLine(Format(' %s =w copy %s', [T, ArgTemp]))
|
|
else
|
|
EmitLine(Format(' %s =l copy %s', [T, ArgTemp]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
MDecl := TMethodDecl(ResolvedDecl);
|
|
QType := QbeTypeOf(MDecl.ResolvedReturnType);
|
|
FuncName := '$' + QBEMangle(Name);
|
|
ArgLine := '';
|
|
for I := 0 to Args.Count - 1 do
|
|
begin
|
|
Par := TMethodParam(MDecl.Params[I]);
|
|
ArgTemp := EmitExpr(TASTExpr(Args[I]));
|
|
if ArgLine <> '' then ArgLine := ArgLine + ', ';
|
|
ArgLine := ArgLine + Format('%s %s', [QbeTypeOf(Par.ResolvedType), ArgTemp]);
|
|
end;
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =%s call %s(%s)', [T, QType, FuncName, ArgLine]));
|
|
Result := T;
|
|
end;
|
|
Exit;
|
|
end;
|
|
|
|
if AExpr is TMethodCallExpr then
|
|
begin
|
|
MCallExpr := TMethodCallExpr(AExpr);
|
|
|
|
{ Interface method call expression: dispatch through itab }
|
|
if (MCallExpr.ResolvedClassType <> nil) and
|
|
(MCallExpr.ResolvedClassType.Kind = tyInterface) then
|
|
begin
|
|
IntfDesc := TInterfaceTypeDesc(MCallExpr.ResolvedClassType);
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_obj',
|
|
[SelfTemp, MCallExpr.ObjectName]));
|
|
VTblTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s_itab',
|
|
[VTblTemp, MCallExpr.ObjectName]));
|
|
SlotOff := IntfDesc.MethodIndex(MCallExpr.Name) * 8;
|
|
FPtrTemp := AllocTemp;
|
|
if SlotOff = 0 then
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, VTblTemp]))
|
|
else
|
|
begin
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
|
end;
|
|
{ Evaluate arguments before the call }
|
|
ArgLine := Format('l %s', [SelfTemp]);
|
|
for I := 0 to MCallExpr.Args.Count - 1 do
|
|
begin
|
|
ArgTemp := EmitExpr(TASTExpr(MCallExpr.Args[I]));
|
|
ArgLine := ArgLine + Format(', w %s', [ArgTemp]);
|
|
end;
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w call %s(%s)', [T, FPtrTemp, ArgLine]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
RT := TRecordTypeDesc(MCallExpr.ResolvedClassType);
|
|
|
|
{ Constructor call with args: TypeName.Create(args) }
|
|
if MCallExpr.IsConstructorCall then
|
|
begin
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_ClassAlloc(l %d, l $_FieldCleanup_%s)',
|
|
[SelfTemp, RT.TotalSize, RT.Name]));
|
|
EmitLine(Format(' call $_ClassAddRef(l %s)', [SelfTemp]));
|
|
{ If there's a user-defined Create method, call it }
|
|
if MCallExpr.ResolvedMethod <> nil then
|
|
begin
|
|
MDecl := TMethodDecl(MCallExpr.ResolvedMethod);
|
|
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]);
|
|
end;
|
|
if MDecl.OwnerTypeName <> '' then
|
|
FuncName := '$' + MDecl.OwnerTypeName + '_Create'
|
|
else
|
|
FuncName := '$' + RT.Name + '_Create';
|
|
EmitLine(Format(' call %s(%s)', [FuncName, ArgLine]));
|
|
end;
|
|
Result := SelfTemp;
|
|
Exit;
|
|
end;
|
|
|
|
MDecl := TMethodDecl(MCallExpr.ResolvedMethod);
|
|
if MDecl.OwnerTypeName <> '' then
|
|
FuncName := '$' + MDecl.OwnerTypeName + '_' + MCallExpr.Name
|
|
else
|
|
FuncName := '$' + RT.Name + '_' + MCallExpr.Name;
|
|
QType := QbeTypeOf(MDecl.ResolvedReturnType);
|
|
|
|
{ Load the object pointer (Self) }
|
|
SelfTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [SelfTemp, MCallExpr.ObjectName]));
|
|
|
|
{ Build argument string }
|
|
ArgLine := Format('l %s', [SelfTemp]);
|
|
for I := 0 to MCallExpr.Args.Count - 1 do
|
|
begin
|
|
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;
|
|
if MDecl.VTableSlot >= 0 then
|
|
begin
|
|
{ Virtual dispatch: load vptr then function pointer from vtable }
|
|
VTblTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [VTblTemp, SelfTemp]));
|
|
FPtrTemp := AllocTemp;
|
|
SlotOff := (MDecl.VTableSlot + 1) * 8;
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
|
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
|
EmitLine(Format(' %s =%s call %s(%s)', [T, QType, FPtrTemp, ArgLine]));
|
|
end
|
|
else
|
|
EmitLine(Format(' %s =%s call %s(%s)', [T, QType, FuncName, ArgLine]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if AExpr is TNilLiteral then
|
|
begin
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l copy 0', [T]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
|
|
if AExpr is TIntLiteral then
|
|
begin
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w copy %d', [T, TIntLiteral(AExpr).Value]));
|
|
Result := T;
|
|
end
|
|
else if AExpr is TStringLiteral then
|
|
begin
|
|
Result := EmitStrLit(TStringLiteral(AExpr).Value);
|
|
end
|
|
else if AExpr is TFieldAccessExpr then
|
|
begin
|
|
FldAccess := TFieldAccessExpr(AExpr);
|
|
{ Chained access: compute base storage pointer, then load the field from
|
|
(base_ptr + offset) using the field's QBE type. }
|
|
if FldAccess.Base <> nil then
|
|
begin
|
|
L := EmitInstancePtr(FldAccess.Base);
|
|
if FldAccess.FieldInfo = nil then
|
|
raise ECodeGenError.CreateFmt(
|
|
'Chained field ''%s'' has no resolved field info', [FldAccess.FieldName]);
|
|
if FldAccess.FieldInfo.Offset > 0 then
|
|
begin
|
|
Ptr := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d',
|
|
[Ptr, L, FldAccess.FieldInfo.Offset]));
|
|
end
|
|
else
|
|
Ptr := L;
|
|
QType := QbeTypeOf(FldAccess.FieldInfo.TypeDesc);
|
|
T := AllocTemp;
|
|
if QType = 'w' then LoadInstr := 'loadw'
|
|
else LoadInstr := 'loadl';
|
|
EmitLine(Format(' %s =%s %s %s', [T, QType, LoadInstr, Ptr]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
if FldAccess.IsConstructorCall then
|
|
begin
|
|
{ TypeName.Create — allocate zeroed instance on heap. _ClassAlloc
|
|
prefixes a 16-byte header (refcount + field-cleanup fn pointer)
|
|
before the returned pointer (see blaise_arc.c). The user pointer
|
|
still points at the vptr, so field offsets are unchanged. The
|
|
cleanup fn is invoked by _ClassRelease before free() when the
|
|
refcount reaches zero and is responsible for releasing any
|
|
ARC-managed fields. }
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =l call $_ClassAlloc(l %d, l $_FieldCleanup_%s)',
|
|
[T, TRecordTypeDesc(FldAccess.ResolvedType).TotalSize,
|
|
QBEMangle(FldAccess.ResolvedType.Name)]));
|
|
{ Store vtable pointer at offset 0 if this class has virtual methods }
|
|
if TRecordTypeDesc(FldAccess.ResolvedType).HasVTable then
|
|
EmitLine(Format(' storel $vtable_%s, %s',
|
|
[QBEMangle(FldAccess.ResolvedType.Name), T]));
|
|
Result := T;
|
|
end
|
|
else if FldAccess.PropRead <> nil then
|
|
begin
|
|
{ Method-backed property read: load Self pointer and call getter }
|
|
L := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [L, FldAccess.RecordName]));
|
|
T := AllocTemp;
|
|
QType := QbeTypeOf(FldAccess.PropRead.TypeDesc);
|
|
EmitLine(Format(' %s =%s call $%s_%s(l %s)',
|
|
[T, QType, QBEMangle(FldAccess.PropOwnerType),
|
|
FldAccess.PropRead.ReadMethod, L]));
|
|
Result := T;
|
|
end
|
|
else if FldAccess.IsClassAccess then
|
|
begin
|
|
{ Load heap pointer, then load field }
|
|
L := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [L, FldAccess.RecordName]));
|
|
if FldAccess.FieldInfo.Offset > 0 then
|
|
begin
|
|
Ptr := AllocTemp;
|
|
EmitLine(Format(' %s =l add %s, %d', [Ptr, L, FldAccess.FieldInfo.Offset]));
|
|
end
|
|
else
|
|
Ptr := L;
|
|
QType := QbeTypeOf(FldAccess.FieldInfo.TypeDesc);
|
|
T := AllocTemp;
|
|
if QType = 'w' then LoadInstr := 'loadw'
|
|
else LoadInstr := 'loadl';
|
|
EmitLine(Format(' %s =%s %s %s', [T, QType, LoadInstr, Ptr]));
|
|
Result := T;
|
|
end
|
|
else
|
|
begin
|
|
{ Record field access }
|
|
if FldAccess.FieldInfo = nil then
|
|
raise ECodeGenError.CreateFmt(
|
|
'Field access ''%s.%s'' has no resolved field info',
|
|
[FldAccess.RecordName, FldAccess.FieldName]);
|
|
Ptr := FieldPtr(FldAccess.RecordName, FldAccess.FieldInfo.Offset);
|
|
QType := QbeTypeOf(FldAccess.FieldInfo.TypeDesc);
|
|
T := AllocTemp;
|
|
if QType = 'w' then LoadInstr := 'loadw'
|
|
else LoadInstr := 'loadl';
|
|
EmitLine(Format(' %s =%s %s %s', [T, QType, LoadInstr, Ptr]));
|
|
Result := T;
|
|
end;
|
|
end
|
|
else if AExpr is TIdentExpr then
|
|
begin
|
|
T := AllocTemp;
|
|
if TIdentExpr(AExpr).IsNoArgFuncCall then
|
|
begin
|
|
{ Bare identifier resolving to a zero-arg function (no parens in source).
|
|
Synthesise a temporary TFuncCallExpr so existing builtin dispatch
|
|
handles it without duplicating logic. }
|
|
NoArgCall := TFuncCallExpr.Create;
|
|
try
|
|
NoArgCall.Name := TIdentExpr(AExpr).Name;
|
|
NoArgCall.ResolvedType := AExpr.ResolvedType;
|
|
Result := EmitExpr(NoArgCall);
|
|
finally
|
|
NoArgCall.Free;
|
|
end;
|
|
Exit;
|
|
end
|
|
else if TIdentExpr(AExpr).IsConstant then
|
|
begin
|
|
EmitLine(Format(' %s =w copy %d', [T, TIdentExpr(AExpr).ConstValue]));
|
|
end
|
|
else if TIdentExpr(AExpr).IsVarParam then
|
|
begin
|
|
{ Var param: load pointer, then dereference }
|
|
Ptr := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [Ptr, TIdentExpr(AExpr).Name]));
|
|
QType := QbeTypeOf(AExpr.ResolvedType);
|
|
if QType = 'l' then
|
|
EmitLine(Format(' %s =l loadl %s', [T, Ptr]))
|
|
else
|
|
EmitLine(Format(' %s =w loadw %s', [T, Ptr]));
|
|
end
|
|
else if (AExpr.ResolvedType <> nil) and (QbeTypeOf(AExpr.ResolvedType) = 'l') then
|
|
begin
|
|
EmitLine(Format(' %s =l loadl %%_var_%s', [T, TIdentExpr(AExpr).Name]));
|
|
end
|
|
else
|
|
begin
|
|
EmitLine(Format(' %s =w loadw %%_var_%s', [T, TIdentExpr(AExpr).Name]));
|
|
end;
|
|
Result := T;
|
|
end
|
|
else if AExpr is TBinaryExpr then
|
|
begin
|
|
BinExpr := TBinaryExpr(AExpr);
|
|
L := EmitExpr(BinExpr.Left);
|
|
R := EmitExpr(BinExpr.Right);
|
|
T := AllocTemp;
|
|
{ String concatenation: delegate to RTL }
|
|
if (BinExpr.Op = boAdd) and
|
|
(BinExpr.Left.ResolvedType <> nil) and
|
|
BinExpr.Left.ResolvedType.IsString then
|
|
begin
|
|
EmitLine(Format(' %s =l call $_StringConcat(l %s, l %s)', [T, L, R]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
{ Pointer arithmetic: Pointer +/- Integer — scale offset by sizeof(base) }
|
|
if (BinExpr.Op in [boAdd, boSub]) and
|
|
(BinExpr.Left.ResolvedType <> nil) and
|
|
(BinExpr.Left.ResolvedType.Kind = tyPointer) then
|
|
begin
|
|
{ Scale the integer offset to bytes. QBE pointer/int ops are both l. }
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l extsw %s', [ArgTemp, R]));
|
|
if BinExpr.Op = boAdd then
|
|
EmitLine(Format(' %s =l add %s, %s', [T, L, ArgTemp]))
|
|
else
|
|
EmitLine(Format(' %s =l sub %s, %s', [T, L, ArgTemp]));
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
{ String equality/inequality: content comparison via RTL helper }
|
|
if (BinExpr.Left.ResolvedType <> nil) and
|
|
BinExpr.Left.ResolvedType.IsString and
|
|
(BinExpr.Op in [boEQ, boNE]) then
|
|
begin
|
|
EmitLine(Format(' %s =w call $_StringEquals(l %s, l %s)', [T, L, R]));
|
|
if BinExpr.Op = boNE then
|
|
begin
|
|
ArgTemp := AllocTemp;
|
|
EmitLine(Format(' %s =w ceqw %s, 0', [ArgTemp, T]));
|
|
T := ArgTemp;
|
|
end;
|
|
Result := T;
|
|
Exit;
|
|
end;
|
|
{ Use long (pointer) comparison instructions when operands are class/nil }
|
|
if (BinExpr.Left.ResolvedType <> nil) and
|
|
(BinExpr.Left.ResolvedType.Kind in [tyClass, tyNil]) then
|
|
begin
|
|
case BinExpr.Op of
|
|
boEQ: Op := 'ceql';
|
|
boNE: Op := 'cnel';
|
|
else
|
|
Op := 'ceql';
|
|
end;
|
|
EmitLine(Format(' %s =w %s %s, %s', [T, Op, L, R]));
|
|
end
|
|
else
|
|
begin
|
|
case BinExpr.Op of
|
|
boAdd: Op := 'add';
|
|
boSub: Op := 'sub';
|
|
boMul: Op := 'mul';
|
|
boDiv: Op := 'div';
|
|
boEQ: Op := 'ceqw';
|
|
boNE: Op := 'cnew';
|
|
boLT: Op := 'csltw';
|
|
boGT: Op := 'csgtw';
|
|
boLE: Op := 'cslew';
|
|
boGE: Op := 'csgew';
|
|
boAnd: Op := 'and';
|
|
boOr: Op := 'or';
|
|
else
|
|
Op := 'add';
|
|
end;
|
|
EmitLine(Format(' %s =w %s %s, %s', [T, Op, L, R]));
|
|
end;
|
|
Result := T;
|
|
end
|
|
else if AExpr is TNotExpr then
|
|
begin
|
|
{ Logical not on Boolean (0/1): xor with 1 flips the low bit. }
|
|
L := EmitExpr(TNotExpr(AExpr).Expr);
|
|
T := AllocTemp;
|
|
EmitLine(Format(' %s =w xor %s, 1', [T, L]));
|
|
Result := T;
|
|
end
|
|
else if AExpr is TDerefExpr then
|
|
begin
|
|
{ P^ — load the value at the pointer address }
|
|
T := EmitExpr(TDerefExpr(AExpr).Expr);
|
|
QType := QbeTypeOf(AExpr.ResolvedType);
|
|
L := AllocTemp;
|
|
if QType = 'w' then
|
|
EmitLine(Format(' %s =w loadw %s', [L, T]))
|
|
else
|
|
EmitLine(Format(' %s =l loadl %s', [L, T]));
|
|
Result := L;
|
|
end
|
|
else if AExpr is TIsExpr then
|
|
Result := EmitIsExpr(TIsExpr(AExpr))
|
|
else if AExpr is TAsExpr then
|
|
Result := EmitAsExpr(TAsExpr(AExpr))
|
|
else
|
|
raise ECodeGenError.Create('Unknown expression node type');
|
|
end;
|
|
|
|
function TCodeGenQBE.EmitIsExpr(AExpr: TIsExpr): string;
|
|
var
|
|
ObjTemp: string;
|
|
ResTemp: string;
|
|
begin
|
|
ObjTemp := EmitExpr(AExpr.Obj);
|
|
ResTemp := AllocTemp;
|
|
if (AExpr.ResolvedTargetType <> nil) and
|
|
(AExpr.ResolvedTargetType.Kind = tyInterface) then
|
|
EmitLine(Format(' %s =w call $_ImplementsInterface(l %s, l $typeinfo_%s)',
|
|
[ResTemp, ObjTemp, AExpr.TypeName]))
|
|
else
|
|
EmitLine(Format(' %s =w call $_IsInstance(l %s, l $typeinfo_%s)',
|
|
[ResTemp, ObjTemp, AExpr.TypeName]));
|
|
Result := ResTemp;
|
|
end;
|
|
|
|
function TCodeGenQBE.EmitAsExpr(AExpr: TAsExpr): string;
|
|
var
|
|
ObjTemp: string;
|
|
OkTemp: string;
|
|
SlotTemp: string;
|
|
ResTemp: string;
|
|
LblOk: string;
|
|
LblFail: string;
|
|
LblEnd: string;
|
|
begin
|
|
ObjTemp := EmitExpr(AExpr.Obj);
|
|
SlotTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l alloc8 1', [SlotTemp]));
|
|
|
|
OkTemp := AllocTemp;
|
|
LblOk := AllocLabel('as_ok');
|
|
LblFail := AllocLabel('as_fail');
|
|
LblEnd := AllocLabel('as_end');
|
|
|
|
EmitLine(Format(' %s =w call $_IsInstance(l %s, l $typeinfo_%s)',
|
|
[OkTemp, ObjTemp, AExpr.TypeName]));
|
|
EmitLine(Format(' jnz %s, @%s, @%s', [OkTemp, LblOk, LblFail]));
|
|
|
|
EmitLine('@' + LblFail);
|
|
EmitLine(' call $_Raise_InvalidCast()');
|
|
EmitLine(Format(' storel 0, %s', [SlotTemp])); { unreachable; satisfies SSA }
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
|
|
EmitLine('@' + LblOk);
|
|
EmitLine(Format(' storel %s, %s', [ObjTemp, SlotTemp]));
|
|
EmitLine(Format(' jmp @%s', [LblEnd]));
|
|
|
|
EmitLine('@' + LblEnd);
|
|
ResTemp := AllocTemp;
|
|
EmitLine(Format(' %s =l loadl %s', [ResTemp, SlotTemp]));
|
|
Result := ResTemp;
|
|
end;
|
|
|
|
function TCodeGenQBE.QBEMangle(const AName: string): string;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := '';
|
|
for I := 1 to Length(AName) do
|
|
case AName[I] of
|
|
'<': Result := Result + '_';
|
|
'>': { skip — closing bracket dropped };
|
|
',': Result := Result + '_';
|
|
else
|
|
Result := Result + AName[I];
|
|
end;
|
|
end;
|
|
|
|
function TCodeGenQBE.QbeEscapeString(const AStr: string): string;
|
|
var
|
|
I: Integer;
|
|
C: Char;
|
|
begin
|
|
Result := '';
|
|
for I := 1 to Length(AStr) do
|
|
begin
|
|
C := AStr[I];
|
|
case C of
|
|
'"': Result := Result + '\"';
|
|
'\': Result := Result + '\\';
|
|
#10: Result := Result + '\n';
|
|
#13: Result := Result + '\r';
|
|
#9: Result := Result + '\t';
|
|
else if (Ord(C) < 32) or (Ord(C) > 126) then
|
|
Result := Result + Format('\%02x', [Ord(C)])
|
|
else
|
|
Result := Result + C;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.Generate(AProg: TProgram);
|
|
var
|
|
Body: TStringList;
|
|
SavedOutput: TStringList;
|
|
begin
|
|
FOutput.Clear;
|
|
FStrLits.Clear;
|
|
FTempCount := 0;
|
|
FLabelCount := 0;
|
|
|
|
Body := TStringList.Create;
|
|
try
|
|
SavedOutput := FOutput;
|
|
FOutput := Body;
|
|
try
|
|
EmitFieldCleanupDefs(AProg);
|
|
EmitMethodDefs(AProg);
|
|
EmitStandaloneDefs(AProg);
|
|
FExitLabel := 'main_exit';
|
|
EmitMainHeader;
|
|
EmitBlock(AProg.Block);
|
|
EmitMainFooter;
|
|
FExitLabel := '';
|
|
finally
|
|
FOutput := SavedOutput;
|
|
end;
|
|
|
|
EmitLine('# Generated by Blaise Compiler');
|
|
EmitLine('# Source: ' + AProg.Name);
|
|
EmitLine('');
|
|
EmitDataSection;
|
|
EmitInterfaceDefs(AProg);
|
|
EmitTypeInfoDefs(AProg);
|
|
EmitVTableDefs(AProg);
|
|
FOutput.AddStrings(Body);
|
|
finally
|
|
Body.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCodeGenQBE.GenerateUnit(AUnit: TUnit);
|
|
var
|
|
I: Integer;
|
|
ImplDecl: TMethodDecl;
|
|
IntfNames: TStringList;
|
|
Body: TStringList;
|
|
SavedOut: TStringList;
|
|
begin
|
|
FOutput.Clear;
|
|
FStrLits.Clear;
|
|
FTempCount := 0;
|
|
FLabelCount := 0;
|
|
|
|
IntfNames := TStringList.Create;
|
|
try
|
|
IntfNames.CaseSensitive := False;
|
|
for I := 0 to AUnit.IntfBlock.ProcDecls.Count - 1 do
|
|
IntfNames.Add(TMethodDecl(AUnit.IntfBlock.ProcDecls[I]).Name);
|
|
|
|
Body := TStringList.Create;
|
|
try
|
|
SavedOut := FOutput;
|
|
FOutput := Body;
|
|
try
|
|
for I := 0 to AUnit.ImplBlock.ProcDecls.Count - 1 do
|
|
begin
|
|
ImplDecl := TMethodDecl(AUnit.ImplBlock.ProcDecls[I]);
|
|
EmitFuncDef(ImplDecl, IntfNames.IndexOf(ImplDecl.Name) >= 0);
|
|
end;
|
|
finally
|
|
FOutput := SavedOut;
|
|
end;
|
|
|
|
EmitLine('# Generated by Blaise Compiler');
|
|
EmitLine('# Unit: ' + AUnit.Name);
|
|
EmitLine('');
|
|
EmitDataSection;
|
|
FOutput.AddStrings(Body);
|
|
finally
|
|
Body.Free;
|
|
end;
|
|
finally
|
|
IntfNames.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCodeGenQBE.GetOutput: string;
|
|
begin
|
|
Result := FOutput.Text;
|
|
end;
|
|
|
|
end.
|