Refactor to generalise assembler output

This commit is contained in:
Curle 2023-04-23 17:32:02 +01:00
parent 39756f4e89
commit 216d6c6b5e
14 changed files with 1480 additions and 1007 deletions

View File

@ -8,7 +8,9 @@ include_directories(include)
add_executable(Erythro add_executable(Erythro
include/Data.h include/Data.h
include/Defs.h include/Defs.h
src/Assembler.c src/assemble/AssemblerDispatcher.c
src/assemble/ASMAssembler.c
src/assemble/QBEAssembler.c
src/Delegate.c src/Delegate.c
src/Dump.c src/Dump.c
src/Lexer.c src/Lexer.c
@ -18,4 +20,5 @@ add_executable(Erythro
src/Statements.c src/Statements.c
src/Symbols.c src/Symbols.c
src/Types.c src/Types.c
src/Importer.c) src/Importer.c
src/assemble/JVMAssembler.c)

View File

@ -48,6 +48,10 @@ extern_ bool OptVerboseOutput;
extern_ char* OutputFileName; extern_ char* OutputFileName;
// The sizes of each of the core types, in bytes. // The sizes of each of the core types, in bytes.
extern_ int TypeSizes[5]; extern_ int TypeSizes[5];
// The name of the Assembler Module that we should use.
extern_ char* OptAssemblerName;
// The Assembler Module to call to when performing generation operations.
extern_ struct AssemblerModule* Assembler;
// The names of each token in the language, synchronized to the TokenTypes enum. // The names of each token in the language, synchronized to the TokenTypes enum.
extern_ char* TokenNames[]; extern_ char* TokenNames[];

View File

@ -513,113 +513,96 @@ void DieBinary(char* Error, int Number);
* * * * C O D E G E N E R A T I O N * * * * * * * * C O D E G E N E R A T I O N * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel, int LoopEndLabel, int ParentOp); int PrimitiveSize (int);
void DeallocateAllRegisters(); /**
* All of the functions required to be implemented by an Assembler Module.
*/
struct AssemblerVtable {
// Entry Point
int (*AssembleTree)(struct ASTNode*, int, int, int, int);
// Register management
void (*DeallocateAllRegisters)();
int (*RetrieveRegister)();
void (*DeallocateRegister)(int);
// Alignment
int (*AsAlignMemory)(int, int, int);
int (*AsCalcOffset)(int);
void (*AsNewStackFrame)();
// Basic operations
int (*AsLoad)(int);
int (*AsAdd)(int, int);
int (*AsMul)(int, int);
int (*AsSub)(int, int);
int (*AsDiv)(int, int);
int (*AsLdGlobalVar)(struct SymbolTableEntry*, int);
int (*AsLdLocalVar)(struct SymbolTableEntry*, int);
int (*AsStrGlobalVar)(struct SymbolTableEntry*, int);
int (*AsStrLocalVar)(struct SymbolTableEntry*, int);
int (*AsDeref)(int, int);
int (*AsStrDeref)(int, int, int);
int (*AsAddr)(struct SymbolTableEntry*);
int (*AsNewString)(char*);
int (*AsLoadString)(int);
int RetrieveRegister(); // Comparisons
int (*AsEqual)(int, int);
int (*AsIneq)(int, int);
int (*AsLess)(int, int);
int (*AsGreat)(int, int);
int (*AsLessE)(int, int);
int (*AsGreatE)(int, int);
void DeallocateRegister(int Register); // Binary operations
int (*AsBitwiseAND)(int, int);
int (*AsBitwiseOR)(int, int);
int (*AsBitwiseXOR)(int, int);
int (*AsNegate)(int);
int (*AsInvert)(int);
int (*AsBooleanNOT)(int);
int (*AsShiftLeft)(int, int);
int (*AsShiftRight)(int, int);
int PrimitiveSize(int Type); // Comparisons
int (*AsBooleanConvert)(int, int, int);
int (*AsCompareJmp)(int, int, int, int);
int (*AsCompare)(int, int, int);
int AsAlignMemory(int Type, int Offset, int Direction); // Loops and jumps
int (*AsIf)(struct ASTNode*, int, int);
int (*AsWhile)(struct ASTNode*);
int (*NewLabel)();
void (*AsJmp)(int);
void (*AsLabel)(int);
int AsLoad(int Value); // Call and return
int (*AsShl)(int, int);
int (*AsReturn)(struct SymbolTableEntry*, int);
int (*AsCallWrapper)(struct ASTNode*);
void (*AsCopyArgs)(int, int);
int (*AsCall)(struct SymbolTableEntry*, int);
void (*AssemblerPrint)(int);
int AsAdd(int Left, int Right); // Preamble and epilogue
void (*AsGlobalSymbol)(struct SymbolTableEntry*);
void (*AssemblerPreamble)();
void (*AsFunctionPreamble)(struct SymbolTableEntry*);
void (*AsFunctionEpilogue)(struct SymbolTableEntry*);
};
int AsMul(int Left, int Right); struct AssemblerModule{
char* name;
const struct AssemblerVtable* vtable;
};
int AsSub(int Left, int Right); int RegisterModule(struct AssemblerModule*);
int AsDiv(int Left, int Right); void RegisterAllModules();
int AsLdGlobalVar(struct SymbolTableEntry* Entry, int Operation); // Module List
void RegisterQBE();
int AsLdLocalVar(struct SymbolTableEntry* Entry, int Operation); void RegisterWin32ASM();
void RegisterJVM();
int AsStrGlobalVar(struct SymbolTableEntry* Entry, int Register);
int AsStrLocalVar(struct SymbolTableEntry* Entry, int Register);
int AsCalcOffset(int Type);
void AsNewStackFrame();
int AsDeref(int Reg, int Type);
int AsStrDeref(int Register1, int Register2, int Type);
int AsAddr(struct SymbolTableEntry* Entry);
void AsGlobalSymbol(struct SymbolTableEntry* Entry);
int AsNewString(char* Value);
int AsLoadString(int ID);
int AsEqual(int Left, int Right);
int AsIneq(int Left, int Right);
int AsLess(int Left, int Right);
int AsGreat(int Left, int Right);
int AsLessE(int Left, int Right);
int AsGreatE(int Left, int Right);
int AsBitwiseAND(int Left, int Right);
int AsBitwiseOR(int Left, int Right);
int AsBitwiseXOR(int Left, int Right);
int AsNegate(int Register);
int AsInvert(int Register);
int AsBooleanNOT(int Register);
int AsShiftLeft(int Left, int Right);
int AsShiftRight(int Left, int Right);
int AsBooleanConvert(int Register, int Operation, int Label);
int AsCompareJmp(int Operation, int RegisterLeft, int RegisterRight, int Label);
int AsCompare(int Operation, int RegisterLeft, int RegisterRight);
int AsIf(struct ASTNode* Node, int LoopStartLabel, int LoopEndLabel);
int NewLabel(void);
void AsJmp(int Label);
void AsLabel(int Label);
int AsShl(int Register, int Val);
int AsReturn(struct SymbolTableEntry* Entry, int Register);
int AsCallWrapper(struct ASTNode* Node);
void AsCopyArgs(int Register, int Position);
int AsCall(struct SymbolTableEntry* Entry, int Args);
int AsWhile(struct ASTNode* Node);
void AssemblerPrint(int Register);
void AssemblerPreamble();
void AsFunctionPreamble(struct SymbolTableEntry* Entry);
void AsFunctionEpilogue(struct SymbolTableEntry* Entry);
/* * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * *

View File

@ -95,7 +95,7 @@ void Compile(struct FileData* InputFile) {
Tokenise(); Tokenise();
AssemblerPreamble(); Assembler->vtable->AssemblerPreamble();
ParseGlobals(); ParseGlobals();

View File

@ -107,6 +107,7 @@ int main(int argc, char* argv[]) {
OptAssembleFiles = true; OptAssembleFiles = true;
OptLinkFiles = true; OptLinkFiles = true;
OptVerboseOutput = false; OptVerboseOutput = false;
OptAssemblerName = "Win32 GAS ASM";
// Temporary .o storage and counter // Temporary .o storage and counter
int ObjectCount = 0; int ObjectCount = 0;
@ -126,24 +127,32 @@ int main(int argc, char* argv[]) {
switch (argv[i][j]) { switch (argv[i][j]) {
case 'o': // output case 'o': // output
OutputFileName = argv[++i]; OutputFileName = argv[++i];
break; break;
case 'T': // print Tree (debug) case 'T': // print Tree (debug)
OptDumpTree = true; OptDumpTree = true;
break; break;
case 'c': // Compile only case 'c': // Compile only
OptAssembleFiles = true; OptAssembleFiles = true;
OptKeepAssembly = false; OptKeepAssembly = false;
OptLinkFiles = false; OptLinkFiles = false;
break; break;
case 'S': // aSsemble only case 'S': // aSsemble only
OptAssembleFiles = true; OptAssembleFiles = true;
OptKeepAssembly = true; OptKeepAssembly = true;
OptLinkFiles = false; OptLinkFiles = false;
break; break;
case 'v': // Verbose output case 'v': // Verbose output
OptVerboseOutput = true; OptVerboseOutput = true;
break; break;
case 'm': // Specify Assembler Module
OptAssemblerName = argv[++i];
break;
default: default:
DisplayUsage(argv[0]); DisplayUsage(argv[0]);
} }
@ -159,6 +168,7 @@ int main(int argc, char* argv[]) {
Files = malloc(sizeof(struct FileData*) * (argc - i) + 1); Files = malloc(sizeof(struct FileData*) * (argc - i) + 1);
memset(Files, 0, sizeof(struct FileData*) * (argc - i) + 1); memset(Files, 0, sizeof(struct FileData*) * (argc - i) + 1);
RegisterAllModules();
// For the rest of the files specified, we can iterate them right to left. // For the rest of the files specified, we can iterate them right to left.
while (i < argc) { while (i < argc) {

View File

@ -241,7 +241,7 @@ struct ASTNode* ParsePrimary(void) {
case LI_STR: case LI_STR:
ID = AsNewString(CurrentIdentifier); ID = Assembler->vtable->AsNewString(CurrentIdentifier);
Node = ConstructASTLeaf(TERM_STRLITERAL, PointerTo(RET_CHAR), NULL, ID); Node = ConstructASTLeaf(TERM_STRLITERAL, PointerTo(RET_CHAR), NULL, ID);
break; break;
@ -621,7 +621,7 @@ void ParseGlobals() {
Tree = ParseFunction(Type); Tree = ParseFunction(Type);
if (Tree && CurrentFile->AllowDefinitions) { if (Tree && CurrentFile->AllowDefinitions) {
printf("\nBeginning assembler creation of new function %s\n", Tree->Symbol->Name); printf("\nBeginning assembler creation of new function %s\n", Tree->Symbol->Name);
AssembleTree(Tree, -1, -1, -1, 0); Assembler->vtable->AssembleTree(Tree, -1, -1, -1, 0);
FreeLocals(); FreeLocals();
} else { } else {
printf("\nFunction prototype saved\r\n"); printf("\nFunction prototype saved\r\n");

View File

@ -110,7 +110,7 @@ struct SymbolTableEntry* BeginCompositeDeclaration(int Type) {
for (Member = Member->NextSymbol; Member != NULL; Member = Member->NextSymbol) { for (Member = Member->NextSymbol; Member != NULL; Member = Member->NextSymbol) {
if (Type == DAT_STRUCT) if (Type == DAT_STRUCT)
Member->SinkOffset = AsAlignMemory(Member->Type, Offset, 1); Member->SinkOffset = Assembler->vtable->AsAlignMemory(Member->Type, Offset, 1);
else else
Member->SinkOffset = 0; Member->SinkOffset = 0;

View File

@ -257,7 +257,7 @@ struct SymbolTableEntry* AddSymbol(char* Name, int Type, int Structure, int Stor
case SC_GLOBAL: case SC_GLOBAL:
AppendSymbol(&Globals, &GlobalsEnd, Node); AppendSymbol(&Globals, &GlobalsEnd, Node);
// We don't want to generate a static block for functions. // We don't want to generate a static block for functions.
if (Structure != ST_FUNC) AsGlobalSymbol(Node); if (Structure != ST_FUNC) Assembler->vtable->AsGlobalSymbol(Node);
break; break;
case SC_STRUCT: case SC_STRUCT:
AppendSymbol(&Structs, &StructsEnd, Node); AppendSymbol(&Structs, &StructsEnd, Node);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
/*************/
/*GEMWIRE */
/* ERYTHRO*/
/*************/
#include <Defs.h>
#include <Data.h>
#define MODULES_SIZE 3
struct AssemblerModule* modules[MODULES_SIZE];
static int moduleID = 0;
void RegisterAllModules() {
RegisterWin32ASM();
RegisterQBE();
RegisterJVM();
for (size_t i = 0; i < moduleID; i++) {
if (strcmp(modules[i]->name, OptAssemblerName) == 0) {
Assembler = modules[i];
break;
}
}
if (Assembler == NULL) {
DieMessage("Unable to find an Assembler for ", OptAssemblerName);
} else {
printf("Using %s assembler.\r\n", OptAssemblerName);
}
}
int RegisterModule(struct AssemblerModule* mod) {
printf("Registering Assembler Module for %s output.\r\n", mod->name);
modules[moduleID] = mod;
moduleID++;
return 0;
}

