/* qm - The Quadruple Machine Copyright (C) 2004 Tuukka Hastrup Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* TODO: * - octal and hex character escapes, multi-line strings * - more correct parser: parentheses * later: * - don't change input in place, garbles error messages * - compile several files on one invocation * - count header CRC32 * - reduce number of passes */ #include #include /* isspace, isdigit: */ #include /* strtoul: */ #include #include "types.h" #include "qm-ds.h" #include "symboltable.h" #include "util.h" #include "errors.h" #define MAXLINE 79 /* 79 characters */ #define READBUFSIZE (MAXLINE+2) /* plus newline and NUL */ #define MAXWORDS 8 /* 'if' instruction has 6 words, plus flag ... plus extra */ #define START_ADDR (4*sizeof(word_t)) /* to get past NULL */ int textsize, datasize, bsssize; static unsigned int lineno; static void write_header(FILE *file) { struct exe_hdr header; int ret; header.magic = QM_MAGIC; header.text_start = 0; header.text_length = textsize; header.data_start = textsize; header.data_length = datasize; header.bss_length = bsssize; header.start_addr = START_ADDR; header.crc32 = 0; ret = fwrite(&header, sizeof(header), 1, file); if (ret != 1) pfatal("Writing header"); } static void write_instruction(const word_t *instr, FILE *file) { int ret = fwrite(instr, sizeof(word_t), 4, file); if (ret != 4) pfatal("Writing instruction"); } static void write_data(word_t data, FILE *file) { int ret = fwrite(&data, sizeof(word_t), 1, file); if (ret != 1) pfatal("Writing data"); } static void write_string(char *string, int len, FILE *file) { int ret = fwrite(string, sizeof(char), len, file); if (ret != len) pfatal("Writing string"); } static void write_bytes(byte_t byte, int num, FILE *file) { int i; for (i = 0; i= 0) return flag; fatal("%u: Couldn't interpret extra word '%s' as a type flag.", lineno, word); } static void check_eol(const char *word) { if (!equals(word,"")) { fatal("%u: Extra word '%s' after the line.", lineno, word); } } static int get_register(const char *word) { if(equals(word,"AR")) return 0; else if (equals(word, "SP")) return 1; else if (equals(word, "FP")) return 2; else if (equals(word, "ZL")) return 3; else return -1; } static int get_mode(const char *word) { if (starts(word, "[FP+") || equals(word, "[FP]")) { return AM_FPPD; } else if (starts(word, "[FP-")) { return AM_FPND; } else if (starts(word, "[AR+") || equals(word, "[AR]")) { return AM_ARPD; } else if (starts(word, "[AR-")) { return AM_ARND; } else if (starts(word, "[SP+") || equals(word, "[SP]")) { return AM_SPPD; } else if (starts(word, "[SP-")) { return AM_SPND; } else if (starts(word, "[")) { return AM_ABS; } else { return AM_IMM; } } static int get_lmode(const char *word) { int mode = get_mode(word); if (mode == AM_IMM) fatal("%u: Immediate values can't be used on left side: '%s'", lineno, word); return mode; } static word_t get_label(const char *name) { if (get_register(name) >= 0) fatal("%u: Register '%s' used in place of a general operand.", lineno, name); return get_symbol(name); } static word_t get_value(char *word) { if (starts(word, "[FP+") || starts(word, "[FP-") || starts(word, "[AR+") || starts(word, "[AR-") || starts(word, "[SP+") || starts(word, "[SP-")) { char *rest; unsigned long num = strtoul(word+4, &rest, 10); if (!equals(rest, "]")) fatal("%u: Error in register displacement " "syntax: '%s'.", lineno, word); return num; } else if (equals(word, "[FP]") || equals(word, "[AR]") || equals(word, "[SP]")) { return 0; } else if (starts(word, "[")) { int len = strlen(word); if (word[len-1] != ']') fatal("%u: Closing bracket missing in " "indirect addressing: '%s'.", lineno, word); word[len-1] = '\0'; return get_label(word+1); } else { char *rest; unsigned long num = strtoul(word, &rest, 10); if (*rest == '\0') return num; else return get_label(word); } } static void unescape(char **s, char **tr) { (*s)++; /* get past the escaping character */ /* add to *tr the unescaped character */ if (**s == '"' ) **tr = '"'; else if (**s == '\'') **tr = '\''; else if (**s == '\\') **tr = '\\'; else if (**s == '\n') fatal("%u: Sorry, multi-line strings not implemented.", lineno); else if (**s == 'a') **tr = '\a'; else if (**s == 'b') **tr = '\b'; else if (**s == 'f') **tr = '\f'; else if (**s == 'n') **tr = '\n'; else if (**s == 'r') **tr = '\r'; else if (**s == 't') **tr = '\t'; else if (**s == 'v') **tr = '\v'; else if (**s == 'x') fatal("%u: Sorry, hex escapes not implemented.", lineno); else if (isdigit(**s)) fatal("%u: Sorry, octal escapes not implemented.", lineno); else if (isspace(**s)) fatal("%u: A blank character other than newline after \\.", lineno); else fatal("%u: Unknown escape sequence: '%c%c'", lineno, *(*s-1), **s); (*s)++; /* get past the escaped character */ (*tr)++; /* get past the unescaped character */ } /* 0. pass: find segment sizes 1. pass: find symbol values 2. pass: write text 3. pass: write data */ static void parse(int pass, FILE *source, FILE *object) { char *s,*label; char *words[MAXWORDS]; char buffer[READBUFSIZE]; unsigned int i, word; word_t i_text, i_data, i_bss; word_t objectaddr; word_t instr[4]; lineno = 0; for (i = 0;i")) op = OP_GT; else if(equals(words[2],">=")) op = OP_GE; else if(equals(words[2],"!=")) op = OP_NE; else fatal("%u: if: Unknown comparison " "operator '%s'.", lineno, words[2]); if (!equals(words[4], "goto")) fatal("%u: if: '%s' instead of " "'goto'", lineno, words[4]); instr[0] = MK_OPR(op,check_flag(words[6]),get_mode(words[1]), get_mode(words[3]), get_mode(words[5])); instr[1] = get_value(words[1]); instr[2] = get_value(words[3]); instr[3] = get_value(words[5]); write_instruction(instr, object); } } else if (equals(words[0], "syscall")) { i_text += 4*4; if (pass == 2) { word_t n = strtoul(words[1], NULL, 10); check_flag(words[2]); instr[0] = MK_OPR(OP_SYSCALL,0,AM_IMM,0,0); instr[1] = n; instr[2] = instr[3] = 0; write_instruction(instr, object); } } else if (equals(words[0], "goto")) { i_text += 4*4; if (pass == 2) { check_flag(words[2]); instr[0] = MK_OPR(OP_JMP,0,get_mode(words[1]), 0,0); instr[1] = get_value(words[1]); instr[2] = instr[3] = 0; write_instruction(instr, object); } } else if (equals(words[0], "noinstr")) { i_text += 0; /* objectaddr is right by default */ check_eol(words[1]); } else if (equals(words[0], "data")) { objectaddr = i_data; i_data += 4; if (pass == 3) { word_t n = strtoul(words[1], NULL, 10); check_eol(words[2]); write_data(n, object); } } else if (equals(words[0], "zero")) { objectaddr = i_bss; i_bss += 4; check_eol(words[1]); } else if (equals(words[0], "string")) { unsigned int len, pad; objectaddr = i_data; if (words[1][0] != '"') fatal("%u: string: opening quote " "missing: '%s'", lineno, words[1]); len = strlen(&words[1][1])-1; if (words[1][len+1] != '"') fatal("%u: string: closing quote " "missing: '%s'", lineno, words[1]); i_data += len; check_eol(words[2]); if (pass == 3) write_string(&words[1][1], len, object); pad = sizeof(word_t) - ((i_data-1) % sizeof(word_t))-1; assert (pad < sizeof(word_t)); i_data += pad; if (pass == 3) write_bytes(0, pad, object); assert (i_data % sizeof(word_t) == 0); } else if (equals(words[1], "<-")) { i_text += 4*4; if (pass == 2) { int lreg = get_register(words[0]); int rreg = get_register(words[2]); if (lreg == get_register("SP") && rreg == get_register("SP")) { int op; if (equals(words[3], "+")) { op = OP_INCSP; } else if (equals(words[3], "-")) { op = OP_DECSP; } else { fatal("%u: SP change syntax " "needs '+' or '-' " "instead of '%s'", lineno, words[3]); } check_eol(words[5]); instr[0] = MK_OPR(op,0,AM_IMM,0,0); instr[1] = strtoul(words[4], NULL, 10); instr[2] = instr[3] = 0; write_instruction(instr, object); } else if (lreg == rreg && lreg >= 0) { fatal("%u: Can't load %s from itself.", lineno, words[0]); } else if (lreg >= 0 && rreg >= 0) { int op; if (lreg > rreg) lreg -= 1; op = OP_MVARSP+rreg*3+lreg; check_eol(words[3]); instr[0] = MK_OPR(op,0,0,0,0); instr[1] = instr[2] = instr[3] = 0; write_instruction(instr, object); } else if (lreg >= 0) { int mode = get_mode(words[2]); check_eol(words[3]); instr[0] = MK_OPR(OP_LDAR+lreg,0,mode,0,0); instr[1] = get_value(words[2]); instr[2] = instr[3] = 0; write_instruction(instr, object); } else if (rreg >= 0) { check_eol(words[3]); instr[0] = MK_OPR(OP_STAR+rreg,0, get_lmode(words[0]), 0,0); instr[1] = get_value(words[0]); instr[2] = instr[3] = 0; write_instruction(instr, object); } else if (equals(words[2],"not")) { instr[0] = MK_OPR(OP_NOT,check_flag(words[4]), get_lmode(words[0]), get_mode(words[3]), 0); instr[1] = get_value(words[0]); instr[2] = get_value(words[3]); instr[3] = 0; write_instruction(instr, object); } else if (get_flag(words[3]) >= 0) { instr[0] = MK_OPR(OP_MOVE,check_flag(words[3]), get_lmode(words[0]), get_mode(words[2]), 0); instr[1] = get_value(words[0]); instr[2] = get_value(words[2]); instr[3] = 0; write_instruction(instr, object); } else { int op; if (equals(words[3],"+")) op = OP_ADD; else if (equals(words[3],"-")) op = OP_SUB; else if (equals(words[3],"*")) op = OP_MUL; else if (equals(words[3],"/")) op = OP_DIV; else if (equals(words[3],"mod")) op = OP_MOD; else if (equals(words[3],"and")) op = OP_AND; else if (equals(words[3],"or")) op = OP_OR; else if (equals(words[3],"xor")) op = OP_XOR; else fatal("%u: Unknown arithmetic " "operator '%s'.", lineno, words[3]); instr[0] = MK_OPR(op,check_flag(words[5]), get_lmode(words[0]), get_mode(words[2]), get_mode(words[4])); instr[1] = get_value(words[0]); instr[2] = get_value(words[2]); instr[3] = get_value(words[4]); write_instruction(instr, object); } } } else if (equals(words[0], "")) { /* Empty lines ignored */ } else { fatal("%u: Unknown instruction '%s'.", lineno, words[0]); } /* store label positions on 1. pass*/ if (pass == 1 && label != NULL) { if (get_register(label) >= 0) fatal("%u: Can't use register as a " "label: '%s'.", lineno, label); if (equals(words[0],"")) fatal("%u: Label can't refer to an " "empty line: '%s'", lineno, buffer); add_symbol(label, objectaddr); /* printf("Added symbol %s = %ld\n", label, objectaddr); */ } } if (!s && ferror(source)) pfatal("Reading line from source file"); /* the result of the 0. pass is segment sizes */ if (pass == 0) { if (i_text == START_ADDR) fatal("No instructions in source file."); textsize = i_text; datasize = i_data; bsssize = i_bss; write_header(object); } } int main(int argc, char **argv) { FILE *source = NULL, *object; char *objectname = NULL; int ret, i, got_o = (0 == 1); if (argc == 1) { printf("Usage: %s [-o objectfile] sourcefile\n", argv[0]); printf(" %s -V\n", argv[0]); return 5; } for (i = 1; i < argc; i++) { if (equals(argv[i],"-V")) { printf("assembler (%s) %s\n", PACKAGE, VERSION); exit(0); } else if (equals(argv[i],"-o")) { if (got_o) fatal("Option -o given twice."); got_o = (0 == 0); if (i+1 >= argc) fatal("Option -o missing object filename argument"); objectname = argv[i+1]; i++; } else { int len = strlen(argv[i]); if (source) fatal("Source file already open, " "can't have '%s' as well.", argv[i]); if (len < 2 || argv[i][len-2] != '.' || argv[i][len-1] != 's') fatal("Source filename '%s' " "doesn't end in '.s'", argv[i]); source = fopen(argv[i], "r"); if (!source) pfatal("Opening source file"); argv[i][len-1] = 'o'; objectname = argv[i]; } } if (!source) fatal("No source filename given."); object = fopen(objectname, "wb"); if (!object) pfatal("Opening object file"); parse(0, source, object); rewind(source); parse(1, source, object); rewind(source); parse(2, source, object); rewind(source); parse(3, source, object); ret = fclose(source); if (ret != 0) pfatal("Closing source file"); ret = fclose(object); if (ret != 0) pfatal("Closing object file"); print_symbols(); printf("\nText %d bytes, data %d bytes, bss %d bytes.\n", textsize, datasize, bsssize); printf("Binary size should be %d bytes.\n", 4*8+textsize+datasize); return 0; }