/* ************************************************************************** * * Utility program to optimize the output of the BCC compiler * * Module: copt.c * Purpose: Optimize BCC assembler output * Entries: main * * This program is based on an idea from Christopher W. Fraser. * ************************************************************************** * * Copyright (C) 1995,1996,1997 Gero Kuhlmann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* #include "utility.h" #include "../../headers/general.h" #include "../../headers/version.h" */ #include #include #include #include #define KEEPCOMMENTS #define MAXLINE 1024 #ifdef __BCC__ #define HASHSIZE 107 #else #define HASHSIZE 1999 #endif #define NOCHAR '\177' #define VARNUM 10 /* Struct containing each string of an input file */ struct line_s { char *text; struct line_s *prev; struct line_s *next; int comment_flg; }; /* Struct containing one rule */ struct rule_s { struct line_s *old; struct line_s *new; struct rule_s *next; }; /* Hash table to store strings in a space saving way */ struct hash_s { char *text; struct hash_s *next; }; /* * Global variables */ static struct rule_s *first = NULL; /* first rule */ static struct rule_s *last = NULL; /* last rule */ static struct line_s *infile = NULL; /* list of strings in input file */ static struct hash_s *htab[HASHSIZE]; /* string hash table */ static int hash_init = 0; /* flag if hash table initialized */ static char *vars[VARNUM]; /* variable table */ static char *progname; /* program name */ /* * Allocate memory and print error if none available */ static void *mymalloc(int size) { void *p; if ((p = malloc(size)) == NULL) { fprintf(stderr, "%s: no memory\n", progname); exit(1); } return(p); } /* * Insert a string into the hash table. If the string is already in there * just return the pointer to that string. */ static char *install(char *str, int slen) { struct hash_s *hp; char *chkstr; char *cp; unsigned int hashval; /* Clear the hashing table if not already done */ if (!hash_init) { for (hashval = 0; hashval < HASHSIZE; hashval++) htab[hashval] = NULL; hash_init++; } /* Get check string */ if (slen < 0) slen = strlen(str); chkstr = mymalloc(slen + 1); strncpy(chkstr, str, slen); chkstr[slen] = '\0'; /* Determine hashing value of string */ hashval = 0; for (cp = chkstr; *cp; cp++) hashval = hashval*75 + *cp; hashval %= HASHSIZE; /* Check if the string is already in the hashing table */ for (hp = htab[hashval]; hp != NULL; hp = hp->next) if (!strcmp(chkstr, hp->text)) { free(chkstr); return(hp->text); } /* String is not in hash table, so create a new entry */ hp = (struct hash_s *)mymalloc(sizeof(struct hash_s)); hp->text = chkstr; hp->next = htab[hashval]; htab[hashval] = hp; return(hp->text); } /* * Read one line from input file and skip all blanks at the beginning */ static char *readline(FILE *fp) { static char buf[MAXLINE]; char *cp; /* Read line from input file */ if (fgets(buf, MAXLINE-1, fp) == NULL) return(NULL); buf[MAXLINE-1] = '\0'; /* Delete trailing newline */ if ((cp = strchr(buf, '\n')) != NULL) *cp = '\0'; /* Delete leading white spaces */ for (cp = buf; *cp && isspace(*cp); cp++) ; if (cp != buf && *cp) memmove(buf, cp, strlen(cp) + 1); return(buf); } /* * Read a list of input lines. Terminate reading when the 'quit' character * has been found in the first column of the input line. All lines with the * 'comment' character in the first position will be skipped. * * If 'havequit' is non-null, then the boolean '*havequit' says whether we * saw an input line with the 'quit' character. */ static struct line_s *readlist(FILE *fp, int quit, int comment, int *havequit) { struct line_s *lp; struct line_s *first_line = NULL; struct line_s *last_line = NULL; char *cp; while ((cp = readline(fp)) != NULL) { if (quit != NOCHAR && quit == *cp) break; if (comment != NOCHAR && comment == *cp) if (quit != NOCHAR) continue; if (*cp == '\0') continue; lp = mymalloc(sizeof(struct line_s)); lp->text = install(cp, -1); lp->prev = last_line; lp->next = NULL; lp->comment_flg = (comment != NOCHAR && *cp == comment); if (first_line == NULL) first_line = lp; if (last_line != NULL) last_line->next = lp; last_line = lp; } if (havequit) *havequit = (cp != NULL); return(first_line); } /* * Read pattern file */ static void readpattern(char *rulesdir, char *filename) { static char path[MAXLINE]; int havequit; struct rule_s *rp; FILE *fp; /* Open pattern file */ if (rulesdir != NULL) sprintf(path, "%s/%s", rulesdir, filename); else sprintf(path, "%s", filename); if ((fp = fopen(path, "r")) == NULL) { fprintf(stderr, "%s: can't open pattern file %s\n", progname, path); exit(1); } /* Read every line of the pattern file */ while (!feof(fp)) { rp = (struct rule_s *)mymalloc(sizeof(struct rule_s)); rp->old = readlist(fp, '=', '#', &havequit); rp->new = readlist(fp, '\0', '#', NULL); if (rp->old == NULL || !havequit) { free(rp); break; } /* This put the rules into the table in reverse order; this is confusing * rp->next = first; first = rp; if (last == NULL) last = rp; */ rp->next = NULL; if (last) { last->next = rp; last = rp; } else { first = last = rp; } } /* Close pattern file */ (void)fclose(fp); } /* * Clear pattern list to allow for another run */ static void clearpattern(void) { struct rule_s *rp1, *rp2; struct line_s *lp1, *lp2; rp1 = first; while (rp1 != NULL) { /* Clear old rule text list */ lp1 = rp1->old; while (lp1 != NULL) { lp2 = lp1; lp1 = lp1->next; free(lp2); } /* Clear new rule text list */ lp1 = rp1->new; while (lp1 != NULL) { lp2 = lp1; lp1 = lp1->next; free(lp2); } /* Clear rule itself */ rp2 = rp1; rp1 = rp1->next; free(rp2); } first = NULL; last = NULL; } /* * Read input file */ static void readinfile(char *filename, int comment) { FILE *fp; fp = stdin; if (filename != NULL && (fp = fopen(filename, "r")) == NULL) { fprintf(stderr, "%s: can't open input file %s\n", progname, filename); exit(1); } infile = readlist(fp, NOCHAR, comment, NULL); if (fp != stdin) (void)fclose(fp); } #define NO_OP 0 #define ADD_OP 1 #define SUB_OP 2 #define MUL_OP 3 #define DIV_OP 4 #define SHL_OP 5 #define SHR_OP 6 long retval = 0; long num = 0; int sign = 1; int base = 10; int op = NO_OP; /* Apply operation to current numeric value */ static void doretval(void) { switch (op) { case NO_OP: retval = num * sign; break; case ADD_OP: retval += num * sign; break; case SUB_OP: retval -= num * sign; break; case MUL_OP: retval *= num * sign; break; case DIV_OP: retval /= num * sign; break; case SHL_OP: retval <<= num; break; case SHR_OP: retval >>= num; break; } op = NO_OP; num = 0; sign = 1; base = 10; } /* * Eval an expression into an integer number */ static long eval(char *str, int len) { char *cp, c; int state = 0; int i, varnum; retval = 0; num = 0; sign = 1; base = 10; op = NO_OP; /* Scan through whole string and decode it */ for (cp = str, i = 0; *cp && i < len; cp++, i++) { c = toupper(*cp); if (c == '-' && (state == 0 || state == 5)) { state = 1; sign = -1; } else if (c == '+' && (state == 0 || state == 5)) { state = 1; sign = 1; } else if (c == '%' && isdigit(*(cp + 1)) && (state < 2 || state == 5)) { state = 4; varnum = *(cp + 1) - '0'; if (vars[varnum] == NULL || i >= len) return(0); num = eval(vars[varnum], strlen(vars[varnum])); doretval(); cp++; i++; } else if (c == '$' && (state < 2 || state == 5)) { state = 2; base = 16; } else if (base == 10 && (c >= '0' && c <= '9') && (state <= 3 || state == 5)) { state = 3; num = num * 10 + (c - '0'); } else if (base == 16 && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) && (state <= 3 || state == 5)) { state = 3; num = num * 16 + (c >= 'A' ? c - '0' - 7 : c - '0'); } else if (c == ' ' && (state == 3 || state == 4 || state == 5)) { if (state == 3) { doretval(); state = 4; } } else if (strchr("+-*/<>", c) != NULL && (state == 3 || state == 4)) { if (state == 3) doretval(); state = 5; switch (c) { case '+': op = ADD_OP; break; case '-': op = SUB_OP; break; case '*': op = MUL_OP; break; case '/': op = DIV_OP; break; case '<': op = SHL_OP; break; case '>': op = SHR_OP; break; } } else return(0); } /* Check if the string has been terminated correctly */ if (state != 3 && state != 4) return(0); if (state == 3) doretval(); return(retval); } /* * Compare an infile string with a pattern string. If there is any variable * defined, it will be inserted into the variable list from the pattern * string. */ static int match(char *ins, char *pat) { char *cp, *oldpat; long val; int varnum; int len = 0; while (*ins && *pat) { if (pat[0] != '%') { if (*pat++ != *ins++) return(0); else continue; } if (pat[1] == '%') { /* '%%' actually means '%' */ if (*ins != '%') return(0); pat += 2; } else if ((pat[1] == '*' || isdigit(pat[1]))) { /* Copy variable text into vars array */ pat += 2; for (cp = ins; *ins && !match(ins, pat); ins++) ; if (pat[-1] == '*') continue; len = ins - cp; varnum = pat[-1] - '0'; if (vars[varnum] == NULL) vars[varnum] = install(cp, len); else if (strlen(vars[varnum]) != len || strncmp(vars[varnum], cp, len)) return(0); } else if (pat[1] == '[') { /* Copy only specific variable text into vars array */ if ((cp = strchr(pat + 2, ']')) == NULL || (*(cp + 1) != '*' && !isdigit(*(cp + 1)))) { if (*ins != '[') return(0); pat += 2; continue; } oldpat = pat + 1; pat = cp + 2; /* Seperate allowable patterns and compare them with ins */ while (*oldpat && *oldpat != ']') { oldpat++; len = strcspn(oldpat, "|]"); if (!strncmp(ins, oldpat, len)) break; oldpat += len; } if (!*oldpat || *oldpat == ']') return(0); ins += len; if (!match(ins, pat)) return(0); /* Install new string into variable table */ if (*(cp + 1) == '*') continue; varnum = *(cp + 1) - '0'; if (vars[varnum] == NULL) vars[varnum] = install(oldpat, len); else if (strlen(vars[varnum]) != len || strncmp(vars[varnum], oldpat, len)) return(0); } else if (pat[1] == '!') { /* Match only if the pattern string is not found */ if (pat[2] != '[' || (cp = strchr(pat + 3, ']')) == NULL) { if (*ins != '!') return(0); pat += 3; continue; } oldpat = pat + 2; pat = cp + 1; /* Seperate allowable patterns and compare them with ins */ while (*oldpat && *oldpat != ']') { oldpat++; len = strcspn(oldpat, "|]"); if (!strncmp(ins, oldpat, len)) return(0); oldpat += len; } } else if (pat[1] == '(') { /* Match ins with expression */ if ((cp = strchr(pat + 2, ')')) == NULL) { if (*ins != '(') return(0); pat += 2; continue; } oldpat = pat + 2; pat = cp + 1; len = cp - oldpat; val = eval(oldpat, len); for (cp = ins; *ins && !match(ins, pat); ins++) ; len = ins - cp; if (val != eval(cp, len)) return(0); } else /* Bad % format cannot match */ return(0); } return(*ins == *pat); } /* * Substitute variables in a string */ static char *subst(char *pat) { char buf[MAXLINE]; char *cp, *cp1, *cp2, *varptr; long num; int i = 0; int j, pos; while (*pat) if (pat[0] == '%' && isdigit(pat[1])) { /* Substitute with value of variable */ cp = vars[pat[1] - '0']; while (cp != NULL && *cp) { buf[i++] = *cp++; if (i >= MAXLINE - 1) { fprintf(stderr, "%s: line too long\n", progname); exit(1); } } pat += 2; } else if (pat[0] == '%' && pat[1] == '(') { /* Substitute with expression */ cp = pat + 2; if ((pat = strchr(cp, ')')) == NULL || pat - cp <= 0) num = 0; else num = eval(cp, pat - cp); if (i >= MAXLINE - 20) { fprintf(stderr, "%s: line too long\n", progname); exit(1); } i += sprintf(&buf[i], "%s$%lx", num < 0 ? "-" : "", labs(num)); pat++; } else if (pat[0] == '%' && pat[1] == '=') { /* Substitute with converted variable */ /* First seperate all parts of the pattern string */ cp = pat + 2; cp1 = cp2 = varptr = NULL; if (*cp == '[') { cp1 = ++cp; while (*cp && *cp != ']') cp++; if (cp[0] == ']' && cp[1] == '[') { cp += 2; cp2 = cp; while (*cp && *cp != ']') cp++; if (cp[0] == ']' && isdigit(cp[1])) varptr = vars[cp[1] - '0']; } } if (cp1 == NULL || cp2 == NULL || varptr == NULL) { buf[i++] = *pat++; if (i >= MAXLINE - 1) { fprintf(stderr, "%s: line too long\n", progname); exit(1); } continue; } pat = cp + 2; /* Now scan through the first string to find variable value */ cp1--; pos = 0; while (*cp1 != ']') { cp1++; j = strcspn(cp1, "|]"); if (strlen(varptr) == j && !strncmp(cp1, varptr, j)) break; pos++; cp1 += j; } if (*cp1 == ']') continue; /* Scan through the second string to find the conversion */ cp2--; while (*cp2 != ']' && pos > 0) { cp2++; j = strcspn(cp2, "|]"); pos--; cp2 += j; } if (*cp2 == ']' || pos != 0) continue; /* Insert conversion string into destination */ cp2++; while (*cp2 != '|' && *cp2 != ']') { buf[i++] = *cp2++; if (i >= MAXLINE - 1) { fprintf(stderr, "%s: line too long\n", progname); exit(1); } } } else { buf[i++] = *pat++; if (i >= MAXLINE - 1) { fprintf(stderr, "%s: line too long\n", progname); exit(1); } } buf[i] = '\0'; return(install(buf, i)); } /* * Optimize one line of the input file */ static struct line_s *optline(struct line_s *cur) { struct rule_s *rp; struct line_s *ins, *pat; struct line_s *lp1, *lp2; int i; for (rp = first; rp != NULL; rp = rp->next) { /* Clear variable array */ for (i = 0; i < VARNUM; i++) vars[i] = NULL; /* Scan through pattern texts and match them against the input file */ ins = cur; pat = rp->old; while (ins != NULL && pat != NULL && ( ins->comment_flg || ( /* (pat->text[0]=='%' || ins->text[0]==pat->text[0]) && */ match(ins->text, pat->text)))) { if (!ins->comment_flg) pat = pat->next; else if (ins->text[0]==pat->text[0]) /* Matching a comment! */ { if (match(ins->text, pat->text)) pat = pat->next; } ins = ins->next; } /* Current pattern matched input line, so replace input with new */ if (pat == NULL) { /* Clear all lines in the source file for this pattern */ lp1 = cur; cur = cur->prev; while (lp1 != ins) { #if 0 if( lp1->comment_flg ) { lp2 = lp1; lp1 = lp1->next; lp2->next = cur->next; cur->next = lp2; lp2->prev = cur; cur=cur->next; } else #endif { lp2 = lp1; lp1 = lp1->next; free(lp2); } } /* Insert new lines into list */ pat = rp->new; lp1 = cur; lp2 = NULL; while (pat != NULL) { lp2 = mymalloc(sizeof(struct line_s)); lp2->text = subst(pat->text); lp2->next = NULL; lp2->prev = lp1; lp2->comment_flg = 0; if (lp1 != NULL) lp1->next = lp2; else infile = lp2; lp1 = lp2; pat = pat->next; } if (ins != NULL) ins->prev = lp2; if (lp2 != NULL) lp2->next = ins; else if (lp1 != NULL) lp1->next = NULL; else infile = NULL; return(cur); } } return(cur->next); } /* * Actually optimize all strings in the input file */ static void optimize(int backup) { struct line_s *cur, *lp; int i; int in_asm = 0; /* Scan through all lines in the input file */ cur = infile; while (cur != NULL) { if (cur->comment_flg || in_asm) { lp=cur->next; if (memcmp(cur->text, "!BCC_", 5) == 0) in_asm = (memcmp(cur->text+5, "ASM", 3) == 0); } else if ((lp = optline(cur)) != NULL && lp != cur->next) { for (i = 0; i < backup && lp != NULL; i++) lp = lp->prev; if (lp == NULL) lp = infile; } cur = lp; } } /* * Write out into destination file */ static void writeoutf(char *filename, char *headstr) { FILE *fp; struct line_s *lp; fp = stdout; if (filename != NULL && (fp = fopen(filename, "w")) == NULL) { fprintf(stderr, "%s: can't open output file %s\n", progname, filename); exit(1); } if (headstr != NULL) { fprintf(fp, "%s", headstr); fprintf(fp, "\n"); } for (lp = infile; lp != NULL; lp = lp->next) fprintf(fp, "%s\n", lp->text); if (fp != stdout) (void)fclose(fp); } /* * Print usage */ static void usage(void) { fprintf(stderr, "usage: %s [-c] [-f] [-o]\n" "\t\t[-b] [-h] [-d] " " ...\n", progname); exit(1); } /* * Main program */ int main(int argc, char **argv) { char comment = NOCHAR; char *srcfile = NULL; char *outfile = NULL; char *headstr = NULL; char *rulesdir = NULL; int backup = 0; int i; /* Get program name */ if ((progname = strrchr(argv[0], '/')) == NULL) progname = argv[0]; else progname++; /* Make life easy for bcc */ if ( argc > 4 && strcmp(argv[2], "-o") == 0 && argv[1][0] != '-' ) { srcfile = argv[1]; argv++,argc--; } /* Get options from command line */ for (i = 1; i < argc; i++) if (!strncmp(argv[i], "-c", 2)) comment = argv[i][2]; else if (!strncmp(argv[i], "-b", 2)) backup = atoi(&(argv[i][3])); else if (!strncmp(argv[i], "-f", 2)) srcfile = &(argv[i][2]); else if (!strncmp(argv[i], "-o", 2)) { if(argv[i][2]) outfile = &(argv[i][2]); else if(++i