180
src/assemble/JVMAssembler.c Normal file
View File

@ -0,0 +1,180 @@
/*************/
/*GEMWIRE */
/* ERYTHRO*/
/*************/
#include "Defs.h"
#include "Data.h"
#define AGGREGATE(x) ":##x"
#define GLOBAL(x) "$##x"
#define TEMPORARY(x) "%##x"
#define LABEL(x) "@##x"
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * R O O T O F A S S E M B L E R * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Just a short "hack" to make sure we only dump the tree the first time this function is called
static int Started = 0;
/*
* Walk the AST tree given, and generate the assembly code that represents
* it.
*
* @param Node: The current Node to compile. If needed, its children will be parsed recursively.
* @param Register: The index of Registers to store the result of the current compilation.
* @param ParentOp: The Operation of the parent of the current Node.
*
* @return dependant on the Node. Typically the Register that stores the result of the Node's operation.
*
*/
static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel, int LoopEndLabel, int ParentOp) {
int LeftVal, RightVal;
if (!Started && OptDumpTree)
DumpTree(Node, 0);
Started = 1;
printf("Current operation: %d\r\n", Node->Operation);
switch (Node->Operation) {
case OP_IF:
case OP_LOOP:
case OP_COMP:
case OP_CALL:
case OP_FUNC:
break;
}
if (Node->Left)
LeftVal = AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation);
if (Node->Right)
RightVal = AssembleTree(Node->Right, LeftVal, LoopBeginLabel, LoopEndLabel, Node->Operation);
switch (Node->Operation) {
case OP_ADD:
case OP_SUBTRACT:
case OP_MULTIPLY:
case OP_DIVIDE:
case OP_SCALE:
switch (Node->Size) {
case 2:
case 4:
case 8:
default:
break;
}
case OP_BREAK:
case OP_CONTINUE:
case OP_ADDRESS:
case OP_DEREF:
case OP_ASSIGN:
printf("Preparing for assignment..\r\n");
if (Node->Right == NULL)
Die("Fault in assigning a null rvalue");
printf("\tCalculating assignment for target %s:\r\n", Node->Right->Symbol->Name);
switch (Node->Right->Operation) {
case REF_IDENT:
if (Node->Right->Symbol->Storage == SC_LOCAL) (void)0;
case OP_DEREF:
default:
DieDecimal("Can't ASSIGN in AssembleTree: ", Node->Operation);
}; return 0;
case OP_WIDEN:
case OP_RET:
case OP_EQUAL:
case OP_INEQ:
case OP_LESS:
case OP_GREAT:
case OP_LESSE:
case OP_GREATE:
if (ParentOp == OP_IF || ParentOp == OP_LOOP) (void)0;
case REF_IDENT:
if (TypeIsPtr(Node->ExprType)) {
}
if (Node->RVal || ParentOp == OP_DEREF) {
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
} else
return -1;
case TERM_INTLITERAL:
case TERM_STRLITERAL:
case OP_PRINT:
case OP_BITAND:
case OP_BITOR:
case OP_BITXOR:
case OP_SHIFTL:
case OP_SHIFTR:
case OP_POSTINC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_POSTDEC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_PREINC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_PREDEC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_BOOLNOT:
case OP_BITNOT:
case OP_NEGATE:
case OP_BOOLCONV:
return 0;
default:
DieDecimal("Unknown ASM Operation", Node->Operation);
return 0;
}
}
static const struct AssemblerVtable JVMAssemblerVtable = {
.AssembleTree = AssembleTree
};
static const struct AssemblerModule JVMAssemblerModule = {
.name = "JVM Bytecode",
.vtable = &JVMAssemblerVtable
};
void RegisterJVM() {
RegisterModule(&JVMAssemblerModule);
}

