blaise/compiler/src/main/pascal/uCodeGenQBE.pas
Graeme Geldenhuys 4c6c3d9b2c Add unit interface/implementation: lexer, AST, parser, semantic, codegen
TUnit AST node with IntfBlock/ImplBlock; ParseUnit and ParseForwardDecl;
AnalyseUnit with signature verification and missing-impl detection;
GenerateUnit with export prefix for interface-declared functions.
2026-04-20 23:10:45 +01:00

1149 lines
32 KiB
ObjectPascal

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;
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 EmitMethodDefs(AProg: TProgram);
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 EmitStringCleanup(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 EmitProcCall(ACall: TProcCall);
procedure EmitWrite(ACall: TProcCall; ANewline: Boolean);
function EmitExpr(AExpr: TASTExpr): string;
function FieldPtr(const ARecordVar: string; AOffset: Integer): string;
function QbeTypeOf(AType: TTypeDesc): string;
function QbeEscapeString(const AStr: 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;
FTempCount := 0;
end;
destructor TCodeGenQBE.Destroy;
begin
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;
begin
if FStrLits.Count = 0 then
Exit;
EmitLine('# String literals');
for I := 0 to FStrLits.Count - 1 do
EmitLine(Format('data $__s%d = { b "%s", b 0 }',
[I, QbeEscapeString(FStrLits[I])]));
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() {');
EmitLine('@start');
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: Result := 'w';
tyInt64, tyString: Result := 'l';
tyRecord: Result := 'l'; { pointer to aggregate }
tyClass: Result := 'l'; { heap pointer }
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:
EmitLine(Format(' %%_var_%s =l alloc4 1', [VarName]));
tyInt64:
EmitLine(Format(' %%_var_%s =l alloc8 1', [VarName]));
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]));
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;
else
raise ECodeGenError.CreateFmt(
'Unsupported type kind %d for variable ''%s''',
[Ord(Decl.ResolvedType.Kind), VarName]);
end;
end;
end;
end;
procedure TCodeGenQBE.EmitStringCleanup(ABlock: TBlock);
var
I, J: Integer;
Decl: TVarDecl;
VarName: string;
ValTemp: string;
begin
for I := 0 to ABlock.Decls.Count - 1 do
begin
Decl := TVarDecl(ABlock.Decls[I]);
if (Decl.ResolvedType <> nil) and Decl.ResolvedType.IsString then
for J := 0 to Decl.Names.Count - 1 do
begin
VarName := Decl.Names[J];
ValTemp := AllocTemp;
EmitLine(Format(' %s =l loadl %%_var_%s', [ValTemp, VarName]));
EmitLine(Format(' call $_StringRelease(l %s)', [ValTemp]));
end;
end;
end;
procedure TCodeGenQBE.EmitBlock(ABlock: TBlock);
var
I: Integer;
begin
EmitVarAllocs(ABlock);
for I := 0 to ABlock.Stmts.Count - 1 do
EmitStmt(TASTStmt(ABlock.Stmts[I]));
EmitStringCleanup(ABlock);
end;
procedure TCodeGenQBE.EmitStmt(AStmt: TASTStmt);
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 TAssignment then
EmitAssignment(TAssignment(AStmt))
else if AStmt is TMethodCallStmt then
EmitMethodCall(TMethodCallStmt(AStmt))
else if AStmt is TProcCall then
EmitProcCall(TProcCall(AStmt))
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
I: Integer;
begin
{ Simplified model: emit try body then finally body sequentially.
Full stack-unwinding requires RTL support (future phase). }
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
for I := 0 to AStmt.FinallyBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.FinallyBody.Stmts[I]));
end;
procedure TCodeGenQBE.EmitTryExceptStmt(AStmt: TTryExceptStmt);
var
LblExcept: string;
LblEnd: string;
I: Integer;
begin
LblExcept := AllocLabel('except_handler');
LblEnd := AllocLabel('except_end');
{ Emit try body — exception dispatch requires RTL support (future phase) }
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
{ Skip except handler in normal execution }
EmitLine(Format(' jmp @%s', [LblEnd]));
{ Except handler block — reachable only via raise dispatch (future) }
EmitLine('@' + LblExcept);
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);
EmitStmt(AStmt.Body);
{ 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);
EmitStmt(AStmt.Body);
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: string;
begin
if AAssign.Expr.ResolvedType = nil then
raise ECodeGenError.CreateFmt(
'Expression in assignment to ''%s'' has no resolved type', [AAssign.Name]);
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
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.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, QType, StoreInstr: string;
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);
QType := QbeTypeOf(AAssign.FieldInfo.TypeDesc);
if QType = 'w' then StoreInstr := 'storew'
else StoreInstr := 'storel';
EmitLine(Format(' %s %s, %s', [StoreInstr, ValTemp, Ptr]));
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;
begin
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]));
{ Use OwnerTypeName so inherited methods resolve to the defining class }
if MDecl.OwnerTypeName <> '' then
FuncName := '$' + MDecl.OwnerTypeName + '_' + ACall.Name
else
FuncName := '$' + RT.Name + '_' + ACall.Name;
{ 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;
EmitLine(Format(' call %s(%s)', [FuncName, 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]);
case Par.ResolvedType.Kind of
tyInteger, tyUInt32, tyBoolean, tyByte:
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;
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]);
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);
{ 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;
EmitBlock(AMethod.Body);
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.EmitMethodDefs(AProg: TProgram);
var
I, J: Integer;
TD: TTypeDecl;
CD: TClassTypeDef;
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
EmitMethodDef(TD.Name, TMethodDecl(CD.Methods[J]));
end;
end;
procedure TCodeGenQBE.EmitFuncDef(ADecl: TMethodDecl; AExported: Boolean);
var
Sig: string;
I: Integer;
Par: TMethodParam;
FuncName: string;
IsFunc: Boolean;
RetQType: string;
RetTemp: string;
Prefix: string;
begin
FuncName := '$' + 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 + ', ';
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]);
case Par.ResolvedType.Kind of
tyInteger, tyUInt32, tyBoolean, tyByte:
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;
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;
EmitBlock(ADecl.Body);
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;
begin
for I := 0 to AProg.Block.ProcDecls.Count - 1 do
EmitStandaloneDef(TMethodDecl(AProg.Block.ProcDecls[I]));
end;
procedure TCodeGenQBE.EmitProcCall(ACall: TProcCall);
var
UCaseName: string;
MDecl: TMethodDecl;
Par: TMethodParam;
ArgTemp: 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]);
ArgTemp := EmitExpr(TASTExpr(ACall.Args[I]));
if ArgLine <> '' then ArgLine := ArgLine + ', ';
ArgLine := ArgLine + Format('%s %s', [QbeTypeOf(Par.ResolvedType), ArgTemp]);
end;
EmitLine(Format(' call $%s(%s)', [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
raise ECodeGenError.CreateFmt(
'Unknown procedure ''%s'' at line %d', [ACall.Name, ACall.Line]);
end;
procedure TCodeGenQBE.EmitWrite(ACall: TProcCall; ANewline: Boolean);
var
ArgExpr: TASTExpr;
ArgTemp: string;
FmtLabel: string;
IsString: Boolean;
begin
if ACall.Args.Count = 0 then
begin
if ANewline then
EmitLine(' call $printf(l $__fmt_nl)');
Exit;
end;
if ACall.Args.Count > 1 then
raise ECodeGenError.CreateFmt(
'Write/WriteLn takes at most 1 argument (line %d)', [ACall.Line]);
ArgExpr := TASTExpr(ACall.Args[0]);
IsString := (ArgExpr.ResolvedType <> nil) and ArgExpr.ResolvedType.IsString;
ArgTemp := EmitExpr(ArgExpr);
if IsString then
FmtLabel := IfThen(ANewline, '$__fmt_s_nl', '$__fmt_s')
else
FmtLabel := IfThen(ANewline, '$__fmt_d_nl', '$__fmt_d');
if IsString then
EmitLine(Format(' call $printf(l %s, ..., l %s)', [FmtLabel, ArgTemp]))
else
EmitLine(Format(' call $printf(l %s, ..., w %s)', [FmtLabel, ArgTemp]));
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;
begin
if AExpr is TFuncCallExpr then
begin
{ Standalone function call expression }
with TFuncCallExpr(AExpr) do
begin
MDecl := TMethodDecl(ResolvedDecl);
QType := QbeTypeOf(MDecl.ResolvedReturnType);
FuncName := '$' + 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);
RT := TRecordTypeDesc(MCallExpr.ResolvedClassType);
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]);
ArgTemp := EmitExpr(TASTExpr(MCallExpr.Args[I]));
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;
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);
if FldAccess.IsConstructorCall then
begin
{ TypeName.Create — allocate instance on heap }
T := AllocTemp;
EmitLine(Format(' %s =l call $malloc(l %d)',
[T, TRecordTypeDesc(FldAccess.ResolvedType).TotalSize]));
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 (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;
{ 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';
else
Op := 'add';
end;
EmitLine(Format(' %s =w %s %s, %s', [T, Op, L, R]));
end;
Result := T;
end
else
raise ECodeGenError.Create('Unknown expression node type');
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
EmitMethodDefs(AProg);
EmitStandaloneDefs(AProg);
EmitMainHeader;
EmitBlock(AProg.Block);
EmitMainFooter;
finally
FOutput := SavedOutput;
end;
EmitLine('# Generated by Blaise Compiler (Phase 2)');
EmitLine('# Source: ' + AProg.Name);
EmitLine('');
EmitDataSection;
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 (Phase 2)');
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.