From 20fef8a03a432311ec3581b89d0fa3777a487da3 Mon Sep 17 00:00:00 2001 From: inglorion Date: Fri, 19 Sep 2008 13:11:19 +0200 Subject: [PATCH] Initial import. --- LICENSE | 20 +++ Makefile | 21 +++ README | 52 ++++++ TODO | 13 ++ doc/Makefile | 7 + doc/file-format | 13 ++ doc/instructions | 72 ++++++++ src/Makefile | 47 ++++++ src/make-opcodes-h | 14 ++ src/make-turbovm-asm-l | 36 ++++ src/make-turbovm-asm-y | 169 +++++++++++++++++++ src/opcodes.dat | 58 +++++++ src/turbovm-asm.c | 447 +++++++++++++++++++++++++++++++++++++++++++++++++ src/turbovm-disasm.c | 149 +++++++++++++++++ src/turbovm-switch.c | 150 +++++++++++++++++ src/turbovm-to-c.c | 212 +++++++++++++++++++++++ src/turbovm.c | 1 + src/turbovm.h | 13 ++ test/Makefile | 20 +++ test/countdown | Bin 0 -> 7676 bytes test/countdown.c | 70 ++++++++ test/countdown.tvma | 24 +++ test/test.sh | 20 +++ test/test.tvma | 108 ++++++++++++ 24 files changed, 1736 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 doc/Makefile create mode 100644 doc/file-format create mode 100644 doc/instructions create mode 100644 src/Makefile create mode 100755 src/make-opcodes-h create mode 100755 src/make-turbovm-asm-l create mode 100755 src/make-turbovm-asm-y create mode 100644 src/opcodes.dat create mode 100644 src/turbovm-asm.c create mode 100644 src/turbovm-disasm.c create mode 100644 src/turbovm-switch.c create mode 100644 src/turbovm-to-c.c create mode 120000 src/turbovm.c create mode 100644 src/turbovm.h create mode 100644 test/Makefile create mode 100755 test/countdown create mode 100644 test/countdown.c create mode 100644 test/countdown.tvma create mode 100755 test/test.sh create mode 100644 test/test.tvma diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f6b14f --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2007 Robbert Haarman + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6e24c22 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +SUBDIRS = doc src test + +build : + cd src && $(MAKE) + +doc : + cd doc && $(MAKE) + +test : build + cd test && $(MAKE) test + +all : + for dir in $(SUBDIRS); do (cd "$$dir" && $(MAKE) all); done + +clean : + for dir in $(SUBDIRS); do (cd "$$dir" && $(MAKE) clean); done + +distclean : + for dir in $(SUBDIRS); do (cd "$$dir" && $(MAKE) distclean); done + +.PHONY : all build clean distclean doc test diff --git a/README b/README new file mode 100644 index 0000000..d90a98a --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +TurboVM +======= + +TurboVM is a virtual machine implementing a RISC-like bytecode language. +Programs in this bytecode language can either be interpreted or compiled +to C, which can then be compiled to native code. An assembler is +provided to allow bytecode programs to be generated from a +human-readable language, and a disassembler is provided to turn bytecode +back into human-readable form. + +Building +-------- + +Building TurboVM is done using make (either GNU make or BSD make should +do) and a C compiler. Just cd into the src directory and run make. + +This will generate the following executables: + + - turbovm: the TurboVM interpreter + - turbovm-asm: The TurboVM assembler + - turbovm-disasm: The TurboVM disassembler + +Performance +----------- + +Performance was measured on a number of different machines, by comparing +the run time of a program that counts down to zero. The three +measurements, in order are for (1) the fastest TurboVM interpreter on +that machine running countdown.tvm, (2) countdown.tvm compiled to native +code, and (3) a C-implementation of the countdown program compiled to +native code. + +On a VIA Nehemiah at 800 MHz: + +turbovm-switch countdown: 2.87 +countdown: 1.36 +countdown-c: + +On a G4 (7447A) at 666 MHz: + +turbovm-switch countdown: 1.80 +countdown: 1.26 +countdown-c: 1.26 + +On a Sempron 3000+ at 2.2 GHz: + +turbovm-switch countdown: 0.80 +countdown: 0.80 +countdown-c: 0.80 + +In other words, the slowdown from using the interpreter is about a +factor 20 (PowerPC and AMD64) to 30 (x86). diff --git a/TODO b/TODO new file mode 100644 index 0000000..18a34e0 --- /dev/null +++ b/TODO @@ -0,0 +1,13 @@ +Before release: + + - Update README. + - Perhaps some more documentation. + + - Add syscall (imm) instruction. + - Remove halt instruction. + - Define system calls. + - Generally rework instruction set. + * More efficient alternatives for ifeq and friends? + * Provide a way to deal with integer overflow. + * Special purpose register for program counter? + - Think about 64-bit issues. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..1aae056 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,7 @@ +all : + +clean : + +distclean : clean + +.PHONY : all clean distclean diff --git a/doc/file-format b/doc/file-format new file mode 100644 index 0000000..2cebab2 --- /dev/null +++ b/doc/file-format @@ -0,0 +1,13 @@ +Header + +Comments + +Imports + +Exports + +Internals + +Code + +Data diff --git a/doc/instructions b/doc/instructions new file mode 100644 index 0000000..7f2515b --- /dev/null +++ b/doc/instructions @@ -0,0 +1,72 @@ +Instructions +============ + +Bitwise +------- + +and - bitwise and +andi - bitwise and immediate +or - bitwise or +ori - bitwise or immediate +not - bitwise not +rot - rotate +roti - rotate immediate +rotis - rotate immediate signed +rots - rotate signed +sh - shift +shi - shift immedate +shis - shift immediate signed +shs - shift signed +xor - bitwise xor +xori - bitwise xor immediate + +Arithmetic +---------- + +add - add +addi - add immediate +div - divide +divi - divide immediate +divis - divide immediate signed +divs - divide signed +mod - modulus +modi - modulus immediate +modis - modulus immediate signed +mods - modulus signed +mul - multiply +muli - multiply immedate +mulis - multiply immediate signed +muls - multiply signed +neg - negate +sub - subtract +subi - subtract immediate + +Control +------- + +call - call +calli - call immediate +goto - goto +gotoi - goto immediate +sys - system call + +Conditional +----------- + +ifeq - if equal +ifge - if greater than or equal +ifgt - if greater than +ifle - if less than or equal +iflt - if less than +ifne - if not equal + +Load/Store +---------- + +lb - load byte +li - load immediate +lip - load instruction pointer +lui - load upper immediate +lw - load word +stb - store byte +stw - store word diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..1657d43 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,47 @@ +SH ?= sh +CFLAGS ?= -Wall -s -O3 + +OBJECTS = turbovm-asm.tab.c turbovm-asm.tab.h turbovm-asm.y \ + turbovm-asm.l turbovm-asm.yy.c +TARGETS = opcodes.h turbovm turbovm-asm turbovm-disasm turbovm-to-c + +all : $(TARGETS) + +clean : + -rm $(OBJECTS) + +distclean : clean + -rm $(TARGETS) + +opcodes.h : opcodes.dat + $(SH) make-opcodes-h + +turbovm : turbovm.c opcodes.h + $(CC) $(CFLAGS) turbovm.c -o turbovm + +turbovm-asm : turbovm-asm.c turbovm-asm.yy.c turbovm-asm.tab.c turbovm-asm.tab.h opcodes.h + $(CC) $(CFLAGS) turbovm-asm.c -o turbovm-asm + +# Empty rule to prevent GNU make from overwriting turbovm-asm.c +turbovm-asm.c : + touch turbovm-asm.c + +turbovm-asm.l : opcodes.dat make-turbovm-asm-l + $(SH) make-turbovm-asm-l + +turbovm-asm.tab.c turbovm-asm.tab.h : turbovm-asm.y + $(YACC) -d turbovm-asm.y + mv y.tab.c turbovm-asm.tab.c + mv y.tab.h turbovm-asm.tab.h + +turbovm-asm.y : opcodes.dat make-turbovm-asm-y + $(SH) make-turbovm-asm-y + +turbovm-asm.yy.c : turbovm-asm.l + $(LEX) turbovm-asm.l + mv lex.yy.c turbovm-asm.yy.c + +turbovm-disasm : turbovm-disasm.c opcodes.h + $(CC) $(CFLAGS) turbovm-disasm.c -o turbovm-disasm + +.PHONY : all clean distclean diff --git a/src/make-opcodes-h b/src/make-opcodes-h new file mode 100755 index 0000000..ea3e0c7 --- /dev/null +++ b/src/make-opcodes-h @@ -0,0 +1,14 @@ +#! /bin/sh + +cat < opcodes.h +#ifndef OPCODES_H +#define OPCODES_H + +EOT + +sed 's/#.*//' < opcodes.dat | tr a-z A-Z | awk '$0 != "" { print "#define TVM_OP_" $1 " 0x" $2 }' >> opcodes.h + +cat <> opcodes.h + +#endif /* ndef OPCODES_H */ +EOT diff --git a/src/make-turbovm-asm-l b/src/make-turbovm-asm-l new file mode 100755 index 0000000..b476f10 --- /dev/null +++ b/src/make-turbovm-asm-l @@ -0,0 +1,36 @@ +#! /bin/sh + +# Write prologue +cat < turbovm-asm.l +%{ +#include +#include +#include +#include "turbovm-asm.tab.h" + +static unsigned lexer_line = 1; +%} + +%% +[A-Za-z][0-9A-Za-z]*: yytext[strlen(yytext) - 1] = 0; yylval = (int) strdup(yytext); return LABEL; +[0-9]+ yylval = strtol(yytext, NULL, 0); return NUMBER; +0x[0-9A-Fa-f]+ yylval = strtol(yytext, NULL, 16); return NUMBER; +\$[A-Za-z][0-9A-Za-z]* yylval = (int) strdup(&yytext[1]); return REFERENCE; +r[0-9]+ yylval = strtol(&yytext[1], NULL, 10); return REGISTER; +[ \t] return SPACE; +\n lexer_line++; return EOL; +#[^\n]* /* Skip comments */ +EOT + +# Add rules for the instructions +for op in $(sed 's/#.*//' < opcodes.dat | awk '$0 != "" { print $1 }') +do + token="OP_$(echo "$op" | tr [a-z] [A-Z])" + echo "$op return $token;" +done >> turbovm-asm.l + +# Write epilogue +cat <> turbovm-asm.l +. fprintf(stderr, "Bogus input on line %u: %s\n", lexer_line, yytext); exit(1); +%% +EOT diff --git a/src/make-turbovm-asm-y b/src/make-turbovm-asm-y new file mode 100755 index 0000000..7db4eae --- /dev/null +++ b/src/make-turbovm-asm-y @@ -0,0 +1,169 @@ +#! /bin/sh + +# Function to write code for handling instructions +instructions() { + rulename="$1" + shift + echo -n "$rulename :" + indent='' + for op in "$@" + do + echo "$indent OP_${op} { \$\$ = TVM_OP_${op}; }" + indent="${rulename//?/ } |" + done + echo "${rulename//?/ } ;" + echo '' +} + +# Write prologue +cat < turbovm-asm.y +%{ +#define YYSTYPE intptr_t + +static unsigned parser_line = 1; + +static char *reference = NULL; + +void yyerror(const char *msg) { + fprintf(stderr, "Parse error on line %u: %s\n", parser_line, msg); + exit(1); +} + +int yywrap() { return 1; } +%} + +%token EOL +%token LABEL +%token NUMBER +%token REFERENCE +%token REGISTER +%token SPACE +EOT + +# Generate instruction token names +instrs='' +for op in $(sed 's/#.*//' < opcodes.dat | awk '$0 != "" { print $1 }') +do + token="OP_$(echo "$op" | tr [a-z] [A-Z])" + if [ x"$instrs" = x ] + then + instrs="$token" + else + instrs="$instrs $token" + fi +done + +# Generate token declarations for all instructions +for instr in $instrs +do + echo "%token $instr" +done >> turbovm-asm.y + +# Write rules +cat <<'EOT' >> turbovm-asm.y + +%% +program : lines ; + +lines : /* empty */ + | lines line { parser_line++; } + ; + +line : opt_space opt_label opt_instruction opt_space EOL ; + +opt_label : /* empty */ + | label + ; + +label : LABEL { register_label_address((char*) $1, current_address); }; + +opt_instruction : /* empty */ + | instruction + ; + +instruction : i_instr + | r_instr + | ri_instr + | rr_instr + | rrr_instr + ; + +i_instr : i_op space number { + if(reference) emit_i_ref($1, reference); + else emit_i($1, $3); + } + ; + +r_instr : r_op space register { emit_r($1, $3); }; + +ri_instr : ri_op space register space number { + if(reference) emit_ri_ref($1, $3, reference); + else emit_ri($1, $3, $5); + } + ; + +rr_instr : rr_op space register space register { emit_rr($1, $3, $5); }; + +rrr_instr : rrr_op space register space register space register { emit_rrr($1, $3, $5, $7); }; + +EOT + +# Write code instructions +instructions "i_op" CALLI GOTOI SYS >> turbovm-asm.y +instructions "r_op" GOTO >> turbovm-asm.y +instructions "ri_op" \ + ADDI \ + ANDI \ + DIVI \ + DIVIS \ + LI \ + LUI \ + MODI \ + MODIS \ + MULI \ + MULIS \ + ORI \ + ROTI \ + SHI \ + SHIS \ + SUBI \ + XORI \ + >> turbovm-asm.y +instructions "rr_op" CALL IFEQ IFGE IFGT IFLE IFLT IFNE LR >> turbovm-asm.y +instructions "rrr_op" \ + ADD \ + AND \ + DIV \ + DIVS \ + LB \ + LW \ + MOD \ + MODS \ + MUL \ + MULS \ + OR \ + ROT \ + SH \ + SHS \ + STB \ + STW \ + SUB \ + XOR \ + >> turbovm-asm.y + +cat <<'EOT' >> turbovm-asm.y +number : NUMBER { reference = NULL; } + | REFERENCE { reference = (char*) $1; } + ; + +register : REGISTER ; + +opt_space : /* empty */ + | space + ; + +space : SPACE + | space SPACE + ; +%% +EOT diff --git a/src/opcodes.dat b/src/opcodes.dat new file mode 100644 index 0000000..edfc1ac --- /dev/null +++ b/src/opcodes.dat @@ -0,0 +1,58 @@ +# op code description + +and 00 # bitwise and +or 01 # bitwise or +xor 02 # bitwise xor +rot 03 # rotate +sh 04 # shift +shs 05 # shift signed +lb 06 # load byte +lr 07 # load register +lw 08 # load word +stb 09 # store byte +stw 0a # store word + +andi 10 # bitwise and immediate +ori 11 # bitwise or immediate +xori 12 # bitwise xor immediate +roti 13 # rotate immediate +shi 14 # shift immediate +shis 15 # shigt immediate signed +li 16 # load immediate +lui 17 # load upper immediate + +add 20 # add +div 21 # divide +divs 22 # divide signed +mod 23 # modulus +mods 24 # modulus signed +mul 25 # multiply +muls 26 # multiply signed +sub 27 # subtract + +addi 30 # add immediate +divi 31 # divide immediate +divis 32 # divide immediate signed +modi 33 # modulus immediate +modis 34 # modulus immediate signed +muli 35 # multiply immediate +mulis 36 # multiply immediate signed +subi 37 # subtract immediate + +call 40 # call +goto 41 # goto + +calli 50 # call immediate +gotoi 51 # goto immediate +sys 52 # system call + +ifeq 60 # if equal +ifge 61 # if greater than or equal +ifges 62 # if greater than or equal signed +ifgt 63 # if greater than +ifgts 64 # if greater than signed +ifle 65 # if less than or equal +ifles 66 # if less than or equal signed +iflt 67 # if less than +iflts 68 # if less than signed +ifne 69 # if not equal diff --git a/src/turbovm-asm.c b/src/turbovm-asm.c new file mode 100644 index 0000000..fd02521 --- /dev/null +++ b/src/turbovm-asm.c @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include "opcodes.h" + +#define ADDRESS_T unsigned +#define BYTE uint8_t +#define NEW(NAME, TYPE) TYPE *NAME = (TYPE *) malloc(sizeof(TYPE)) + +/** Instruction formats. */ +#define FMT_I 1 +#define FMT_R 2 +#define FMT_RI 3 +#define FMT_RR 4 +#define FMT_RRR 5 + +/** A linked list, terminated by NULL. Each element consists of a + * pointer to some data, and a pointer to the next element. + */ +struct list { + void *data; + struct list *next; +}; + +/** Information about an instruction that needs to be patched up. + * It contains the address at which the instruction resides, + * the format of the instruction, and its operands. + */ +struct instruction { + ADDRESS_T address; + int format; + int opcode, reg1; +}; + +/** Information about a label. It contains the label's name, its + * address, and a list of instructions that need to be patched up once + * the label's address is known, and a flag telling whether the label's + * address is known. + */ +struct label { + char *name; + int known; + ADDRESS_T address; + struct list *instructions; +}; + +/** All labels in the program. */ +static struct list *labels = NULL; + +/** Address of next instruction. */ +static ADDRESS_T current_address; + +/** Code that has been emitted so far. */ +static BYTE *code = NULL; + +/** Size of code block. */ +static ADDRESS_T code_size = 0; + +/* Forward declarations, yay! */ +static BYTE *insert_word(ADDRESS_T address, uint32_t word); +static struct instruction *make_i_instruction(ADDRESS_T address, int opcode); +static struct instruction *make_ri_instruction(ADDRESS_T address, int opcode, int reg); + +/** Add an instruction to a label. */ +static struct instruction *add_instruction_to_label(struct label *x, struct instruction *y) { + NEW(z, struct list); + if(z) { + z->data = y; + z->next = x->instructions; + x->instructions = z; + return y; + } else return NULL; +} + +/** Add a new i-instruction to a label. */ +static struct instruction *add_i_instruction_to_label(struct label *x, ADDRESS_T address, int opcode) { + struct instruction *y = make_i_instruction(address, opcode); + if(y) { + if(add_instruction_to_label(x, y)) return y; + else { + free(y); + return NULL; + } + } else return NULL; +} + +/** Add a new ri-instruction to a label. */ +static struct instruction *add_ri_instruction_to_label(struct label *x, ADDRESS_T address, int opcode, int reg) { + struct instruction *y = make_ri_instruction(address, opcode, reg); + if(y) { + if(add_instruction_to_label(x, y)) return y; + else { + free(y); + return NULL; + } + } else return NULL; +} + +/** Insert an instruction of i-format at a given address. */ +static void insert_i(ADDRESS_T address, int opcode, int imm) { + uint32_t word = 0; + BYTE *p = (BYTE*) &word; + p[0] = (BYTE) opcode; + p[2] = (BYTE) (imm >> 8); + p[3] = (BYTE) (imm & 0xff); + insert_word(address, word); +} + +/** Insert an instruction of r-format at a given address. */ +static void insert_r(ADDRESS_T address, int opcode, int reg) { + uint32_t word = 0; + BYTE *p = (BYTE*) &word; + p[0] = (BYTE) opcode; + p[1] = (BYTE) reg; + insert_word(address, word); +} + +/** Insert an instruction of ri-format at a given address. */ +static void insert_ri(ADDRESS_T address, int opcode, int reg, int imm) { + uint32_t word = 0; + BYTE *p = (BYTE*) &word; + p[0] = (BYTE) opcode; + p[1] = (BYTE) reg; + p[2] = (BYTE) (imm >> 8); + p[3] = (BYTE) (imm & 0xff); + insert_word(address, word); +} + +/** Insert an instruction of rr-format at a given address. */ +static void insert_rr(ADDRESS_T address, int opcode, int reg1, int reg2) { + uint32_t word = 0; + BYTE *p = (BYTE*) &word; + p[0] = (BYTE) opcode; + p[1] = (BYTE) reg1; + p[2] = (BYTE) reg2; + insert_word(address, word); +} + +/** Insert an instruction of rrr-format at a given address. */ +static void insert_rrr(ADDRESS_T address, int opcode, int reg1, int reg2, int reg3) { + uint32_t word = 0; + BYTE *p = (BYTE*) &word; + p[0] = (BYTE) opcode; + p[1] = (BYTE) reg1; + p[2] = (BYTE) reg2; + p[3] = (BYTE) reg3; + insert_word(address, word); +} + + +/** Find a label by name. + * Returns the label if found, NULL if not found. + */ +static struct label *find_label(char *name) { + struct label *y; + struct list *x = labels; + while(x) { + y = x->data; + if(!strcmp(name, y->name)) return y; + x = x->next; + } + return NULL; +} + +/** Free a list. */ +void free_list(struct list *x) { + struct list *next; + while(x) { + free(x->data); + next = x->next; + free(x); + x = next; + } +} + +/** Insert a word into the code. */ +static BYTE *insert_word(ADDRESS_T address, uint32_t word) { + ADDRESS_T newsize; + if(address + 4 > code_size) { + newsize = (address / 0x1000 + 1) * 0x1000; + code = realloc(code, newsize); + if(!code) return NULL; + code_size = newsize; + } + + memcpy(&code[address], &word, 4); + return code; +} + +/** Make an i-format instruction. */ +static struct instruction *make_i_instruction(ADDRESS_T address, int opcode) { + NEW(x, struct instruction); + if(x) { + x->address = address; + x->format = FMT_I; + x->opcode = opcode; + } + return x; +} + +/** Make a new label. */ +static struct label *make_label(char *name, int known, ADDRESS_T address, struct list *instructions) { + NEW(x, struct label); + if(x) { + x->name = name; + x->known = known; + x->address = address; + x->instructions = instructions; + } + return x; +} + +/** Make a new label with a known address. */ +static struct label *make_known_label(char *name, ADDRESS_T address) { + return make_label(name, 1, address, NULL); +} + +/** Make a new label without a known address, but with an instruction to be patched up. */ +static struct label *make_label_with_instruction(char *name, struct instruction *instruction) { + struct label *y; + NEW(x, struct list); + if(x) { + x->data = instruction; + x->next = NULL; + y = make_label(name, 0, 0, x); + if(y) return y; + else { + free(x); + return NULL; + } + } else return NULL; +} + +/** Make a new uknonwn label with an i-instruction to be patched. */ +static struct label *make_label_with_i_instruction(char *name, ADDRESS_T address, int opcode) { + struct label *y; + struct instruction *x = make_i_instruction(address, opcode); + if(x) { + y = make_label_with_instruction(name, x); + if(y) return y; + else { + free(x); + return NULL; + } + } else return NULL; +} + +/** Make a new uknonwn label with an ri-instruction to be patched. */ +static struct label *make_label_with_ri_instruction(char *name, ADDRESS_T address, int opcode, int reg) { + struct label *y; + struct instruction *x = make_ri_instruction(address, opcode, reg); + if(x) { + y = make_label_with_instruction(name, x); + if(y) return y; + else { + free(x); + return NULL; + } + } else return NULL; +} + +/** Make an ri-format instruction. */ +static struct instruction *make_ri_instruction(ADDRESS_T address, int opcode, int reg) { + NEW(x, struct instruction); + if(x) { + x->address = address; + x->format = FMT_RI; + x->opcode = opcode; + x->reg1 = reg; + } + return x; +} + +/** Patch an instruction. */ +void patch_instruction(struct instruction *x, ADDRESS_T address) { + switch(x->format) { + case FMT_I: + switch(x->opcode) { + case TVM_OP_GOTOI: + insert_i(x->address, x->opcode, address - x->address); + break; + default: + fprintf(stderr, "Don't know how to patch instruction with opcode %02x\n", x->opcode); + } + break; + case FMT_RI: + switch(x->opcode) { + case TVM_OP_CALLI: + insert_ri(x->address, x->opcode, x->reg1, address - x->address); + break; + default: + fprintf(stderr, "Don't know how to patch instruction with opcode %02x\n", x->opcode); + } + break; + default: + fprintf(stderr, "Don't know how to patch instruction of format %u\n", x->format); + } +} + +/** Patch instructions. */ +void patch_instructions(struct list *x, ADDRESS_T address) { + while(x) { + patch_instruction((struct instruction*) x->data, address); + x = x->next; + } +} + +/** Register a label. */ +struct list *register_label(struct label *x) { + NEW(y, struct list); + if(y) { + y->data = x; + y->next = labels; + labels = y; + } + return y; +} + +/** Create a label with an i-instruction to be patched and register the label. */ +struct list *register_label_with_i_instruction(char *name, ADDRESS_T address, int opcode) { + struct list *y; + struct label *x = make_label_with_i_instruction(name, address, opcode); + if(x) { + y = register_label(x); + if(y) return y; + else { + free(x); + return NULL; + } + } else return NULL; +} + +/** Create a label with an ri-instruction to be patched and register the label. */ +struct list *register_label_with_ri_instruction(char *name, ADDRESS_T address, int opcode, int reg) { + struct list *y; + struct label *x = make_label_with_ri_instruction(name, address, opcode, reg); + if(x) { + y = register_label(x); + if(y) return y; + else { + free(x); + return NULL; + } + } else return NULL; +} + +/** Register a label's address. + * This will create and register the label if it didn't exist yet, + * and patch up any instructions as necessary. + */ +struct label *register_label_address(char *name, ADDRESS_T address) { + struct label *x = find_label(name); + if(x) { + x->address = address; + x->known = 1; + patch_instructions(x->instructions, address); + free_list(x->instructions); + x->instructions = NULL; + } else { + x = make_known_label(name, address); + if(x) { + register_label(x); + } + } + return x; +} + +static void emit_i(int op, int imm) { + insert_i(current_address, op, imm); + current_address += 4; +} + +static void emit_i_ref(int op, char *reference) { + struct label *label = find_label(reference); + if(label) { + /* Label found, do we know its address yet? */ + if(label->known) { + /* Yes. Good! Emit instruction. */ + emit_i(op, label->address - current_address); + } else { + /* No. Too bad. Schedule instruction for patching. */ + add_i_instruction_to_label(label, current_address, op); + current_address += 4; + } + } else { + /* Label not found. */ + register_label_with_i_instruction(reference, current_address, op); + current_address += 4; + } +} + +/** Insert an r-instruction at the current address. */ +static void emit_r(int op, int reg) { + insert_r(current_address, op, reg); + current_address += 4; +} + +/** Insert an ri-instruction at the current address. */ +static void emit_ri(int op, int reg, int imm) { + insert_ri(current_address, op, reg, imm); + current_address += 4; +} + +/** Insert an ri-instruction that refers to a label at the current address. + * If the address of the label is not known yet, the instruction is added + * to the label's patch list. + */ +static void emit_ri_ref(int op, int reg, char *reference) { + struct label *label = find_label(reference); + if(label) { + /* Label found, do we know its address yet? */ + if(label->known) { + /* Yes. Good! Emit instruction. */ + emit_ri(op, reg, label->address - current_address); + } else { + /* No. Too bad. Schedule instruction for patching. */ + add_ri_instruction_to_label(label, current_address, reg, op); + current_address += 4; + } + } else { + /* Label not found. */ + register_label_with_ri_instruction(reference, current_address, op, reg); + current_address += 4; + } +} + +/** Insert an rr-instruction at the current address. */ +static void emit_rr(int op, int reg1, int reg2) { + insert_rr(current_address, op, reg1, reg2); + current_address += 4; +} + +/** Insert an rrr-instruction at the current address. */ +static void emit_rrr(int op, int reg1, int reg2, int reg3) { + insert_rrr(current_address, op, reg1, reg2, reg3); + current_address += 4; +} + +#include "turbovm-asm.yy.c" +#include "turbovm-asm.tab.c" + +int main(int argc, char **argv) { + yyparse(); + fwrite(code, 1, current_address, stdout); + return 0; +} diff --git a/src/turbovm-disasm.c b/src/turbovm-disasm.c new file mode 100644 index 0000000..0142ccd --- /dev/null +++ b/src/turbovm-disasm.c @@ -0,0 +1,149 @@ +#include "turbovm.h" +#include "opcodes.h" +#include +#include +#include +#include + +#define BYTE uint8_t +#define IMM_T int16_t +#define WORD TVM_WORD + +#define OPCODE instr[0] +#define IMM (((IMM_T) (instr[2] << 8)) | ((IMM_T) instr[3])) + +#define I(OP) case TVM_OP_ ## OP: \ + fprintf(stream, "L%010u: %s %d\n", \ + ip, strtolower(#OP), IMM); \ + break + +#define R(OP) case TVM_OP_ ## OP: \ + fprintf(stream, "L%010u: %s r%u\n", \ + ip, strtolower(#OP), instr[1]); \ + break + +#define RI(OP) case TVM_OP_ ## OP: \ + fprintf(stream, "L%010u: %s r%u %d\n", \ + ip, strtolower(#OP), instr[1], IMM); \ + break + +#define RR(OP) case TVM_OP_ ## OP: \ + fprintf(stream, "L%010u: %s r%u r%u\n", \ + ip, strtolower(#OP), instr[1], instr[2]); \ + break + +#define RRR(OP) case TVM_OP_ ## OP: \ + fprintf(stream, "L%010u: %s r%u r%u r%u\n", \ + ip, strtolower(#OP), instr[1], instr[2], instr[3]); \ + break + +/* Holy crappy code Batman! + * Make sure to strdup the result of this function before + * calling it again! + */ +static char *strtolower(const char *str) { + static char *result = NULL; + int i, len = strlen(str); + + result = (char*) realloc(result, len + 1); + if(result) { + for(i = 0; i < len; i++) { + result[i] = tolower(str[i]); + } + result[len] = 0; + } + + return result; +} + +void disassemble_to_stream(FILE *stream, BYTE *code, unsigned int code_size) { + unsigned int ip = 0; + BYTE *instr = code; + while(ip < code_size) { + switch(OPCODE) { + RRR(AND); + RRR(OR); + RRR(XOR); + RRR(SH); + RRR(SHS); + RRR(ROT); + RRR(LB); + RR(LR); + RRR(LW); + RRR(STB); + RRR(STW); + + RI(ANDI); + RI(ORI); + RI(XORI); + RI(SHI); + RI(ROTI); + RI(LI); + RI(LUI); + + RRR(ADD); + RRR(DIV); + RRR(DIVS); + RRR(MOD); + RRR(MODS); + RRR(MUL); + RRR(MULS); + RRR(SUB); + + RI(ADDI); + RI(DIVI); + RI(MODI); + RI(MODIS); + RI(MULI); + RI(SUBI); + + RR(CALL); + R(GOTO); + + RI(CALLI); + I(GOTOI); + I(SYS); + + RR(IFEQ); + RR(IFGE); + RR(IFGT); + RR(IFLE); + RR(IFLT); + RR(IFNE); + default: + fprintf(stderr, "Illegal opcode %02x at %08x\n", OPCODE, ip); + exit(255); + } + + instr += 4; + ip += 4; + } +} + +void disassemble(BYTE *code, unsigned int code_size) { + disassemble_to_stream(stdout, code, code_size); +} + +int main(int argc, char **argv) { + int n = 0x10000, code_size = 0; + BYTE *code = NULL; + + /* Load program */ + while(n == 0x10000) { + code = realloc(code, code_size + 0x10000); + if(!code) { + perror("realloc"); + exit(1); + } + n = fread(&(code[code_size]), 1, 0x10000, stdin); + if(ferror(stdin)) { + perror("fread"); + exit(1); + } + code_size += n; + } + + disassemble(code, code_size); + + return 0; +} diff --git a/src/turbovm-switch.c b/src/turbovm-switch.c new file mode 100644 index 0000000..a917fa5 --- /dev/null +++ b/src/turbovm-switch.c @@ -0,0 +1,150 @@ +#include "turbovm.h" +#include "opcodes.h" +#include +#include +#include + +#define BYTE uint8_t +#define IMM_T uint16_t +#define IMMS_T int16_t +#define WORD TVM_WORD + +#define OPCODE instr[0] +#define REG(N) reg[N] +#define REG1 REG(instr[1]) +#define REG2 REG(instr[2]) +#define REG3 REG(instr[3]) +#define IMM (((IMM_T) (instr[2] << 8)) | ((IMM_T) instr[3])) +#define IMMS (((IMMS_T) (instr[2] << 8)) | ((IMMS_T) instr[3])) +#define IMPL(OP, CODE) case TVM_OP_ ## OP: CODE; NEXT +#define BINOP(OP, SYM) IMPL(OP, REG1 = REG2 SYM REG3) +#define BINOPI(OP, SYM) IMPL(OP, REG1 SYM ## = IMM) +#define BINOPIS(OP, SYM) IMPL(OP, REG1 SYM ## = IMMS) +#define NEXT instr += 4; break + +struct turbovm { + WORD reg[256]; + BYTE *code; + WORD code_size; +}; + +static void syscall(struct turbovm *vm, IMM_T num) { + WORD *reg = vm->reg; + switch(num) { + case 1: + exit(REG(3) & 0xff); + break; + case 2: + printf("%d\n", REG(3)); + break; + default: + fprintf(stderr, "Illegal syscall: %u\n", num); + exit(1); + } +} + +static void run(struct turbovm *vm) { + BYTE *instr = vm->code; + WORD *reg = vm->reg; + while(1) { + switch(OPCODE) { + BINOP(AND, &); + BINOP(OR, |); + BINOP(XOR, ^); + IMPL(SH, REG1 = (REG3 < 0) ? REG2 >> -REG3 : REG2 << REG3); + IMPL(SHS, REG1 = (REG3 < 0) ? REG2 >> -REG3 : REG2 << REG3); + IMPL(ROT, REG1 = (REG3 < 0) + ? ((REG2 >> -REG3) | (REG2 << (TVM_WORDSIZE + REG3))) + : ((REG2 << REG3) | (REG2 >> (TVM_WORDSIZE - REG3)))); + IMPL(LB, REG1 = (WORD) *((BYTE *) (REG2 + REG3))); + IMPL(LR, REG1 = REG2); + IMPL(LW, REG1 = *((WORD *) (REG2 + REG3))); + IMPL(STB, *((BYTE *) (REG2 + REG3)) = (BYTE) (REG1 & 0xff)); + IMPL(STW, *((WORD *) (REG2 + REG3)) = REG1); + + BINOPI(ANDI, &); + BINOPI(ORI, |); + BINOPI(XORI, ^); + IMPL(SHI, REG1 = (IMM < 0) ? REG2 >> -IMM : REG2 << IMM); + IMPL(ROTI, REG1 = (IMM < 0) + ? ((REG2 >> -IMM) | (REG2 << (TVM_WORDSIZE + IMM))) + : ((REG2 << IMM) | (REG2 >> (TVM_WORDSIZE - IMM)))); + IMPL(LI, REG1 = IMM); + IMPL(LUI, REG1 = IMM << 16); + + BINOP(ADD, +); + BINOP(DIV, /); + BINOP(DIVS, /); + BINOP(MOD, %); + BINOP(MODS, %); + BINOP(MUL, *); + BINOP(MULS, *); + BINOP(SUB, -); + + BINOPI(ADDI, +); + BINOPI(DIVI, /); + BINOPIS(DIVIS, /); + BINOPI(MODI, %); + BINOPIS(MODIS, %); + BINOPI(MULI, *); + BINOPIS(MULIS, *); + BINOPI(SUBI, -); + + case TVM_OP_CALL: + REG1 = (WORD) instr + 4; instr = (BYTE*) REG2; break; + case TVM_OP_GOTO: + instr = (BYTE*) REG1; break; + + case TVM_OP_CALLI: + REG1 = (WORD) instr + 4; instr += IMMS; break; + case TVM_OP_GOTOI: + instr += IMMS; break; + IMPL(SYS, syscall(vm, IMM)); + + IMPL(IFEQ, if(REG1 != REG2) instr += 4); + IMPL(IFGE, if(REG1 < REG2) instr += 4); + IMPL(IFGT, if(REG1 <= REG2) instr += 4); + IMPL(IFLE, if(REG1 > REG2) instr += 4); + IMPL(IFLT, if(REG1 >= REG2) instr += 4); + IMPL(IFNE, if(REG1 == REG2) instr += 4); + + default: + fprintf(stderr, "Illegal opcode %02x at %08x\n", OPCODE, (WORD) (instr - vm->code)); + exit(255); + } + } +} + +int main(int argc, char **argv) { + int n = 0x10000; + struct turbovm *vm; + + /* Initialize vm */ + vm = (struct turbovm*) malloc(sizeof(struct turbovm)); + if(!vm) { + perror("malloc"); + exit(1); + } + memset(vm, 0, sizeof(struct turbovm)); + + /* Load program */ + while(n == 0x10000) { + vm->code = realloc(vm->code, vm->code_size + 0x10000); + if(!vm->code) { + perror("realloc"); + exit(1); + } + n = fread(&(vm->code[vm->code_size]), 1, 0x10000, stdin); + if(ferror(stdin)) { + perror("fread"); + exit(1); + } + vm->code_size += n; + } + + /* Run */ + run(vm); + + /* Should not be reached */ + return 1; +} diff --git a/src/turbovm-to-c.c b/src/turbovm-to-c.c new file mode 100644 index 0000000..36e191d --- /dev/null +++ b/src/turbovm-to-c.c @@ -0,0 +1,212 @@ +#include "turbovm.h" +#include "opcodes.h" +#include +#include +#include + +#define BYTE uint8_t +#define IMM_T uint16_t +#define IMMS_T int16_t +#define WORD TVM_WORD + +#define OPCODE code[p] +#define REG(N) N +#define REG1 REG(code[p + 1]) +#define REG2 REG(code[p + 2]) +#define REG3 REG(code[p + 3]) +#define IMM (((IMM_T) (code[p + 2] << 8)) | ((IMM_T) code[p + 3])) +#define IMMS (((IMMS_T) (code[p + 2] << 8)) | ((IMMS_T) code[p + 3])) +#define MATCH(OP) case TVM_OP_ ## OP: +#define EMIT(FMT) fprintf(stream, FMT, REG1, REG2, REG3, IMM, IMMS) +#define IMPL(OP, FMT) case TVM_OP_ ## OP: EMIT(FMT); NEXT +#define IF(OP, SYM) case TVM_OP_ ## OP: fprintf(stream, "if(TVM_REG(%1$u) " #SYM " TVM_REG(%2$u)) {\n", REG1, REG2); \ + *pptr = p + 4; \ + emit_instruction(stream, code, size, pptr); \ + fprintf(stream, " }\n"); \ + NEXT +#define BINOP(OP, SYM) IMPL(OP, "TVM_REG(%1$u) = TVM_REG(%2$u) " #SYM "TVM_REG(%3$u);\n") +#define BINOPI(OP, SYM) IMPL(OP, "TVM_REG(%1$u) " #SYM "= (TVM_IMM_T) 0x%4$x;\n") +#define BINOPIS(OP, SYM) IMPL(OP, "TVM_REG(%1$u) " #SYM "= (TVM_IMMS_T) 0x%5$x;\n") +#define NEXT break + +struct turbovm { + WORD reg[256]; + BYTE *code; + WORD code_size; +}; + +static void emit_instruction(FILE *stream, BYTE *code, WORD size, WORD *pptr) { + WORD p = *pptr; + fprintf(stream, " case %1$10u: \n L%1$010u: ", p); + switch(OPCODE) { + BINOP(AND, &); + BINOP(OR, |); + BINOP(XOR, ^); + IMPL(SH, "TVM_REG(%1$u) = (TVM_REG(%3$u) < 0) " + "? (TVM_REG(%2$u) >> -TVM_REG(%3$u)) " + ": (TVM_REG(%2$u) << TVM_REG(%3$u));\n"); + IMPL(SHS, "TVM_REG(%1$u) = (TVM_REG(%3$u) < 0) " + "? (TVM_REG(%2$u) >> -TVM_REG(%3$u)) " + ": (TVM_REG(%2$u) << TVM_REG(%3$u));\n"); + IMPL(ROT, "TVM_REG(%1$u) = (TVM_REG(%3$u) < 0) " + "? ((TVM_REG(%2$u) >> -TVM_REG(%3$u)) | (TVM_REG(%2$u) << (TVM_WORDSIZE + TVM_REG(%3$u))))" + ": ((TVM_REG(%2$u) << TVM_REG(%3$u)) | (TVM_REG(%2$u) >> (TVM_WORDSIZE - TVM_REG(%3$u))));\n"); + IMPL(LB, "TVM_REG(%1$u) = (TVM_WORD) *((BYTE *) (TWM_REG[%2$u) + TVM_REG(%3$u)))\n"); + IMPL(LR, "TVM_REG(%1$u) = TVM_REG(%2$u)\n"); + IMPL(LW, "TVM_REG(%1$u) = *((TVM_WORD *) (TVM_REG(%2$u) + TVM_REG(%3$u)));\n"); + IMPL(STB, "*((TVM_BYTE *) (TVM_REG(%2$u) + TVM_REG(%3$u))) = (TVM_BYTE) (TVM_REG(%1$u) & 0xff);\n"); + IMPL(STW, "*((TVM_WORD *) (TVM_REG(%2$u) + TVM_REG(%3$u))) = TVM_REG(%1$u);\n"); + + BINOPI(ANDI, &); + BINOPI(ORI, |); + BINOPI(XORI, ^); + IMPL(SHI, "TVM_REG(%1$u) = (0x%5$x < 0) ? TVM_REG(%2$u) >> -0x%5$x : TVM_REG(%2$u) << 0x%5$x;\n"); + IMPL(ROTI, "TVM_REG(%1$u) = (0x%5$x < 0) " + "? ((TVM_REG(%2$u) >> -0x%5$x) | (TVM_REG(%2$u) << (TVM_WORDSIZE + 0x%5$x)))" + ": ((TVM_REG(%2$u) << 0x%5$x) | (TVM_REG(%2$u) >> (TVM_WORDSIZE - 0x%5$x)));\n"); + IMPL(LI, "TVM_REG(%1$u) = 0x%4$x;\n"); + IMPL(LUI, "TVM_REG(%1$u) = 0x%4$x << 16;\n"); + + BINOP(ADD, +); + BINOP(DIV, /); + BINOP(DIVS, /); + BINOP(MOD, %); + BINOP(MODS, %); + BINOP(MUL, *); + BINOP(MULS, *); + BINOP(SUB, -); + + BINOPI(ADDI, +); + BINOPI(DIVI, /); + BINOPIS(DIVIS, /); + BINOPI(MODI, %); + BINOPIS(MODIS, %); + BINOPI(MULI, *); + BINOPIS(MULIS, *); + BINOPI(SUBI, -); + + case TVM_OP_CALL: + fprintf(stream, "TVM_REG(%u] = (TVM_WORD) %u; tvm->instr = (TVM_BYTE*) TVM_REG(%u]; break;\n", + REG1, p + 4, REG2); + NEXT; + case TVM_OP_GOTO: + fprintf(stream, "vm->instr = (TVM_WORD) TVM_REG(%u]; break;\n", REG1); + NEXT; + + case TVM_OP_CALLI: + fprintf(stream, "TVM_REG(%u] = (TVM_WORD) %u; goto L%010u;\n", + REG1, p + 4, p + IMMS); + NEXT; + case TVM_OP_GOTOI: + fprintf(stream, "goto L%010u;\n", p + IMMS); + NEXT; + case TVM_OP_SYS: + fprintf(stream, "vm->instr = (TVM_WORD) %d; syscall(vm, 0x%x);\n", p + 4, IMM); + NEXT; + + IF(IFEQ, ==); + IF(IFGE, >=); + IF(IFGT, >); + IF(IFLE, <=); + IF(IFLT, <); + IF(IFNE, !=); + default: + fprintf(stderr, "Illegal opcode %02x at %10u\n", OPCODE, p); + exit(255); + } +} + +void compile_to_stream(FILE *stream, BYTE *code, WORD size) { + WORD p; + fprintf(stream, + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "\n" + "#define TVM_BYTE uint8_t\n" + "#define TVM_IMM_T uint16_t\n" + "#define TVM_IMMS_T int16_t\n" + "#define TVM_WORD int32_t\n" + "#define TVM_WORDSIZE 32\n\n" + "#define TVM_REG(N) reg[N]\n" + "\n" + "struct turbovm {\n" + " TVM_WORD reg[256];\n" + " TVM_WORD instr;\n" + "};\n" + "\n" + "static void syscall(struct turbovm *vm, TVM_IMM_T num) {\n" + " TVM_WORD *reg = vm->reg;\n" + " switch(num) {\n" + " case 1:\n" + " exit(TVM_REG(3) & 0xff);\n" + " break;\n" + " case 2:\n" + " printf(\"%d\\n\", TVM_REG(3));\n" + " break;\n" + " default:\n" + " fprintf(stderr, \"Illegal syscall: %%u\\n\", num);\n" + " exit(1);\n" + " }\n" + "}\n" + "\n" + "struct turbovm *run(struct turbovm *vm) {\n" + " TVM_WORD *reg = vm->reg;\n" + " while(1) {\n switch(vm->instr) {\n"); + for(p = 0; p < size; p += 4) { + emit_instruction(stream, code, size, &p); + } + fprintf(stream, " default:\n" + " /* whoops! value out of range! */\n" + " fprintf(stderr, \"Invalid instruction pointer: %%u\\n\", vm->instr);" + " exit(0x55);\n" + " break;\n" + " }\n }\n}\n" + "\n" + "int main(int argc, char **argv) {\n" + " struct turbovm vm;\n" + " memset(&(vm.reg), 0, sizeof(vm.reg));\n" + " vm.instr = 0;\n" + " run(&vm);\n" + " /* Should not be reached. */\n" + " return 1;\n" + "}\n"); +} + +void compile(BYTE *code, WORD size) { + compile_to_stream(stdout, code, size); +} + +int main(int argc, char **argv) { + int n = 0x10000; + struct turbovm *vm; + + /* Initialize vm */ + vm = (struct turbovm*) malloc(sizeof(struct turbovm)); + if(!vm) { + perror("malloc"); + exit(1); + } + memset(vm, 0, sizeof(struct turbovm)); + + /* Load program */ + while(n == 0x10000) { + vm->code = realloc(vm->code, vm->code_size + 0x10000); + if(!vm->code) { + perror("realloc"); + exit(1); + } + n = fread(&(vm->code[vm->code_size]), 1, 0x10000, stdin); + if(ferror(stdin)) { + perror("fread"); + exit(1); + } + vm->code_size += n; + } + + /* Compile. */ + compile(vm->code, vm->code_size); + + return 0; +} diff --git a/src/turbovm.c b/src/turbovm.c new file mode 120000 index 0000000..07065ae --- /dev/null +++ b/src/turbovm.c @@ -0,0 +1 @@ +turbovm-switch.c \ No newline at end of file diff --git a/src/turbovm.h b/src/turbovm.h new file mode 100644 index 0000000..dc13117 --- /dev/null +++ b/src/turbovm.h @@ -0,0 +1,13 @@ +#ifndef TURBOVM_H +#define TURBOVM_H + +#include + +#ifndef INLINE +#define INLINE inline +#endif + +#define TVM_WORD int32_t +#define TVM_WORDSIZE 32 + +#endif /* ndef TURBOVM_H */ diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..bace6cc --- /dev/null +++ b/test/Makefile @@ -0,0 +1,20 @@ +OBJECTS = test.expected test.out +TARGETS = countdown.tvm test.tvm + +all : $(TARGETS) + +clean : + -rm $(OBJECTS) + +distclean : clean + -rm $(TARGETS) + +test : + sh test.sh + +.SUFFIXES : .tvm .tvma + +.tvma.tvm : + ../src/turbovm-asm < $< > $@ + +.PHONY : all clean distclean test diff --git a/test/countdown b/test/countdown new file mode 100755 index 0000000000000000000000000000000000000000..3234a5def1ea248b6f45b32120b810fd4c837fcd GIT binary patch literal 7676 zcwX&UeQX@X6`$R^voEn@{Dt|5Dd%twjuhYJoY+86AdcfCMot282p{;L+4CTS zSS~SrC0s5;G7VrFWEg)97-I=<17j2Lo1krs9>5-p?17K;FqtQa`u=#V&mRvr$75P% zk1w6@wXkv=3bbwGmIoJB{zV{tJU$Ng^<*zP z^T_WVo~^AVWbD~Fkd2(xUHP9v=HcGcv*R?dz6`B7fi~;q7wWS!&XvpU#CfzOTvxS8hJ_XK&%kY5wUoii>@tnSteRAm{ovQGLspzI^Y$K^;^2ELD02l#X^@((40-(RXvN zqar>&8w+*WQ_j1-dnxmY&^lJ{EwZ=gO&$1eBSc8CQ?G!dWsVjoQ#~@=XAqDLOvW}YuzST^KpHh zgA~`{y^t=1^a+S%yx#R(5B7gQcsY7=6GWJsuvQXSZDE}Oy_i1;X*I8h?=dtAJT4K3 zaxf`@wp@aA=RRidmksz16ZyY`4jJCP)ex6JY{{CkDdcVJ=veJ(f>+Qo&njQQ*Wzhe zv7#*y2()^dk|{N%#?@e2UFK<8ulB`)8s!9tne`9tXp?v6a|J)Ra{;0_`U9MbFl$cTd)F`j?zZj)`bw){5q_U(+s+Jt?#qLIT4KArt zl~h?$4bR4knleYl;tH_aOB@wtW%qy=J0R<3S?4M!U0rr}Sy#=y`dS86vH0Shc2ciQ`7kBab#L+1V?@ z-c!1bs=j4D)g-3HbYBQ{kRMap_W5YnJhDaq3nAQFxQ*!ljxwpi->~M%j=|MT6-m@r4I9sK`=1MSKx0EGBG^ zhOZrZ4F@iJ0g3dx?1-$9>|Q#P)|G2>Uh?y6;RN5BGOFWmaFbEAxnx@iWfDePLW?{jj!+ z0hgEsybkUw58Cu$iZ92wg@^mlqcYo{qP)z)%W(a;&&8$Vq|ElNA_%=n3-288&dJ67 z7324$g@cjATV)_ArmoQeCn1}ZR3vU8=6D6k1 z%KIVb;eSW(hf3Hu*m)Bc-f`d^{~X@WEId3gpZJ`1j}z8c+ z-e(MzUab$~8x|g(KlYbi?e`dGExbwKO_s7VkClhvG-2%{4^Qn=rStdqH^lU949I)l zQXsPL*?6148TVMQ(ETSHZw7dc7A$0++ISxUug&6DLI&@bBHm)?>@EuyvPv885b!4F z=|?^1iMz5^*s=W9QcvO$&v_0@x%)aoZl6b=;mbS>!9nr~7u$#9nj+R0oV!+i@81lz zSPHDR+XJqbm^wJC$-u1TO$KHP_Zpb}%X0>1-YL$d4t^^cH*hJ%2Mx^r)uRSpK=DZf zFQoXKfy*dfn-!ROCawnzFQS;vdx|S4oG|cp6i+@cFk4-Y8@P(%69%rPc-p}1jWYCt zz_k?bH!wTpPa3$6;wb}LS4zY;+VD*_d^52I2Yt&~v2_JRe2Wc#!-j9QVUGxTU>3q%&X8{>PBMVrd^Ywo{h&DbH-_ zJyJ2=Yr_X5)4wFSh8&X2@gVZ$!cT1YH5)!9nf@oq*OPNL`QNZT%bonEynQcWclHZl zioe3^SIKpx4yQcyXET+TlclmbAFE+LE|-hH5!@r2^AY)-fbmMYhvNrj)Bh`>zd^uw zR~_NX+PcXraIApK+ zd@}FFKqwfGTfVN~`6{hv`ujn#qI7KEx=rcs>e;R+fYukG`#KB&oA_j0)zz@CwXG#U zl8^-M0!lccMB<6QU|gYx0wtK)L+D{a31 z|NR)?VKS7+XnHuYTfYy7q2m z>xKtd6GQ&J`RC;=YF16j+=8LGhk_;(&G^2gX o*SCkzL0})_iYnQ^#aWxWZ +#include +#include +#include + +#define TVM_BYTE uint8_t +#define TVM_IMM_T uint16_t +#define TVM_IMMS_T int16_t +#define TVM_WORD int32_t +#define TVM_WORDSIZE 32 + +#define TVM_REG(N) reg[N] + +struct turbovm { + TVM_WORD reg[256]; + TVM_WORD instr; +}; + +static void syscall(struct turbovm *vm, TVM_IMM_T num) { + TVM_WORD *reg = vm->reg; + switch(num) { + case 1: + exit(TVM_REG(3) & 0xff); + break; + case 2: + printf("65536\n", TVM_REG(3)); + break; + default: + fprintf(stderr, "Illegal syscall: %u\n", num); + exit(1); + } +} + +struct turbovm *run(struct turbovm *vm) { + TVM_WORD *reg = vm->reg; + while(1) { + switch(vm->instr) { + case 0: + L0000000000: TVM_REG(3) = 0x5f5 << 16; + case 4: + L0000000004: TVM_REG(3) |= (TVM_IMM_T) 0xe100; + case 8: + L0000000008: if(TVM_REG(0) == TVM_REG(3)) { + case 12: + L0000000012: goto L0000000024; + } + case 16: + L0000000016: TVM_REG(3) -= (TVM_IMM_T) 0x1; + case 20: + L0000000020: goto L0000000008; + case 24: + L0000000024: TVM_REG(3) = 0x0; + case 28: + L0000000028: vm->instr = (TVM_WORD) 32; syscall(vm, 0x1); + default: + /* whoops! value out of range! */ + fprintf(stderr, "Invalid instruction pointer: %u\n", vm->instr); exit(0x55); + break; + } + } +} + +int main(int argc, char **argv) { + struct turbovm vm; + memset(&(vm.reg), 0, sizeof(vm.reg)); + vm.instr = 0; + run(&vm); + /* Should not be reached. */ + return 1; +} diff --git a/test/countdown.tvma b/test/countdown.tvma new file mode 100644 index 0000000..282208a --- /dev/null +++ b/test/countdown.tvma @@ -0,0 +1,24 @@ +#### Count down from 1000000000 to 0 + +## Initialize counter +#lui r3 15258 +#ori r3 51712 +lui r3 1525 +ori r3 57600 + +label0: +## If counter reached 0, exit loop +ifeq r0 r3 +gotoi $label1 + +## Decrement counter +subi r3 1 + +## Next iteration +gotoi $label0 + +label1: + +## exit(0) +li r3 0 +sys 1 diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..fd9fe13 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,20 @@ +#! /bin/sh + +# Extract expected output +awk '$2 == "EXPECT" { print $3 }' < test.tvma > test.expected + +# Assemble test program +../src/turbovm-asm < test.tvma > test.tvm + +# Run test program +../src/turbovm < test.tvm > test.out + +# Compare expected and obtained outputs +if cmp -s test.out test.expected +then + echo "Test passed" +else + echo "Expected and obtained output differ." + echo "Run diff -u test.out test.expected to see differences." + exit 1 +fi diff --git a/test/test.tvma b/test/test.tvma new file mode 100644 index 0000000..4760799 --- /dev/null +++ b/test/test.tvma @@ -0,0 +1,108 @@ +#### Test for TurboVM + +### This program performs various operations and prints the results +### Some script can then be run to compare the output to known good +### output. To aid writing such a script, the exected output of this +### program is contained in comments. + +# Test that r0 is initialized to 0 +lr r3 r0 +sys 2 +# EXPECT 0 + +# Test loading immediate values +li r3 1 +sys 2 +# EXPECT 1 +li r3 0x7fff +sys 2 +# EXPECT 32767 +li r3 0x8000 +sys 2 +# EXPECT 32768 +li r3 0xffff +sys 2 +# EXPECT 65535 +lui r3 1 +sys 2 +# EXPECT 65536 +lui r3 0x7fff +sys 2 +# EXPECT 2147418112 +lui r3 0x8000 +sys 2 +# EXPECT -2147483648 +lui r3 0xffff +sys 2 +# EXPECT -65536 +lui r3 1 +li r3 0xffff +sys 2 +# EXPECT 65535 + +# Test andi +li r3 0xffff +andi r3 0xffff +sys 2 +# EXPECT 65535 +andi r3 0xa5a5 +sys 2 +# EXPECT 42405 +andi r3 0x5a5a +sys 2 +# EXPECT 0 +andi r3 0xffff +sys 2 +# EXPECT 0 + +# Test ori +lr r3 r0 +ori r3 0x5a5a +sys 2 +# EXPECT 23130 +ori r3 0xa5a5 +sys 2 +# EXPECT 65535 +ori r3 0xffff +sys 2 +# EXPECT 65535 +ori r3 0 +sys 2 +# EXPECT 65535 + +# Test xori +lr r3 r0 +xori r3 0x5a5a +sys 2 +# EXPECT 23130 +xori r3 0xa5a5 +sys 2 +# EXPECT 65535 +xori r3 0x5a5a +sys 2 +# EXPECT 42405 +xori r3 0xa5a5 +sys 2 +# EXPECT 0 + +# Test addi +lr r3 r0 +addi r3 1 +sys 2 +# EXPECT 1 +addi r3 0xffff +sys 2 +# EXPECT 65536 +lui r3 0x7fff +ori r3 0xffff +addi r3 1 +sys 2 +# EXPECT -2147483648 +lui r3 0xffff +addi r3 0xffff +sys 2 +# EXPECT -1 + +# Exit +li r3 0 +sys 1 -- 2.11.4.GIT