180
src/assemble/QBEAssembler.c Normal file
View File

@ -0,0 +1,180 @@
/*************/
/*GEMWIRE */
/* ERYTHRO*/
/*************/
#include "Defs.h"
#include "Data.h"
#define AGGREGATE(x) ":##x"
#define GLOBAL(x) "$##x"
#define TEMPORARY(x) "%##x"
#define LABEL(x) "@##x"
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * R O O T O F A S S E M B L E R * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Just a short "hack" to make sure we only dump the tree the first time this function is called
static int Started = 0;
/*
* Walk the AST tree given, and generate the assembly code that represents
* it.
*
* @param Node: The current Node to compile. If needed, its children will be parsed recursively.
* @param Register: The index of Registers to store the result of the current compilation.
* @param ParentOp: The Operation of the parent of the current Node.
*
* @return dependant on the Node. Typically the Register that stores the result of the Node's operation.
*
*/
static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel, int LoopEndLabel, int ParentOp) {
int LeftVal, RightVal;
if (!Started && OptDumpTree)
DumpTree(Node, 0);
Started = 1;
printf("Current operation: %d\r\n", Node->Operation);
switch (Node->Operation) {
case OP_IF:
case OP_LOOP:
case OP_COMP:
case OP_CALL:
case OP_FUNC:
break;
}
if (Node->Left)
LeftVal = AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation);
if (Node->Right)
RightVal = AssembleTree(Node->Right, LeftVal, LoopBeginLabel, LoopEndLabel, Node->Operation);
switch (Node->Operation) {
case OP_ADD:
case OP_SUBTRACT:
case OP_MULTIPLY:
case OP_DIVIDE:
case OP_SCALE:
switch (Node->Size) {
case 2:
case 4:
case 8:
default:
break;
}
case OP_BREAK:
case OP_CONTINUE:
case OP_ADDRESS:
case OP_DEREF:
case OP_ASSIGN:
printf("Preparing for assignment..\r\n");
if (Node->Right == NULL)
Die("Fault in assigning a null rvalue");
printf("\tCalculating assignment for target %s:\r\n", Node->Right->Symbol->Name);
switch (Node->Right->Operation) {
case REF_IDENT:
if (Node->Right->Symbol->Storage == SC_LOCAL) (void)0;
case OP_DEREF:
default:
DieDecimal("Can't ASSIGN in AssembleTree: ", Node->Operation);
}; return 0;
case OP_WIDEN:
case OP_RET:
case OP_EQUAL:
case OP_INEQ:
case OP_LESS:
case OP_GREAT:
case OP_LESSE:
case OP_GREATE:
if (ParentOp == OP_IF || ParentOp == OP_LOOP) (void)0;
case REF_IDENT:
if (TypeIsPtr(Node->ExprType)) {
}
if (Node->RVal || ParentOp == OP_DEREF) {
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
} else
return -1;
case TERM_INTLITERAL:
case TERM_STRLITERAL:
case OP_PRINT:
case OP_BITAND:
case OP_BITOR:
case OP_BITXOR:
case OP_SHIFTL:
case OP_SHIFTR:
case OP_POSTINC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_POSTDEC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_PREINC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_PREDEC:
if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) (void)0;
case OP_BOOLNOT:
case OP_BITNOT:
case OP_NEGATE:
case OP_BOOLCONV:
return 0;
default:
DieDecimal("Unknown ASM Operation", Node->Operation);
return 0;
}
}
static const struct AssemblerVtable QBEAssemblerVtable = {
.AssembleTree = AssembleTree
};
static const struct AssemblerModule QBEAssemblerModule = {
.name = "QBE",
.vtable = &QBEAssemblerVtable
};
void RegisterQBE() {
RegisterModule(&QBEAssemblerModule);
}

View File

@ -1,4 +1,4 @@
int :: printf(char* format); import "print.em"
int :: main () { int :: main () {
printf("%s\r\n", "hi there"); printf("%s\r\n", "hi there");

5
tests/test.java Normal file
View File

@ -0,0 +1,5 @@
class Test {
public static void main(String[] args) {
java.lang.System.out.println("hi");
}
}