diff --git a/.gitignore b/.gitignore index 2600096..98709a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .* *~ *.iml +*.o +*.asm /target !.git* !.mailmap diff --git a/examples/double.tb b/examples/double.tb new file mode 100644 index 0000000..55cd878 --- /dev/null +++ b/examples/double.tb @@ -0,0 +1,6 @@ +10 INPUT X +20 GOSUB 50 +30 PRINT X +40 END +50 LET X = X * 2 +60 RETURN diff --git a/src/main/java/com/grahamedgecombe/tinybasic/TinyBasicCompiler.java b/src/main/java/com/grahamedgecombe/tinybasic/TinyBasicCompiler.java index 419055e..225e5c6 100644 --- a/src/main/java/com/grahamedgecombe/tinybasic/TinyBasicCompiler.java +++ b/src/main/java/com/grahamedgecombe/tinybasic/TinyBasicCompiler.java @@ -1,12 +1,14 @@ package com.grahamedgecombe.tinybasic; import com.grahamedgecombe.tinybasic.ast.Program; +import com.grahamedgecombe.tinybasic.codegen.CodeGenerator; +import com.grahamedgecombe.tinybasic.codegen.x86_64.X86_64CodeGenerator; import com.grahamedgecombe.tinybasic.parser.Parser; -import com.grahamedgecombe.tinybasic.stackir.Instruction; import com.grahamedgecombe.tinybasic.stackir.InstructionSequence; import com.grahamedgecombe.tinybasic.tokenizer.Tokenizer; import java.io.IOException; +import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -15,15 +17,16 @@ public final class TinyBasicCompiler { public static void main(String[] args) throws IOException { - Path path = Paths.get(args[0]); - try (Tokenizer tokenizer = new Tokenizer(Files.newBufferedReader(path, StandardCharsets.UTF_8))) { + Path inputPath = Paths.get(args[0]); + try (Tokenizer tokenizer = new Tokenizer(Files.newBufferedReader(inputPath, StandardCharsets.UTF_8))) { try (Parser parser = new Parser(tokenizer)) { Program program = parser.parse(); - InstructionSequence seq = new InstructionSequence(); - program.compile(seq); - for (Instruction instruction : seq.getInstructions()) - System.out.println(instruction); + InstructionSequence seq = program.compile(); + + try (CodeGenerator generator = new X86_64CodeGenerator(new OutputStreamWriter(System.out))) { + generator.generate(seq); + } } } } diff --git a/src/main/java/com/grahamedgecombe/tinybasic/ast/PrintStatement.java b/src/main/java/com/grahamedgecombe/tinybasic/ast/PrintStatement.java index 4162170..c67d0fd 100644 --- a/src/main/java/com/grahamedgecombe/tinybasic/ast/PrintStatement.java +++ b/src/main/java/com/grahamedgecombe/tinybasic/ast/PrintStatement.java @@ -61,6 +61,8 @@ public void compile(InstructionSequence seq) { /* this is rather hacky, but the only place types are important, so it doesn't seem worth improving it */ seq.append(new Instruction(value instanceof ImmediateString ? Opcode.OUTS : Opcode.OUTI)); } + + seq.append(new Instruction(Opcode.PUSHS, "\n"), new Instruction(Opcode.OUTS)); } } diff --git a/src/main/java/com/grahamedgecombe/tinybasic/ast/Program.java b/src/main/java/com/grahamedgecombe/tinybasic/ast/Program.java index 87ca22e..e34bec8 100644 --- a/src/main/java/com/grahamedgecombe/tinybasic/ast/Program.java +++ b/src/main/java/com/grahamedgecombe/tinybasic/ast/Program.java @@ -49,10 +49,12 @@ public String toString() { return buf.toString(); } - public void compile(InstructionSequence seq) { + public InstructionSequence compile() { + InstructionSequence seq = new InstructionSequence(); for (Line line : lines) { line.compile(seq); } + return seq; } } diff --git a/src/main/java/com/grahamedgecombe/tinybasic/codegen/CodeGenerator.java b/src/main/java/com/grahamedgecombe/tinybasic/codegen/CodeGenerator.java new file mode 100644 index 0000000..875d7e5 --- /dev/null +++ b/src/main/java/com/grahamedgecombe/tinybasic/codegen/CodeGenerator.java @@ -0,0 +1,12 @@ +package com.grahamedgecombe.tinybasic.codegen; + +import com.grahamedgecombe.tinybasic.stackir.InstructionSequence; + +import java.io.Closeable; +import java.io.IOException; + +public abstract class CodeGenerator implements Closeable { + + public abstract void generate(InstructionSequence seq) throws IOException; + +} diff --git a/src/main/java/com/grahamedgecombe/tinybasic/codegen/x86_64/X86_64CodeGenerator.java b/src/main/java/com/grahamedgecombe/tinybasic/codegen/x86_64/X86_64CodeGenerator.java new file mode 100644 index 0000000..ef18c18 --- /dev/null +++ b/src/main/java/com/grahamedgecombe/tinybasic/codegen/x86_64/X86_64CodeGenerator.java @@ -0,0 +1,202 @@ +package com.grahamedgecombe.tinybasic.codegen.x86_64; + +import com.grahamedgecombe.tinybasic.codegen.CodeGenerator; +import com.grahamedgecombe.tinybasic.stackir.Instruction; +import com.grahamedgecombe.tinybasic.stackir.InstructionSequence; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +public final class X86_64CodeGenerator extends CodeGenerator { + + private final Writer writer; + + public X86_64CodeGenerator(Writer writer) { + this.writer = writer; + } + + @Override + public void generate(InstructionSequence seq) throws IOException { + writer.append("[extern exit]\n"); + writer.append("[extern printf]\n"); + writer.append("[extern scanf]\n"); + writer.append("[section .code]\n"); + writer.append("[global main]\n"); + writer.append("main:\n"); + writer.append(" push rbp\n"); + writer.append(" mov rbp, rsp\n"); + writer.append(" sub rsp, " + (8 * 27) + "\n"); + + Map strings = new HashMap<>(); + for (Instruction instruction : seq.getInstructions()) { + switch (instruction.getOpcode()) { + case LABEL: + writer.append(instruction.getStringOperand().get() + ":\n"); + break; + + case PUSHI: + writer.append(" push 0x" + Integer.toHexString(instruction.getIntegerOperand().get()) + "\n"); + break; + + case PUSHS: + String label = seq.createGeneratedLabel(); + strings.put(label, instruction.getStringOperand().get()); + writer.append(" push " + label + "\n"); + break; + + case LOAD: + writer.append(" mov rax, [rbp - " + varIndex(instruction) + "]\n"); + writer.append(" push rax\n"); + break; + + case STORE: + writer.append(" pop rax\n"); + writer.append(" mov [rbp - " + varIndex(instruction) + "], rax\n"); + break; + + case ADD: + writer.append(" pop rax\n"); + writer.append(" pop rbx\n"); + writer.append(" add rax, rbx\n"); + writer.append(" push rax\n"); + break; + + case SUB: + writer.append(" pop rax\n"); + writer.append(" pop rbx\n"); + writer.append(" sub rax, rbx\n"); + writer.append(" push rax\n"); + break; + + case MUL: + writer.append(" pop rax\n"); + writer.append(" pop rbx\n"); + writer.append(" imul rax, rbx\n"); + writer.append(" push rax\n"); + break; + + case DIV: + writer.append(" xor rdx, rdx\n"); + writer.append(" pop rax\n"); + writer.append(" pop rbx\n"); + writer.append(" idiv rbx\n"); + writer.append(" push rax\n"); + break; + + case CALL: + writer.append(" call " + instruction.getStringOperand().get() + "\n"); + break; + + case RET: + writer.append(" ret\n"); + break; + + case JMP: + writer.append(" jmp " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPGT: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" jg " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPGTE: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" jge " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPLT: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" jl " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPLTE: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" jle " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPEQ: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" je " + instruction.getStringOperand().get() + "\n"); + break; + + case JMPNE: + writer.append(" pop rbx\n"); + writer.append(" pop rax\n"); + writer.append(" cmp rax, rbx\n"); + writer.append(" jne " + instruction.getStringOperand().get() + "\n"); + break; + + case HLT: + writer.append(" mov rdi, 0\n"); + writer.append(" call exit\n"); + break; + + case IN: + strings.put("num_fmt", "%d"); + writer.append(" lea rsi, [rbp - " + (8 * 26) + "]\n"); + writer.append(" mov rdi, num_fmt\n"); + writer.append(" mov al, 0\n"); + writer.append(" call scanf\n"); + writer.append(" xor rax, rax\n"); + writer.append(" mov eax, [rbp - " + (8 * 26) + "]\n"); + writer.append(" push rax\n"); + break; + + case OUTS: + strings.put("str_fmt", "%s"); + writer.append(" pop rsi\n"); + writer.append(" mov rdi, str_fmt\n"); + writer.append(" mov al, 0\n"); + writer.append(" call printf\n"); + break; + + case OUTI: + strings.put("num_fmt", "%d"); + writer.append(" pop rsi\n"); + writer.append(" mov rdi, num_fmt\n"); + writer.append(" mov al, 0\n"); + writer.append(" call printf\n"); + break; + } + } + + writer.append(" mov rax, 0\n"); + writer.append(" mov rsp, rbp\n"); + writer.append(" pop rbp\n"); + writer.append(" ret\n"); + + writer.append("[section .rodata]\n"); + for (Map.Entry string : strings.entrySet()) { + writer.append(string.getKey() + ":\n"); + writer.append(" db \"" + escape(string.getValue()) + "\", 0\n"); + } + } + + private String escape(String value) { + value = value.replace("\n", "\", 10, \""); + return value; + } + + private int varIndex(Instruction instruction) { + return (instruction.getStringOperand().get().charAt(0) - 'A') * 8; + } + + @Override + public void close() throws IOException { + writer.close(); + } + +}