From 29b797d7dca09046bd6285b4535c9656f861f1ff Mon Sep 17 00:00:00 2001 From: Curle Date: Wed, 6 Dec 2023 19:01:20 +0000 Subject: [PATCH] Refactor codegen, allow compiling for linux --- CMakeLists.txt | 6 +- include/Defs.h | 1 + src/Main.c | 6 +- src/Parser.c | 4 + src/assemble/AssemblerDispatcher.c | 3 +- src/assemble/JVMAssembler.c | 2 +- src/assemble/LinuxGASAssembler.c | 1335 +++++++++++++++++ .../{ASMAssembler.c => Win32GASAssembler.c} | 8 +- 8 files changed, 1356 insertions(+), 9 deletions(-) create mode 100644 src/assemble/LinuxGASAssembler.c rename src/assemble/{ASMAssembler.c => Win32GASAssembler.c} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa97b12..8b1cb92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,8 @@ add_executable(Erythro include/Data.h include/Defs.h src/assemble/AssemblerDispatcher.c - src/assemble/ASMAssembler.c + src/assemble/Win32GASAssembler.c + src/assemble/LinuxGASAssembler.c src/assemble/QBEAssembler.c src/Delegate.c src/Dump.c @@ -21,4 +22,5 @@ add_executable(Erythro src/Symbols.c src/Types.c src/Importer.c - src/assemble/JVMAssembler.c src/Errors.c) + src/assemble/JVMAssembler.c + src/Errors.c) diff --git a/include/Defs.h b/include/Defs.h index e835bfb..56fd0e8 100644 --- a/include/Defs.h +++ b/include/Defs.h @@ -609,6 +609,7 @@ void RegisterAllModules(); // Module List void RegisterQBE(); void RegisterWin32ASM(); +void RegisterLinuxASM(); void RegisterJVM(); diff --git a/src/Main.c b/src/Main.c index 5bdedcc..d5de1c4 100644 --- a/src/Main.c +++ b/src/Main.c @@ -171,7 +171,11 @@ int main(int argc, char* argv[]) { OptAssembleFiles = true; OptLinkFiles = true; OptVerboseOutput = false; - OptAssemblerName = "Win32 GAS ASM"; + OptAssemblerName = "Win32"; + + struct FileData* InitData = malloc(sizeof(struct FileData)); + InitData->CurrentLine = 0; + CurrentFile = InitData; // Temporary .o storage and counter int ObjectCount = 0; diff --git a/src/Parser.c b/src/Parser.c index 802d770..b94033b 100644 --- a/src/Parser.c +++ b/src/Parser.c @@ -9,6 +9,10 @@ #include "Defs.h" #include "Data.h" +#if defined(__GNUC__) || defined(APPLE) +#include +#endif + /* * The Precedence of an operator is directly related to Token Type. * Precedence determines how soon the operator and its surrounding values diff --git a/src/assemble/AssemblerDispatcher.c b/src/assemble/AssemblerDispatcher.c index c2e6ee1..eb6a009 100644 --- a/src/assemble/AssemblerDispatcher.c +++ b/src/assemble/AssemblerDispatcher.c @@ -6,12 +6,13 @@ #include #include -#define MODULES_SIZE 3 +#define MODULES_SIZE 4 struct AssemblerModule* modules[MODULES_SIZE]; static int moduleID = 0; void RegisterAllModules() { RegisterWin32ASM(); + RegisterLinuxASM(); RegisterQBE(); RegisterJVM(); diff --git a/src/assemble/JVMAssembler.c b/src/assemble/JVMAssembler.c index c789c9e..664cf42 100644 --- a/src/assemble/JVMAssembler.c +++ b/src/assemble/JVMAssembler.c @@ -170,7 +170,7 @@ static const struct AssemblerVtable JVMAssemblerVtable = { }; static const struct AssemblerModule JVMAssemblerModule = { - .name = "JVM Bytecode", + .name = "JVM", .vtable = &JVMAssemblerVtable }; diff --git a/src/assemble/LinuxGASAssembler.c b/src/assemble/LinuxGASAssembler.c new file mode 100644 index 0000000..8983aee --- /dev/null +++ b/src/assemble/LinuxGASAssembler.c @@ -0,0 +1,1335 @@ +/*************/ +/*GEMWIRE */ +/* ERYTHRO*/ +/*************/ + +#include "Defs.h" +#include "Data.h" + +static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel, int LoopEndLabel, int ParentOp); + +/* + * Stores how many hardware registers are being used at any one time. + * It is empirically proven that only 4 clobber registers are + * needed for any arbitrary length program. + * + * If UsedRegisters[i] =? 1, then Registers[i] contains useful data. + * If UsedRegisters[i] =? 0, then Registers[i] is unused. + * + */ + +static int UsedRegisters[4]; + +/* The https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention + * calling convention on Windows requires that + * the last 4 arguments are placed in registers + * rcx, rdx, r8 and r9. + * This order must be preserved, and they must be placed + * right to left. + * + * The 4 clobber registers are first, and the 4 parameter registers are last. + */ +static char* Registers[10] = {"%r10", "%r11", "%r12", "%r13", "%r9", "%r8", "%rcx", "%rdx", "%rsi", "%rdi"}; +static char* DoubleRegisters[10] = {"%r10d", "%r11d", "%r12d", "%r13d", "%r9d", "%r8d", "%ecx", "%edx", "%esi", "%edi"}; +static char* ByteRegisters[10] = {"%r10b", "%r11b", "%r12b", "%r13b", "%r9b", "%r8b", "%cl", "%dl", "%sil", "%dil"}; + +/* + * For ease of reading later code, we store the valid x86 comparison instructions, + * and the inverse jump instructions together, in a synchronized fashion. + */ + +static char* Comparisons[6] = {"sete", "setne", "setl", "setg", "setle", "setge"}; +static char* InvComparisons[6] = {"jne", "je", "jge", "jle", "jg", "jl"}; + +// How far above the base pointer is the last local? +static int LocalVarOffset; +// How far must we lower the base pointer to retrieve the parameters? +static int StackFrameOffset; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * * 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; + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * * R E G I S T E R M A N A G E M E N T * * * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Set all Registers to unused. +static void DeallocateAllRegisters() { + UsedRegisters[0] = UsedRegisters[1] = UsedRegisters[2] = UsedRegisters[3] = 0; +} + +/* + * Search for an unused register, allocate it, and return it. + * If none available, cancel compilation. + */ +static int RetrieveRegister() { + for (size_t i = 0; i < 4; i++) { + if (UsedRegisters[i] == 0) { + UsedRegisters[i] = 1; + return i; + } + } + fprintf(stderr, "Out of registers!\n"); + exit(1); +} + +/* + * Set the given register to unused. + * If the register is not used, it is an invalid state. + * @param Register: The Registers index to deallocate. + */ +static void DeallocateRegister(int Register) { + if (UsedRegisters[Register] != 1) { + fprintf(stderr, "Error trying to free register %d\n", Register); + exit(1); + } + + UsedRegisters[Register] = 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * * * * S T A C K M A N A G E M E N T * * * * * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * Prepare a new stack frame pointer. + * This resets the highest local. + * + */ +static void AsNewStackFrame() { + LocalVarOffset = 0; +} + +// Assemble an immediate jump +static void AsJmp(int Label) { + printf("\t\tJumping to label %d\n", Label); + fprintf(OutputFile, "\tjmp\tL%d\n", Label); +} + +/* Create a new base label + * @param Label: The number to create the label of + */ +static void AsLabel(int Label) { + printf("\tCreating label %d\n", Label); + fprintf(OutputFile, "\nL%d:\n", Label); +} + +/* + * Given the type of input, how far do we need to go down the stack frame + * to store or retrieve this type? + * + * The stack must be 4-bytes aligned, so we set a hard minimum. + * + * @param Type: The DataTypes we want to store. + * @return the offset to store the type, taking into account the current state of the stack frame. + * + */ +static int AsCalcOffset(int Type) { + LocalVarOffset += PrimitiveSize(Type) > 4 ? PrimitiveSize(Type) : 4; + return -LocalVarOffset; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * * C O D E G E N E R A T I O N * * * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * A way to keep track of the largest label number. + * Call this function to increase the number SRG-like. + * + * @return the highest available label number + * + */ +static int NewLabel(void) { + static int id = 1; + return id++; +} + +/* + * Align non-char types to a 4 byte alignment. + * Chars need no alignment on x86_64. + * + * @param Type: The DataTypes representation of the data to align + * @param Offset: The offset to align + * @param Direction: The desired direction to move the address for alignment. 1 = up, -1 = down. + * @return the new alignment + * + */ +static int AsAlignMemory(int Type, int Offset, int Direction) { + switch (Type) { + case RET_CHAR: + return Offset; + case RET_INT: + case RET_LONG: + break; + default: + ErrorReport("Unable to align type %s\n", TypeNames(Type)); + } + + int Alignment = 4; + Offset = (Offset + Direction * (Alignment - 1)) & ~(Alignment - 1); + return (Offset); +} + +// Assemble an If statement +static int AsIf(struct ASTNode* Node, int LoopStartLabel, int LoopEndLabel) { + int FalseLabel, EndLabel; + + FalseLabel = NewLabel(); + if (Node->Right) + EndLabel = NewLabel(); + + // Left is the condition + AssembleTree(Node->Left, FalseLabel, LoopStartLabel, LoopEndLabel, Node->Operation); + DeallocateAllRegisters(); + + // Middle is the true block + AssembleTree(Node->Middle, -1, LoopStartLabel, LoopEndLabel, Node->Operation); + DeallocateAllRegisters(); + + // Right is the optional else + if (Node->Right) + AsJmp(EndLabel); + + AsLabel(FalseLabel); + + if (Node->Right) { + AssembleTree(Node->Right, -1, LoopStartLabel, LoopEndLabel, Node->Operation); + DeallocateAllRegisters(); + AsLabel(EndLabel); + } + + return -1; +} + +// Assemble a comparison +static int AsCompare(int Operation, int RegisterLeft, int RegisterRight) { + printf("Comparing registers %d & %d\n", RegisterLeft, RegisterRight); + + if (Operation < OP_EQUAL || Operation > OP_GREATE) + Die("Bad Operation in AsCompare"); + + fprintf(OutputFile, "\tcmpq\t%s, %s\n", Registers[RegisterRight], Registers[RegisterLeft]); + fprintf(OutputFile, "\t%s\t\t%s\n", Comparisons[Operation - OP_EQUAL], ByteRegisters[RegisterRight]); + fprintf(OutputFile, "\tmovzbq\t%s, %s\n", ByteRegisters[RegisterRight], Registers[RegisterLeft]); + DeallocateRegister(RegisterLeft); + return RegisterRight; +} + +// Assemble an inverse comparison (a one-line jump) +static int AsCompareJmp(int Operation, int RegisterLeft, int RegisterRight, int Label) { + if (Operation < OP_EQUAL || Operation > OP_GREATE) + Die("Bad Operation in AsCompareJmp"); + + printf("\tBranching on comparison of registers %d & %d, with operation %s\n\n", RegisterLeft, RegisterRight, + Comparisons[Operation - OP_EQUAL]); + + fprintf(OutputFile, "\tcmpq\t%s, %s\n", Registers[RegisterRight], Registers[RegisterLeft]); + fprintf(OutputFile, "\t%s\tL%d\n", InvComparisons[Operation - OP_EQUAL], Label); + DeallocateAllRegisters(); + + return -1; +} + + +/* + * Assemble a new global string into the data segment. + * @param Value: The name of the string, as a string + */ +static int AsNewString(char* Value) { + int Label = NewLabel(); + char* CharPtr; + + AsLabel(Label); + + for (CharPtr = Value; *CharPtr; CharPtr++) + fprintf(OutputFile, "\t.byte\t%d\r\n", *CharPtr); + fprintf(OutputFile, "\t.byte\t0\r\n"); + + return Label; +} + +/* + * Load a string into a Register. + * @param ID: the Label number of the string + */ +static int AsLoadString(int ID) { + int Register = RetrieveRegister(); + fprintf(OutputFile, "\tleaq\tL%d(\%%rip), %s\r\n", ID, Registers[Register]); + return Register; +} + +// Assemble a While loop +static int AsWhile(struct ASTNode* Node) { + int BodyLabel, BreakLabel; + + BodyLabel = NewLabel(); + BreakLabel = NewLabel(); + + printf("\tInitiating loop between labels %d and %d\n", BodyLabel, BreakLabel); + + // Mark the start position + AsLabel(BodyLabel); + + // Assemble the condition - this should include a jump to end! + AssembleTree(Node->Left, BreakLabel, BodyLabel, BreakLabel, Node->Operation); + DeallocateAllRegisters(); + + // Assemble the body + AssembleTree(Node->Right, -1, BodyLabel, BreakLabel, Node->Operation); + DeallocateAllRegisters(); + + // Jump back to the body - as awe've already failed the condition check if we get here + AsJmp(BodyLabel); + + // Set up the label to break out of the loop. + AsLabel(BreakLabel); + + + return -1; + +} + +static void AsSwitchTable(int reg, int cases, int toplabel, int* caselabel, int* caseval, int defaultlabel) { + int i, label; + + label = NewLabel(); + AsLabel(label); + + // Add a default case even if not present. + if (cases == 0) { + caseval[0] = 0; + caselabel[0] = defaultlabel; + cases = 1; + } + + fprintf(OutputFile, "\t.quad\t%d\n", cases); + for (i = 0; i < cases; i++) + fprintf(OutputFile, "\t.quad\t%d, L%d\n", caseval[i], caselabel[i]); + fprintf(OutputFile, "\t.quad\tL%d\n", defaultlabel); + + AsLabel(toplabel); + fprintf(OutputFile, "\tmovq\t%s, %%rax\n", Registers[reg]); + fprintf(OutputFile, "\tleaq\tL%d(%%rip), %%rdx\n", label); + fprintf(OutputFile, "\tjmp\tswitch\n"); +} + +static int AsSwitch(struct ASTNode* root) { + int* caseval, *caselabel; + int Ljump, Lend; + int i, reg, defaultlabel=0, cases=0; + struct ASTNode* c; + + caseval = (int*) malloc((root->IntValue + 1) * sizeof(int)); + caselabel = (int*) malloc((root->IntValue + 1) * sizeof(int)); + + Ljump = NewLabel(); + Lend = NewLabel(); + + defaultlabel = Lend; + + reg = AssembleTree(root->Left, -1, -1, -1, 0); + AsJmp(Ljump); + DeallocateAllRegisters(); + + for (i = 0, c = root->Right; c != NULL; i++, c = c->Right) { + caselabel[i] = NewLabel(); + caseval[i] = c->IntValue; + AsLabel(caselabel[i]); + + if (c->Operation == OP_DEFAULT) + defaultlabel = caselabel[i]; + else + cases++; + + AssembleTree(c->Left, -1, -1, Lend, 0); + DeallocateAllRegisters(); + } + + AsJmp(Lend); + AsSwitchTable(reg, cases, Ljump, caselabel, caseval, defaultlabel); + AsLabel(Lend); + return -1; +} + +// Load a value into a register. +static int AsLoad(int Value) { + int Register = RetrieveRegister(); + + printf("\tStoring value %d into %s\n", Value, Registers[Register]); + + fprintf(OutputFile, "\tmovq\t$%d, %s\n", Value, Registers[Register]); + + return Register; +} + +// Assemble an addition. +static int AsAdd(int Left, int Right) { + printf("\tAdding Registers %s, %s\n", Registers[Left], Registers[Right]); + fprintf(OutputFile, "\taddq\t%s, %s\n", Registers[Left], Registers[Right]); + + DeallocateRegister(Left); + + return Right; +} + +// Assemble a multiplication. +static int AsMul(int Left, int Right) { + printf("\tMultiplying Registers %s, %s\n", Registers[Left], Registers[Right]); + fprintf(OutputFile, "\timulq\t%s, %s\n", Registers[Left], Registers[Right]); + + DeallocateRegister(Left); + + return Right; +} + +// Assemble a subtraction. +static int AsSub(int Left, int Right) { + printf("\tSubtracting Registers %s, %s\n", Registers[Left], Registers[Right]); + fprintf(OutputFile, "\tsubq\t%s, %s\n", Registers[Right], Registers[Left]); + + DeallocateRegister(Right); + + return Left; +} + +// Assemble a division. +static int AsDiv(int Left, int Right) { + printf("\tDividing Registers %s, %s\n", Registers[Left], Registers[Right]); + fprintf(OutputFile, "\tmovq\t%s, %%rax\n", Registers[Left]); + fprintf(OutputFile, "\tcqo\n"); + fprintf(OutputFile, "\tidivq\t%s\n", Registers[Right]); + fprintf(OutputFile, "\tmovq\t%%rax, %s\n", Registers[Left]); + + DeallocateRegister(Right); + + return Left; +} + +// Assemble an ASL +static int AsShl(int Register, int Val) { + printf("\tShifting %s to the left by %d bits.\n", Registers[Register], Val); + fprintf(OutputFile, "\tsalq\t$%d, %s\n", Val, Registers[Register]); + return Register; +} + +/* + * Load a global variable into a register, with optional pre/post-inc/dec + * @param Entry: The variable to load. + * @param Operation: An optional SyntaxOps element + */ +static int AsLdGlobalVar(struct SymbolTableEntry* Entry, int Operation) { + int Reg = RetrieveRegister(); + printf("\tGlobally storing a %s\n", ScopeNames[Entry->Storage]); + printf("\tStoring %s's contents into %s, globally\n", Entry->Name, Registers[Reg]); + + int TypeSize = PrimitiveSize(Entry->Type); + switch (TypeSize) { + case 1: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincb\t%s(\%%rip)\n", Entry->Name); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecb\t%s(\%%rip)\n", Entry->Name); + break; + } + + fprintf(OutputFile, "\tmovzbq\t%s(\%%rip), %s\n", Entry->Name, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincb\t%s(\%%rip)\n", Entry->Name); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecb\t%s(\%%rip)\n", Entry->Name); + break; + } + + break; + + case 4: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincl\t%s(\%%rip)\n", Entry->Name); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecl\t%s(\%%rip)\n", Entry->Name); + break; + } + + fprintf(OutputFile, "\tmovslq\t%s(\%%rip), %s\n", Entry->Name, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincl\t%s(\%%rip)\n", Entry->Name); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecl\t%s(\%%rip)\n", Entry->Name); + break; + } + + break; + case 8: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincq\t%s(\%%rip)\n", Entry->Name); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecq\t%s(\%%rip)\n", Entry->Name); + break; + } + + fprintf(OutputFile, "\tmovq\t%s(\%%rip), %s\n", Entry->Name, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincq\t%s(\%%rip)\n", Entry->Name); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecq\t%s(\%%rip)\n", Entry->Name); + break; + } + + break; + + default: + DieMessage("Bad type for loading", TypeNames(Entry->Type)); + } + + return Reg; +} + +/* + * Store a value from a register into a global variable. + * @param Entry: The variable to store into. + * @param Regsiter: The Registers index containing the value to store. + */ +static int AsStrGlobalVar(struct SymbolTableEntry* Entry, int Register) { + printf("\tStoring contents of %s into %s, type %d, globally:\n", Registers[Register], Entry->Name, Entry->Type); + + int TypeSize = PrimitiveSize(Entry->Type); + switch (TypeSize) { + case 1: + // movzbq zeroes, then moves a byte into the quad register + fprintf(OutputFile, "\tmovb\t%s, %s(\%%rip)\n", ByteRegisters[Register], Entry->Name); + break; + + case 4: + fprintf(OutputFile, "\tmovl\t%s, %s(\%%rip)\n", DoubleRegisters[Register], Entry->Name); + break; + + case 8: + fprintf(OutputFile, "\tmovq\t%s, %s(%%rip)\n", Registers[Register], Entry->Name); + break; + + default: + DieMessage("Bad type for saving", TypeNames(Entry->Type)); + } + + return Register; +} + +/* + * Load a value from a local variable into a register, with optional post/pre-inc/dec + * @param Entry: The local variable to read + * @param Operation: An optional SyntaxOps entry + */ + +static int AsLdLocalVar(struct SymbolTableEntry* Entry, int Operation) { + int Reg = RetrieveRegister(); + + printf("\tStoring the var at %d's contents into %s, locally\n", Entry->SinkOffset, Registers[Reg]); + + int TypeSize = PrimitiveSize(Entry->Type); + switch (TypeSize) { + case 1: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincb\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecb\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + fprintf(OutputFile, "\tmovzbq\t%d(\%%rbp), %s\n", Entry->SinkOffset, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincb\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecb\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + break; + + case 4: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincl\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecl\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + fprintf(OutputFile, "\tmovslq\t%d(\%%rbp), %s\n", Entry->SinkOffset, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincl\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecl\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + break; + case 8: + switch (Operation) { + case OP_PREINC: + fprintf(OutputFile, "\tincq\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_PREDEC: + fprintf(OutputFile, "\tdecq\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + fprintf(OutputFile, "\tmovq\t%d(\%%rbp), %s\n", Entry->SinkOffset, Registers[Reg]); + + switch (Operation) { + case OP_POSTINC: + fprintf(OutputFile, "\tincq\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + case OP_POSTDEC: + fprintf(OutputFile, "\tdecq\t%d(\%%rbp)\n", Entry->SinkOffset); + break; + } + + break; + + default: + DieMessage("Bad type for loading", TypeNames(Entry->Type)); + } + + return Reg; +} + +/* + * Store a value from a register into a local variable. + * @param Entry: The local variable to write to. + * @param Register: The Registers index containing the desired value + * + */ +static int AsStrLocalVar(struct SymbolTableEntry* Entry, int Register) { + printf("\tStoring contents of %s into %s, type %d, locally\n", Registers[Register], Entry->Name, Entry->Type); + + int TypeSize = PrimitiveSize(Entry->Type); + switch (TypeSize) { + case 1: + // movzbq zeroes, then moves a byte into the quad register + fprintf(OutputFile, "\tmovb\t%s, %d(\%%rbp)\n", ByteRegisters[Register], Entry->SinkOffset); + break; + + case 4: + fprintf(OutputFile, "\tmovl\t%s, %d(\%%rbp)\n", DoubleRegisters[Register], Entry->SinkOffset); + break; + + case 8: + fprintf(OutputFile, "\tmovq\t%s, %d(%%rbp)\n", Registers[Register], Entry->SinkOffset); + break; + + default: + DieMessage("Bad type for saving", TypeNames(Entry->Type)); + } + + return Register; +} + +// Assemble a pointerisation +static int AsAddr(struct SymbolTableEntry* Entry) { + int Register = RetrieveRegister(); + printf("\tSaving pointer of %s into %s\n", Entry->Name, Registers[Register]); + + fprintf(OutputFile, "\tleaq\t%s(%%rip), %s\n", Entry->Name, Registers[Register]); + return Register; +} + +// Assemble a dereference +static int AsDeref(int Reg, int Type) { + + int DestSize = PrimitiveSize(ValueAt(Type)); + + printf("\tDereferencing %s\n", Registers[Reg]); + switch (DestSize) { + case 1: + fprintf(OutputFile, "\tmovzbq\t(%s), %s\n", Registers[Reg], Registers[Reg]); + break; + case 2: + fprintf(OutputFile, "\tmovslq\t(%s), %s\n", Registers[Reg], DoubleRegisters[Reg]); + break; + case 4: + fprintf(OutputFile, "\tmovl\t(%s), %s\n", Registers[Reg], DoubleRegisters[Reg]); + break; + case 8: + fprintf(OutputFile, "\tmovq\t(%s), %s\n", Registers[Reg], Registers[Reg]); + break; + default: + DieDecimal("Can't generate dereference for type", Type); + } + + return Reg; +} + +// Assemble a store-through-dereference +static int AsStrDeref(int Register1, int Register2, int Type) { + printf("\tStoring contents of %s into %s through a dereference, type %d\n", Registers[Register1], + Registers[Register2], Type); + + switch (Type) { + case RET_CHAR: + fprintf(OutputFile, "\tmovb\t%s, (%s)\n", ByteRegisters[Register1], Registers[Register2]); + break; + case RET_INT: + fprintf(OutputFile, "\tmovl\t%s, (%s)\n", DoubleRegisters[Register1], Registers[Register2]); + break; + case RET_LONG: + fprintf(OutputFile, "\tmovq\t%s, (%s)\n", Registers[Register1], Registers[Register2]); + break; + default: + DieDecimal("Can't generate store-into-deref of type", Type); + } + + return Register1; +} + +static void AsDataSegment() { + fprintf(OutputFile, ".data\n"); +} + +// Assemble a global symbol (variable, struct, enum, function, string) +static void AsGlobalSymbol(struct SymbolTableEntry* Entry) { + int size, type, init, i; + + if (Entry == NULL) return; + if (Entry->Structure == ST_FUNC) return; + + if (Entry->Structure == ST_ARR) { + type = ValueAt(Entry->Type); + size = TypeSize(type, Entry->CompositeType); + } else { + size = Entry->Size; + type = Entry->Type; + } + + AsDataSegment(); + fprintf(OutputFile,"\t.globl\t%s\n", Entry->Name); + fprintf(OutputFile, "%s:\n", Entry->Name); + + for (i = 0; i < Entry->Length; i++) { + init = 0; + if (Entry->InitialValues != NULL) + init = Entry->InitialValues[i]; + + switch (size) { + case 1: + fprintf(OutputFile, "\t.byte\t%d\r\n", init); + break; + case 4: + fprintf(OutputFile, "\t.long\t%d\r\n", init); + break; + case 8: + if (Entry->InitialValues && type == PointerTo(RET_CHAR)) + fprintf(OutputFile, "\t.quad\tL%d\r\n", init); + else + fprintf(OutputFile, "\t.quad\t%d\r\n", init); + break; + default: + for (i = 0; i < size; i++) + fprintf(OutputFile, "\t.byte\t0\n"); + } + } + +} + + +// Copy a function argument from Register to argument Position +static void AsCopyArgs(int Register, int Position) { + if (Position > 4) { // Args above 4 go on the stack + fprintf(OutputFile, "\tpushq\t%s\n", Registers[Register]); + } else { + fprintf(OutputFile, "\tmovq\t%s, %s\n", Registers[Register], Registers[8 - Position]); + } +} + +// Assemble an actual function call. +// NOTE: this should not be called. Use AsCallWrapper. +static int AsCall(struct SymbolTableEntry* Entry, int Args) { + + int OutRegister = RetrieveRegister(); + + printf("\t\tCalling function %s with %d parameters\n", Entry->Name, Args); + printf("\t\t\tFunction returns into %s\n", Registers[OutRegister]); + + // Allocate shadow space + fprintf(OutputFile, "\taddq\t$-32, %%rsp\n"); + fprintf(OutputFile, "\tcall\t%s\n", Entry->Name); + // Deallocate arguments and stack space. + if (Args > 4) + fprintf(OutputFile, "\taddq\t$%d, %%rsp\n", (8 * (Args - 4)) + 32); + else + fprintf(OutputFile, "\taddq\t$32, %%rsp\n"); + + fprintf(OutputFile, "\tmovq\t%%rax, %s\n", Registers[OutRegister]); + + return OutRegister; +} + +// Assemble a function call, with all associated parameter bumping and stack movement. +static int AsCallWrapper(struct ASTNode* Node) { + struct ASTNode* CompositeTree = Node->Left; + int Register, Args = 0; + + while (CompositeTree) { + Register = AssembleTree(CompositeTree->Right, -1, -1, -1, CompositeTree->Operation); + AsCopyArgs(Register, CompositeTree->Size); + if (Args == 0) Args = CompositeTree->Size; + DeallocateAllRegisters(); + CompositeTree = CompositeTree->Left; + } + + return AsCall(Node->Symbol, Args); +} + +// Assemble a function return. +static int AsReturn(struct SymbolTableEntry* Entry, int Register) { + + printf("\t\tCreating return for function %s\n", Entry->Name); + + switch (Entry->Type) { + case RET_CHAR: + fprintf(OutputFile, "\tmovzbl\t%s, %%eax\n", ByteRegisters[Register]); + break; + + case RET_INT: + fprintf(OutputFile, "\tmovl\t%s, %%eax\n", DoubleRegisters[Register]); + break; + + case RET_LONG: + fprintf(OutputFile, "\tmovq\t%s, %%rax\n", Registers[Register]); + break; + + default: + DieMessage("Bad function type in generating return", TypeNames(Entry->Type)); + + } + + AsJmp(Entry->EndLabel); +} + + +// Assemble a =? +static int AsEqual(int Left, int Right) { + // Set the lowest bit if left = right + return AsCompare(OP_EQUAL, Left, Right); +} + +// Assemble a != +static int AsIneq(int Left, int Right) { + // Set the lowest bit if left != right + return AsCompare(OP_INEQ, Left, Right); +} + +// Assemble a < +static int AsLess(int Left, int Right) { + // Set the lowest bit if left < right + return AsCompare(OP_LESS, Left, Right); +} + +// Assemble a > +static int AsGreat(int Left, int Right) { + // Set the lowest bit if left > right + return AsCompare(OP_GREAT, Left, Right); +} + +// Assemble a <= +static int AsLessE(int Left, int Right) { + // Set the lowest bit if left <= right + return AsCompare(OP_LESSE, Left, Right); +} + +// Assemble a => +static int AsGreatE(int Left, int Right) { + // Set the lowest bit if left => right + return AsCompare(OP_GREATE, Left, Right); +} + +// Assemble a print statement +static void AssemblerPrint(int Register) { + printf("\t\tPrinting Register %s\n", Registers[Register]); + + fprintf(OutputFile, "\tmovq\t%s, %%rcx\n", Registers[Register]); + //fprintf(OutputFile, "\tleaq\t.LC0(%%rip), %%rcx\n"); + fprintf(OutputFile, "\tcall\tPrintInteger\n"); + + DeallocateRegister(Register); +} + +// Assemble a & +static int AsBitwiseAND(int Left, int Right) { + fprintf(OutputFile, "\tandq\t%s, %s\n", Registers[Left], Registers[Right]); + DeallocateRegister(Left); + return Right; +} + +// Assemble a | +static int AsBitwiseOR(int Left, int Right) { + fprintf(OutputFile, "\torq\t%s, %s\n", Registers[Left], Registers[Right]); + DeallocateRegister(Left); + return Right; +} + +// Assemble a ^ +static int AsBitwiseXOR(int Left, int Right) { + fprintf(OutputFile, "\txorq\t%s, %s\n", Registers[Left], Registers[Right]); + DeallocateRegister(Left); + return Right; +} + +// Assemble a ~ +static int AsNegate(int Register) { + fprintf(OutputFile, "\tnegq\t%s\n", Registers[Register]); + return Register; +} + +// Assemble a ! +static int AsInvert(int Register) { + fprintf(OutputFile, "\tnotq\t%s\n", Registers[Register]); + return Register; +} + +// Assemble a ! +static int AsBooleanNOT(int Register) { + fprintf(OutputFile, "\ttest\t%s, %s\n", Registers[Register], Registers[Register]); + fprintf(OutputFile, "\tsete\t%s\n", ByteRegisters[Register]); + fprintf(OutputFile, "\tmovzbq\t%s, %s\n", ByteRegisters[Register], Registers[Register]); + return Register; +} + +// Assemble a << +static int AsShiftLeft(int Left, int Right) { + fprintf(OutputFile, "\tmovb\t%s, \%%cl\n", ByteRegisters[Right]); + fprintf(OutputFile, "\tshlq\t\%%cl, %s\n", Registers[Left]); + DeallocateRegister(Right); + return Left; +} + +// Assemble a >> +static int AsShiftRight(int Left, int Right) { + fprintf(OutputFile, "\tmovb\t%s, \%%cl\n", ByteRegisters[Right]); + fprintf(OutputFile, "\tshrq\t\%%cl, %s\n", Registers[Left]); + DeallocateRegister(Right); + return Left; +} + +// Assemble a conversion from arbitrary type to boolean. +// Facilitates if(ptr) +static int AsBooleanConvert(int Register, int Operation, int Label) { + fprintf(OutputFile, "\ttest\t%s, %s\n", Registers[Register], Registers[Register]); + + switch (Operation) { + case OP_IF: + case OP_LOOP: + fprintf(OutputFile, "\tje\tL%d\n", Label); + break; + default: + fprintf(OutputFile, "\tsetnz\t%s\n", ByteRegisters[Register]); + fprintf(OutputFile, "\tmovzbq\t%s, %s\n", ByteRegisters[Register], Registers[Register]); + break; + } + + return Register; +} + +// Assemble the start of an assembly file +static void AssemblerPreamble() { + DeallocateAllRegisters(); + fputs( + "\t.text\n" + "switch:\n" + "\t\tpushq %rsi\n" + "\t\tmovq %rdx, %rsi\n" + "\t\tmovq %rax, %rbx\n" + "\t\tcld\n" + "\t\tlodsq\n" + "\t\tmovq %rax, %rcx\n" + "next:\n" + "\t\tlodsq\n" + "\t\tmovq %rax, %rdx\n" + "\t\tlodsq\n" + "\t\tcmpq %rdx, %rbx\n" + "\t\tjnz no\n" + "\t\tpopq %rsi\n" + "\t\tjmp *%rax\n" + "no:\n" + "\t\tloop next\n" + "\t\tlodsq\n" + "\t\tpopq %rsi\n" + "\t\tjmp *%rax\n", + OutputFile); +} + +/* + * Assemble a function block for the Entry. + * Handles all stack logic for local variables, + * as well as copying parameters out of registers and + * into the spill space. + * + * @param Entry: The function to generate + * + */ +static void AsFunctionPreamble(struct SymbolTableEntry* Entry) { + char* Name = Entry->Name; + struct SymbolTableEntry* Param, * Local; + int ParamOffset = 16, ParamReg = 9, ParamCount = 0; + + LocalVarOffset = 0; // Prepare parameters + + fprintf(OutputFile, + "\t.text\n" + "\t.globl\t%s\n" + "\t.type\t%s, @function\n" + "%s:\n" + "\tpushq\t%%rbp\n" + "\tmovq\t%%rsp, %%rbp\r\n", + Name, Name, Name); + + // Need to share this between two loops. Fun. + int LoopIndex; + + // If we have parameters, move them to the last 6 registers + for (Param = Entry->Start, ParamCount = 1; Param != NULL; Param = Param->NextSymbol, ParamCount++) { + if (ParamCount > 6) { // We only have 6 argument registers + Param->SinkOffset = ParamOffset; + ParamOffset += 8; + } + + Entry->SinkOffset = AsCalcOffset(Param->Type); + AsStrLocalVar(Param, ParamReg--); + } + + // If we have more parameters, move them to the stack + for (Local = Locals; Local != NULL; Local = Local->NextSymbol) { + Local->SinkOffset = AsCalcOffset(Local->Type); + } + + // With all the parameters on the stack, we can allocate the shadow space + StackFrameOffset = ((LocalVarOffset + 31) & ~31); + fprintf(OutputFile, + "\taddq\t$%d, %%rsp\n", -StackFrameOffset); + +} + + +// Assemble the epilogue of a function +static void AsFunctionEpilogue(struct SymbolTableEntry* Entry) { + AsLabel(Entry->EndLabel); + + fprintf(OutputFile, + "\taddq\t$%d, %%rsp\n" + "\tpopq\t%%rbp\n" + "\tret\n", + StackFrameOffset); +} + +/* + * 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: %s\r\n", OperationNames[Node->Operation]); + fflush(stdout); + switch (Node->Operation) { + case OP_IF: + return AsIf(Node, LoopBeginLabel, LoopEndLabel); + + case OP_LOOP: + return AsWhile(Node); + + case OP_COMP: + if (Node->Left) AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation); + DeallocateAllRegisters(); + if (Node->Right) AssembleTree(Node->Right, -1, LoopBeginLabel, LoopEndLabel, Node->Operation); + DeallocateAllRegisters(); + return -1; + + case OP_CALL: + return AsCallWrapper(Node); + + case OP_FUNC: + AsFunctionPreamble(Node->Symbol); + AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation); + AsFunctionEpilogue(Node->Symbol); + return -1; + + case OP_SWITCH: + return AsSwitch(Node); + } + + + 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: + return AsAdd(LeftVal, RightVal); + + case OP_SUBTRACT: + return AsSub(LeftVal, RightVal); + + case OP_MULTIPLY: + return AsMul(LeftVal, RightVal); + + case OP_DIVIDE: + return AsDiv(LeftVal, RightVal); + + case OP_SCALE: + // We can (ab)use the powers of 2 to do + // efficient scaling with bitshifting. + switch (Node->Size) { + case 2: + return AsShl(LeftVal, 1); + case 4: + return AsShl(LeftVal, 2); + case 8: + return AsShl(LeftVal, 3); + + default: + RightVal = AsLoad(Node->Size); + return AsMul(LeftVal, RightVal); + } + + case OP_BREAK: + printf("\t\tBreaking to the end of the loop; L%d\n", LoopEndLabel); + AsJmp(LoopEndLabel); + return -1; + case OP_CONTINUE: + printf("\t\tContinuing to the start of the loop; L%d\n", LoopBeginLabel); + AsJmp(LoopBeginLabel); + return -1; + + case OP_ADDRESS: + return AsAddr(Node->Symbol); + + case OP_DEREF: + return Node->RVal ? AsDeref(LeftVal, Node->Left->ExprType) : LeftVal; + + 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) + return AsStrLocalVar(Node->Right->Symbol, LeftVal); + else + return AsStrGlobalVar(Node->Right->Symbol, LeftVal); + + case OP_DEREF: + return AsStrDeref(LeftVal, RightVal, Node->Right->ExprType); + default: + DieDecimal("Can't ASSIGN in AssembleTree: ", Node->Operation); + } + + case OP_WIDEN: + printf("\tWidening types..\r\n"); + return LeftVal; + + case OP_RET: + printf("\tReturning from %s\n", Node->Symbol->Name); + AsReturn(CurrentFile->FunctionEntry, LeftVal); + return -1; + + 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) + return AsCompareJmp(Node->Operation, LeftVal, RightVal, Register); + else + return AsCompare(Node->Operation, LeftVal, RightVal); + + + case REF_IDENT: + if (TypeIsPtr(Node->ExprType)) { + return AsAddr(Node->Symbol); + } + + if (Node->RVal || ParentOp == OP_DEREF) { + if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) + return AsLdLocalVar(Node->Symbol, Node->Operation); + else + return AsLdGlobalVar(Node->Symbol, Node->Operation); + } else + return -1; + + case TERM_INTLITERAL: + return AsLoad(Node->IntValue); + + case TERM_STRLITERAL: + return AsLoadString(Node->IntValue); + + case OP_PRINT: + AssemblerPrint(LeftVal); + DeallocateAllRegisters(); + return -1; + + case OP_BITAND: + return AsBitwiseAND(LeftVal, RightVal); + + case OP_BITOR: + return AsBitwiseOR(LeftVal, RightVal); + + case OP_BITXOR: + return AsBitwiseXOR(LeftVal, RightVal); + + case OP_SHIFTL: + return AsShiftLeft(LeftVal, RightVal); + + case OP_SHIFTR: + return AsShiftRight(LeftVal, RightVal); + + case OP_POSTINC: + if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) + return AsLdLocalVar(Node->Symbol, Node->Operation); + else + return AsLdGlobalVar(Node->Symbol, Node->Operation); + + case OP_POSTDEC: + if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) + return AsLdLocalVar(Node->Symbol, Node->Operation); + else + return AsLdGlobalVar(Node->Symbol, Node->Operation); + + case OP_PREINC: + if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) + return AsLdLocalVar(Node->Symbol, Node->Operation); + else + return AsLdGlobalVar(Node->Symbol, Node->Operation); + + case OP_PREDEC: + if (Node->Symbol->Storage == SC_LOCAL || Node->Symbol->Storage == SC_PARAM) + return AsLdLocalVar(Node->Symbol, Node->Operation); + else + return AsLdGlobalVar(Node->Symbol, Node->Operation); + + case OP_BOOLNOT: + return AsBooleanNOT(LeftVal); + + case OP_BITNOT: + return AsInvert(LeftVal); + + case OP_NEGATE: + return AsNegate(LeftVal); + + case OP_BOOLCONV: + return AsBooleanConvert(LeftVal, ParentOp, Register); + + default: + DieDecimal("Unknown ASM Operation", Node->Operation); + return 0; + } +} + +static const struct AssemblerVtable LinuxGASVtable = { + .AssembleTree = AssembleTree, + .AsAdd = AsAdd, + .AsAddr = AsAddr, + .AsAlignMemory = AsAlignMemory, + .AsBitwiseAND = AsBitwiseAND, + .AsBitwiseOR = AsBitwiseOR, + .AsBitwiseXOR = AsBitwiseXOR, + .AsBooleanConvert = AsBooleanConvert, + .AsBooleanNOT = AsBooleanNOT, + .AsCalcOffset = AsCalcOffset, + .AsCall = AsCall, + .AsCallWrapper = AsCallWrapper, + .AsCompare = AsCompare, + .AsCompareJmp = AsCompareJmp, + .AsCopyArgs = AsCopyArgs, + .AsDeref = AsDeref, + .AsDiv = AsDiv, + .AsEqual = AsEqual, + .AsFunctionEpilogue = AsFunctionEpilogue, + .AsFunctionPreamble = AsFunctionPreamble, + .AsGreat = AsGreat, + .AsGreatE = AsGreatE, + .AsGlobalSymbol = AsGlobalSymbol, + .AsIf = AsIf, + .AsIneq = AsIneq, + .AsInvert = AsInvert, + .AsJmp = AsJmp, + .AsLabel = AsLabel, + .AsLdGlobalVar = AsLdGlobalVar, + .AsLdLocalVar = AsLdLocalVar, + .AsLess = AsLess, + .AsLessE = AsLessE, + .AsLoad = AsLoad, + .AsLoadString = AsLoadString, + .AsMul = AsMul, + .AsNegate = AsNegate, + .AsNewStackFrame = AsNewStackFrame, + .AsNewString = AsNewString, + .AsReturn = AsReturn, + .AsShiftLeft = AsShiftLeft, + .AsShiftRight = AsShiftRight, + .AsShl = AsShl, + .AsStrDeref = AsStrDeref, + .AsStrGlobalVar = AsStrGlobalVar, + .AsStrLocalVar = AsStrLocalVar, + .AsSub = AsSub, + .AsSwitch = AsSwitch, + .AsSwitchTable = AsSwitchTable, + .AsWhile = AsWhile, + .DeallocateAllRegisters = DeallocateAllRegisters, + .RetrieveRegister = RetrieveRegister, + .DeallocateRegister = DeallocateRegister, + .NewLabel = NewLabel, + .AssemblerPreamble = AssemblerPreamble +}; + +static struct AssemblerModule LinuxGASMModule = { + .name = "Linux", + .vtable = &LinuxGASVtable +}; + +void RegisterLinuxASM() { + RegisterModule(&LinuxGASMModule); +} \ No newline at end of file diff --git a/src/assemble/ASMAssembler.c b/src/assemble/Win32GASAssembler.c similarity index 99% rename from src/assemble/ASMAssembler.c rename to src/assemble/Win32GASAssembler.c index 6e02a1f..4a2cf31 100644 --- a/src/assemble/ASMAssembler.c +++ b/src/assemble/Win32GASAssembler.c @@ -146,7 +146,7 @@ static int AsCalcOffset(int Type) { * @return the highest available label number * */ -int NewLabel(void) { +static int NewLabel(void) { static int id = 1; return id++; } @@ -1273,7 +1273,7 @@ static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel, } } -static const struct AssemblerVtable Win32ASMVtable = { +static const struct AssemblerVtable Win32GASVtable = { .AssembleTree = AssembleTree, .AsAdd = AsAdd, .AsAddr = AsAddr, @@ -1331,8 +1331,8 @@ static const struct AssemblerVtable Win32ASMVtable = { }; static struct AssemblerModule Win32ASMModule = { - .name = "Win32 GAS ASM", - .vtable = &Win32ASMVtable + .name = "Win32", + .vtable = &Win32GASVtable }; void RegisterWin32ASM() {