--- q3asm.c.orig 2002-10-28 11:52:44.000000000 -0800 +++ q3asm.c 2006-09-17 12:30:25.000000000 -0700 @@ -6,6 +6,16 @@ /* MSVC-ism fix. */ #define atoi(s) strtoul(s,NULL,10) +/* TODO: +fails miserably on hash collision. +*/ + +/* 19079 total symbols in FI, 2002 Jan 23 */ +#define Q3ASM_TURBO +#ifdef Q3ASM_TURBO +#define MangleSymbol(dest,src) sprintf(dest, ":%d:_%s", currentFileIndex, src) +#endif /* Q3ASM_TURBO */ + char outputFilename[MAX_OS_PATH]; // the zero page size is just used for detecting run time faults @@ -128,6 +138,26 @@ int value; } symbol_t; +#ifdef Q3ASM_TURBO +/* Tree. */ +typedef struct treenode_s { + int tag; /* balancing info. */ + void *data; /* payload. */ + struct treenode_s *left; + struct treenode_s *right; +} treenode_t; + +treenode_t *symtree; +treenode_t *optree; +treenode_t *linktree; + +typedef struct linkentry_s { + int hash; + char *from_name; + char *to_name; +} linkentry_t; + +#endif /* Q3ASM_TURBO */ segment_t segment[NUM_SEGMENTS]; segment_t *currentSegment; @@ -136,9 +166,10 @@ int numSymbols; int errorCount; +qboolean q3asmVerbose = qfalse; symbol_t *symbols; -symbol_t *lastSymbol; +symbol_t *lastSymbol = 0; /* Most recent symbol defined. */ #define MAX_ASM_FILES 256 @@ -170,6 +201,9 @@ typedef struct { char *name; int opcode; +#ifdef Q3ASM_TURBO + int hash; +#endif /* Q3ASM_TURBO */ } sourceOps_t; sourceOps_t sourceOps[] = { @@ -181,11 +215,516 @@ int opcodesHash[ NUM_SOURCE_OPS ]; +#ifdef Q3ASM_TURBO + +int +vreport (const char* fmt, va_list vp) +{ + if (q3asmVerbose != qtrue) + return 0; + return vprintf(fmt, vp); +} + +int +report (const char *fmt, ...) +{ + va_list va; + int retval; + + va_start(va, fmt); + retval = vreport(fmt, va); + va_end(va); + return retval; +} + + + +/* Comparator function for quicksorting. */ +int +symlist_cmp (const void *e1, const void *e2) +{ + const symbol_t *a, *b; + + a = *(const symbol_t **)e1; + b = *(const symbol_t **)e2; +//crumb("Symbol comparison (1) %d to (2) %d\n", a->value, b->value); + return ( a->value - b->value); +} + +/* + Sort the symbols list by using QuickSort (qsort()). + This may take a LOT of memory (a few megabytes?), but memory is cheap these days. + However, qsort(3) already exists, and I'm really lazy. + -PH +*/ +void +sort_symbols () +{ + int i, elems; + symbol_t *s; + symbol_t **symlist; + +//crumb("sort_symbols: Constructing symlist array\n"); + for (elems = 0, s = symbols; s; s = s->next, elems++) /* nop */ ; + symlist = malloc(elems * sizeof(symbol_t*)); + for (i = 0, s = symbols; s; s = s->next, i++) + { + symlist[i] = s; + } +//crumbf("sort_symbols: Quick-sorting %d symbols\n", elems); + qsort(symlist, elems, sizeof(symbol_t*), symlist_cmp); +//crumbf("sort_symbols: Reconstructing symbols list\n"); + s = symbols = symlist[0]; + for (i = 1; i < elems; i++) + { + s->next = symlist[i]; + s = s->next; + } + lastSymbol = s; + s->next = 0; +//crumbf("sort_symbols: verifying..."); fflush(stdout); + for (i = 0, s = symbols; s; s = s->next, i++) /*nop*/ ; +//crumbf(" %d elements\n", i); + free(symlist); /* d'oh. no gc. */ +} + + + +/* AA tree, aka Right-biased R-B tree */ +/* Implementation from "Algorithms, Data Structres, and Problem Solving with C++" by Mark Allen Weiss. */ +/* FFS, I hate C++. */ + +/* Comparator function for aatree_insert() */ +int +symtree_lessthan (void *x, void *y) +{ + symbol_t *a, *b; + a = (symbol_t*)x; + b = (symbol_t*)y; + if (a->hash < b->hash) return 1; + return 0; +} + +/* Comparator function for aatree_insert() */ +int +optree_lessthan (void *x, void *y) +{ + sourceOps_t *a, *b; +// int a0, b0; + a = (sourceOps_t*)x; + b = (sourceOps_t*)y; +// a0 = opcodesHash[a->opcode]; +// b0 = opcodesHash[b->opcode]; +// if (a->opcode < b->opcode) return 1; + if (a->hash < b->hash) return 1; +// if (a0 < b0) return 1; + return 0; +} + +int +linktree_lessthan (void *x, void *y) +{ + linkentry_t *a, *b; + a = (linkentry_t *)x; + b = (linkentry_t *)y; + if (a->hash < b->hash) return 1; + return 0; +} + +/* Conjure up a new tree node. */ +treenode_t * +treenode_new () +{ + treenode_t *retval; + + retval = malloc(sizeof(treenode_t)); + memset(retval, 0, sizeof(retval)); + retval->tag = 0; + retval->data = NULL; + retval->left = NULL; + retval->right = NULL; + return retval; +} + +/* Count total nodes in tree. */ +int +treenode_count (treenode_t *node) +{ + if (!node) + return 0; + return 1 + treenode_count(node->left) + treenode_count(node->right); +} + +/* Count height of tree. */ +int +treenode_height (treenode_t *node) +{ + int leftheight, rightheight; + if (!node) return 0; + leftheight = treenode_height(node->left); + rightheight = treenode_height(node->right); + if (leftheight > rightheight) + return (1 + leftheight); + else + return (1 + rightheight); +} + +treenode_t * +treenode_rotate_left (treenode_t *node) +{ + treenode_t *temp; + + temp = node->left; + node->left = temp->right; + temp->right = node; + return temp; +} + +treenode_t * +treenode_rotate_right (treenode_t *node) +{ + treenode_t *temp; + + temp = node->right; + node->right = temp->left; + temp->left = node; + return temp; +} + +treenode_t * +aatree_skew (treenode_t *node) +{ + if ((!node) || (!node->left)) + return node; + if (node->left->tag == node->tag) + { + node = treenode_rotate_left(node); + } + return node; +} + +treenode_t * +aatree_split (treenode_t *node) +{ + if ((!node) || (!node->right) || (!node->right->right)) + return node; + if (node->right->right->tag == node->tag) + { + node = treenode_rotate_right(node); + node->tag++; + } + return node; +} + +void +symtree_subdump (void *x) +{ + symbol_t *sym; + sym = (symbol_t*)x; + printf(" %08X/[%s] ", sym->hash, sym->name); +} + +void +optree_subdump (void *x) +{ + sourceOps_t *op; + op = (sourceOps_t*)x; +// printf(" %08X/(%d)[%s] ", opcodesHash[op->opcode], op->opcode, op->name); + printf(" %08X/(%d)[%s] ", op->hash, op->opcode, op->name); +} + +void +linktree_subdump (void *x) +{ + linkentry_t *link; + link = (linkentry_t*)x; + printf(" %08X/[%s]=>[%s]) ", link->hash, link->from_name, link->to_name); +} + +void +aatree_dump (treenode_t *node, void (*subdump)(void*)) +{ + printf("("); + if (node->left) + { + printf("\n"); + aatree_dump(node->left, subdump); + } + if (subdump) + { + subdump(node->data); + } + else + { + printf(" %p ", node->data); + } + if (node->right) + { + aatree_dump(node->right, subdump); + } +} + +treenode_t * +aatree_insert (treenode_t *node, void *data, int (lessthan)(void*, void*)) +{ + treenode_t *retval; + if (node == NULL) + { + retval = treenode_new(); + retval->data = data; + retval->tag = 1; /* is leaf node. */ + return retval; + } + if (lessthan(data, node->data)) + { + node->left = aatree_insert(node->left, data, lessthan); + } + else + { + node->right = aatree_insert(node->right, data, lessthan); + } + node = aatree_skew(node); + node = aatree_split(node); + return node; +} + +void +aatree_stats (treenode_t *node, char *treename) +{ + if (q3asmVerbose) { + report("Stats for %s tree: %d nodes, tree height of %d\n", treename, treenode_count(node), treenode_height(node)); + } +} + + + + +treenode_t * +symtree_insert (treenode_t *node, symbol_t *symbol) +{ +// report("Inserting symbol '%s'\n", symbol->name); + return aatree_insert(node, (void*)symbol, symtree_lessthan); +} + +treenode_t * +symtree_remove (treenode_t *node, symbol_t *symbol) +{ + /* No removal? */ + return node; +} + +symbol_t * +symtree_find (treenode_t *node, int targethash, char *symname) +{ + symbol_t *sym; + + if (!node) return NULL; + sym = ((symbol_t*)(node->data)); + + +#if 0 + if (targethash < sym->hash) + { + return symtree_find (node->left, targethash, symname); + } + else if ((targethash == sym->hash) && (0 == strcmp(symname, sym->name))) + { + return sym; + } + else + { + return symtree_find (node->right, targethash, symname); + } +#else /* 0 */ + if (targethash == sym->hash) + { + //Exhaustive search either branch. + if (0 == strcmp(symname, sym->name)) + { + return sym; + } + else + { + symbol_t *temp; + if ((temp = symtree_find(node->left, targethash, symname))) + return temp; + return symtree_find(node->right, targethash, symname); + } + } + else if (targethash < sym->hash) + { + return symtree_find (node->left, targethash, symname); + } + else if (targethash > sym->hash) + { + return symtree_find (node->right, targethash, symname); + } + return 0; +#endif /* 0 */ +} + +void +symtree_dump (treenode_t *node) +{ + aatree_dump(node, symtree_subdump); + printf("\n"); +} + +void +symtree_stats (treenode_t *node) +{ + aatree_stats(node, "symbols"); +} + + +treenode_t * +optree_insert (treenode_t *node, sourceOps_t *op) +{ +//printf("optree inserting (%d)%08X/[%s]\n", op->opcode, opcodesHash[op->opcode], op->name); + return aatree_insert(node, (void*)op, optree_lessthan); +} + +sourceOps_t * +optree_find (treenode_t *node, int targethash, char *opname) +{ + sourceOps_t *op; + + if (!node) return NULL; + op = ((sourceOps_t *)(node->data)); +//printf("optree_find: looking for [%s/%08X] in [%s/%08X]\n", opname, targethash, op->name, opcodesHash[op->opcode]); +#if 0 +// if (targethash < opcodesHash[op->opcode]) + if (targethash < op->hash) + { + return optree_find(node->left, targethash, opname); + } +// else if ((targethash == opcodesHash[op->opcode]) && (0 == strcmp(op->name, opname))) + else if ((targethash == op->hash) && (0 == strcmp(op->name, opname))) + { + return op; + } + else + { + return optree_find(node->right, targethash, opname); + } +#else /* 0 */ + if (targethash == op->hash) + { + if (0 == strcmp(op->name, opname)) + { + return op; + } + else + { + sourceOps_t *temp; + if ((temp = optree_find(node->left, targethash, opname))) + return temp; + return optree_find(node->right, targethash, opname); + } + } + else if (targethash < op->hash) + { + return optree_find(node->left, targethash, opname); + } + else if (targethash > op->hash) + { + return optree_find(node->right, targethash, opname); + } + return 0; +#endif /* 0 */ +} + +void +optree_dump (treenode_t *node) +{ + aatree_dump(node, optree_subdump); + printf("\n"); +} + +void +optree_stats (treenode_t *node) +{ + aatree_stats(node, "opcodes"); +} + + +treenode_t * +linktree_insert (treenode_t *node, linkentry_t *lent) +{ +// report("Exporting '%s'=>'%s'\n", lent->from_name, lent->to_name); + return aatree_insert(node, (void*)lent, linktree_lessthan); +} + +linkentry_t * +linktree_find (treenode_t *node, int targethash, char *name) +{ + linkentry_t *lent; + + if (!node) return NULL; + lent = ((linkentry_t *)(node->data)); +#if 0 + if (targethash < lent->hash) + { + return linktree_find(node->left, targethash, name); + } + else if ((targethash == lent->hash) && (0 == strcmp(lent->from_name, name))) + { + return lent; + } + else + { + return linktree_find(node->right, targethash, name); + } +#else /* 0 */ + if (targethash == lent->hash) + { + if (0 == strcmp(lent->from_name, name)) + { + return lent; + } + else + { + linkentry_t *temp; + if ((temp = linktree_find(node->left, targethash, name))) + return temp; + return linktree_find(node->right, targethash, name); + } + } + else if (targethash < lent->hash) + { + return linktree_find(node->left, targethash, name); + } + else if (targethash > lent->hash) + { + return linktree_find(node->right, targethash, name); + } + return 0; +#endif /* 0 */ +} + +void +linktree_dump (treenode_t *node) +{ + aatree_dump(node, linktree_subdump); + printf("\n"); +} + +void +linktree_stats (treenode_t *node) +{ + aatree_stats(node, "exports"); +} + + + +#endif /* Q3ASM_TURBO */ + /* ============= HashString ============= */ +#ifndef Q3ASM_TURBO int HashString( char *s ) { int v = 0; @@ -195,6 +734,31 @@ } return v; } +#else /* Q3ASM_TURBO */ +/* Default hash function of Kazlib 1.19, slightly modified. */ +unsigned int HashString (const char *key) +{ + static unsigned long randbox[] = { + 0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U, + 0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU, + 0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU, + 0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU, + }; + + const char *str = key; + unsigned int acc = 0; + + while (*str) { + acc ^= randbox[(*str + acc) & 0xf]; + acc = (acc << 1) | (acc >> 31); + acc &= 0xffffffffU; + acc ^= randbox[((*str++ >> 4) + acc) & 0xf]; + acc = (acc << 2) | (acc >> 30); + acc &= 0xffffffffU; + } + return abs(acc); +} +#endif /* Q3ASM_TURBO */ /* @@ -207,7 +771,7 @@ errorCount++; - printf( "%s:%i ", currentFileName, currentFileLine ); + report( "%s:%i ", currentFileName, currentFileLine ); va_start( argptr,fmt ); vprintf( fmt,argptr ); @@ -251,6 +815,7 @@ ============ */ void DefineSymbol( char *sym, int value ) { +#ifndef Q3ASM_TURBO symbol_t *s, *after; char expanded[MAX_LINE_LENGTH]; int hash; @@ -258,7 +823,7 @@ if ( passNumber == 1 ) { return; } - + // TTimo // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=381 // as a security, bail out if vmMain entry point is not first @@ -300,6 +865,65 @@ } s->next = after->next; after->next = s; +#else /* Q3ASM_TURBO */ + /* Hand optimization by PhaethonH */ + symbol_t *s; + char expanded[MAX_LINE_LENGTH]; + int hash; + + if ( passNumber == 1 ) { + return; + } + + if (!Q_stricmp(sym, "vmMain")) + if (value) + Error( "vmMain must be the first symbol in the qvm (got offset %d)\n", value ); + +#if 0 + // add the file prefix to local symbols to guarantee unique + if ( sym[0] == '$' ) { + sprintf( expanded, "%s_%i", sym, currentFileIndex ); + sym = expanded; + } +#else /* 0 */ + // localize everything, export selectively. + if (sym[0] != ':') { + MangleSymbol(expanded, sym); + sym = expanded; + } +#endif /* 0 */ + + hash = HashString( sym ); + + if (symtree_find(symtree, hash, sym)) { + CodeError( "Multiple definitions for %s\n", sym ); + return; + } + + s = malloc( sizeof( *s ) ); + s->next = NULL; + s->name = copystring( sym ); + s->hash = hash; + s->value = value; + s->segment = currentSegment; + + symtree = symtree_insert(symtree, s); + +/* + Hash table lookup already speeds up symbol lookup enormously. + We postpone sorting until end of pass 0. + Since we're not doing the insertion sort, lastSymbol should always + wind up pointing to the end of list. + This allows constant time for adding to the list. + -PH +*/ + if (symbols == 0) { + lastSymbol = symbols = s; + } else { + lastSymbol->next = s; + lastSymbol = s; + } +#endif /* Q3ASM_TURBO */ } @@ -311,210 +935,776 @@ ============ */ int LookupSymbol( char *sym ) { +#ifndef Q3ASM_TURBO + symbol_t *s; + char expanded[MAX_LINE_LENGTH]; + int hash; + + if ( passNumber == 0 ) { + return 0; + } + + // add the file prefix to local symbols to guarantee unique + if ( sym[0] == '$' ) { + sprintf( expanded, "%s_%i", sym, currentFileIndex ); + sym = expanded; + } + + hash = HashString( sym ); + for ( s = symbols ; s ; s = s->next ) { + if ( hash == s->hash && !strcmp( sym, s->name ) ) { + return s->segment->segmentBase + s->value; + } + } + + CodeError( "ERROR: symbol %s undefined\n", sym ); + passNumber = 0; + DefineSymbol( sym, 0 ); // so more errors aren't printed + passNumber = 1; + return 0; +#else /* Q3ASM_TURBO */ symbol_t *s; char expanded[MAX_LINE_LENGTH]; int hash; + linkentry_t *lent; if ( passNumber == 0 ) { return 0; } +#if 0 // add the file prefix to local symbols to guarantee unique if ( sym[0] == '$' ) { sprintf( expanded, "%s_%i", sym, currentFileIndex ); sym = expanded; } +#else /* 0 */ + if (sym[0] != ':') { +//printf("Mangling global-looking name in %d: %s\n", currentFileIndex, sym); + MangleSymbol(expanded, sym); + sym = expanded; + } + + /* Resolve for imported symbol. */ + while ((lent = linktree_find(linktree, HashString(sym), sym))) { + sym = lent->to_name; +//printf("Resolved imported name: %s\n", sym); + } +#endif /* 0 */ + + hash = HashString( sym ); + +/* + Hand optimization by PhaethonH + -PH +*/ + s = symtree_find(symtree, hash, sym); + if (s) { + return s->segment->segmentBase + s->value; + } + + CodeError( "ERROR: symbol %s undefined (maybe static?)\n", sym ); + passNumber = 0; + DefineSymbol( sym, 0 ); // so more errors aren't printed + passNumber = 1; + return 0; +#endif /* Q3ASM_TURBO */ +} + + +/* +============== +ExtractLine + +Extracts the next line from the given text block. +If a full line isn't parsed, returns NULL +Otherwise returns the updated parse pointer +=============== +*/ +char *ExtractLine( char *data ) { +#ifndef Q3ASM_TURBO + int i; + + currentFileLine++; + lineParseOffset = 0; + token[0] = 0; + + if ( data[0] == 0 ) { + lineBuffer[0] = 0; + return NULL; + } + + for ( i = 0 ; i < MAX_LINE_LENGTH ; i++ ) { + if ( data[i] == 0 || data[i] == '\n' ) { + break; + } + } + if ( i == MAX_LINE_LENGTH ) { + CodeError( "MAX_LINE_LENGTH" ); + return data; + } + memcpy( lineBuffer, data, i ); + lineBuffer[i] = 0; + data += i; + if ( data[0] == '\n' ) { + data++; + } + return data; +#else /* Q3ASM_TURBO */ +/* Goal: + Given a string `data', extract one text line into buffer `lineBuffer' that is no longer than MAX_LINE_LENGTH characters long. + Return value is remainder of `data' that isn't part of `lineBuffer'. + -PH +*/ + /* Hand-optimized by PhaethonH */ + char *p, *q; + + currentFileLine++; + + lineParseOffset = 0; + token[0] = 0; + *lineBuffer = 0; + + p = q = data; + if (!*q) { + return NULL; + } + + for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ; + + if ((p - q) >= MAX_LINE_LENGTH) { + CodeError( "MAX_LINE_LENGTH" ); + return data; + } + + memcpy( lineBuffer, data, (p - data) ); + lineBuffer[(p - data)] = 0; + p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */ + return p; +#endif /* Q3ASM_TURBO */ +} + + +/* +============== +Parse + +Parse a token out of linebuffer +============== +*/ +qboolean Parse( void ) { +#ifndef Q3ASM_TURBO + int c; + int len; + + len = 0; + token[0] = 0; + + // skip whitespace + while ( lineBuffer[ lineParseOffset ] <= ' ' ) { + if ( lineBuffer[ lineParseOffset ] == 0 ) { + return qfalse; + } + lineParseOffset++; + } + + // skip ; comments + c = lineBuffer[ lineParseOffset ]; + if ( c == ';' ) { + return qfalse; + } + + + // parse a regular word + do { + token[len] = c; + len++; + lineParseOffset++; + c = lineBuffer[ lineParseOffset ]; + } while (c>32); + + token[len] = 0; + return qtrue; +#else /* Q3ASM_TURBO */ + /* Hand-optimized by PhaethonH */ + const char *p, *q; + + /* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */ + + *token = 0; /* Clear token. */ + + // skip whitespace + for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ; + + // skip ; comments + /* die on end-of-string */ + if ((*p == ';') || (*p == 0)) { + lineParseOffset = p - lineBuffer; + return qfalse; + } + + q = p; /* Mark the start of token. */ + /* Find separator first. */ + for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */ + /* *p now sits on separator. Mangle other values accordingly. */ + strncpy(token, q, p - q); + token[p - q] = 0; + + lineParseOffset = p - lineBuffer; + + return qtrue; +#endif /* Q3ASM_TURBO */ +} + + +/* +============== +ParseValue +============== +*/ +int ParseValue( void ) { + Parse(); + return atoi( token ); +} + + +/* +============== +ParseExpression +============== +*/ +int ParseExpression(void) { +#ifndef Q3ASM_TURBO + int i, j; + char sym[MAX_LINE_LENGTH]; + int v; + + if ( token[0] == '-' ) { + i = 1; + } else { + i = 0; + } + + for ( ; i < MAX_LINE_LENGTH ; i++ ) { + if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) { + break; + } + } + + memcpy( sym, token, i ); + sym[i] = 0; + + if ( ( sym[0] >= '0' && sym[0] <= '9' ) || sym[0] == '-' ) { + v = atoi( sym ); + } else { + v = LookupSymbol( sym ); + } + + // parse add / subtract offsets + while ( token[i] != 0 ) { + for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) { + if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) { + break; + } + } + + memcpy( sym, token+i+1, j-i-1 ); + sym[j-i-1] = 0; + + if ( token[i] == '+' ) { + v += atoi( sym ); + } + if ( token[i] == '-' ) { + v -= atoi( sym ); + } + i = j; + } + + return v; +#else /* Q3ASM_TURBO */ + /* Hand optimization, PhaethonH */ + int i, j; + char sym[MAX_LINE_LENGTH]; + int v; + + /* Skip over a leading minus. */ + for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) { + if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) { + break; + } + } + + memcpy( sym, token, i ); + sym[i] = 0; + + switch (*sym) { /* Resolve depending on first character. */ +/* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */ + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + v = atoi(sym); + break; + default: + v = LookupSymbol(sym); + break; + } + + // parse add / subtract offsets + while ( token[i] != 0 ) { + for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) { + if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) { + break; + } + } + + memcpy( sym, token+i+1, j-i-1 ); + sym[j-i-1] = 0; + + switch (token[i]) { + case '+': + v += atoi(sym); + break; + case '-': + v -= atoi(sym); + break; + } + + i = j; + } + + return v; +#endif /* Q3ASM_TURBO */ +} + + +/* +============== +HackToSegment + +BIG HACK: I want to put all 32 bit values in the data +segment so they can be byte swapped, and all char data in the lit +segment, but switch jump tables are emited in the lit segment and +initialized strng variables are put in the data segment. + +I can change segments here, but I also need to fixup the +label that was just defined + +Note that the lit segment is read-write in the VM, so strings +aren't read only as in some architectures. +============== +*/ +void HackToSegment( segmentName_t seg ) { + if ( currentSegment == &segment[seg] ) { + return; + } + + currentSegment = &segment[seg]; + if ( passNumber == 0 ) { + lastSymbol->segment = currentSegment; + lastSymbol->value = currentSegment->imageUsed; + } +} + + + + + + + +#ifdef Q3ASM_TURBO + +//#define STAT(L) report("STAT " L "\n"); +#define STAT(L) +#define ASM(O) int TryAssemble##O () + + +/* + These clauses were moved out from AssembleLine() to allow reordering of if's. + An optimizing compiler should reconstruct these back into inline code. + -PH +*/ + + // call instructions reset currentArgOffset +ASM(CALL) +{ + if ( !strncmp( token, "CALL", 4 ) ) { +STAT("CALL"); + EmitByte( &segment[CODESEG], OP_CALL ); + instructionCount++; + currentArgOffset = 0; + return 1; + } + return 0; +} + + // arg is converted to a reversed store +ASM(ARG) +{ + if ( !strncmp( token, "ARG", 3 ) ) { +STAT("ARG"); + EmitByte( &segment[CODESEG], OP_ARG ); + instructionCount++; + if ( 8 + currentArgOffset >= 256 ) { + CodeError( "currentArgOffset >= 256" ); + return 1; + } + EmitByte( &segment[CODESEG], 8 + currentArgOffset ); + currentArgOffset += 4; + return 1; + } + return 0; +} + + // ret just leaves something on the op stack +ASM(RET) +{ + if ( !strncmp( token, "RET", 3 ) ) { +STAT("RET"); + EmitByte( &segment[CODESEG], OP_LEAVE ); + instructionCount++; + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + return 1; + } + return 0; +} + + // pop is needed to discard the return value of + // a function +ASM(POP) +{ + if ( !strncmp( token, "pop", 3 ) ) { +STAT("POP"); + EmitByte( &segment[CODESEG], OP_POP ); + instructionCount++; + return 1; + } + return 0; +} + + // address of a parameter is converted to OP_LOCAL +ASM(ADDRF) +{ + int v; + if ( !strncmp( token, "ADDRF", 5 ) ) { +STAT("ADDRF"); + instructionCount++; + Parse(); + v = ParseExpression(); + v = 16 + currentArgs + currentLocals + v; + EmitByte( &segment[CODESEG], OP_LOCAL ); + EmitInt( &segment[CODESEG], v ); + return 1; + } + return 0; +} + + // address of a local is converted to OP_LOCAL +ASM(ADDRL) +{ + int v; + if ( !strncmp( token, "ADDRL", 5 ) ) { +STAT("ADDRL"); + instructionCount++; + Parse(); + v = ParseExpression(); + v = 8 + currentArgs + v; + EmitByte( &segment[CODESEG], OP_LOCAL ); + EmitInt( &segment[CODESEG], v ); + return 1; + } + return 0; +} + +ASM(PROC) +{ + char name[1024]; + if ( !strcmp( token, "proc" ) ) { +STAT("PROC"); + Parse(); // function name + strcpy( name, token ); + + DefineSymbol( token, instructionCount ); // segment[CODESEG].imageUsed ); + + currentLocals = ParseValue(); // locals + currentLocals = ( currentLocals + 3 ) & ~3; + currentArgs = ParseValue(); // arg marshalling + currentArgs = ( currentArgs + 3 ) & ~3; + + if ( 8 + currentLocals + currentArgs >= 32767 ) { + CodeError( "Locals > 32k in %s\n", name ); + } + + instructionCount++; + EmitByte( &segment[CODESEG], OP_ENTER ); + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + return 1; + } + return 0; +} + + +ASM(ENDPROC) +{ + int v, v2; + if ( !strcmp( token, "endproc" ) ) { +STAT("ENDPROC"); + Parse(); // skip the function name + v = ParseValue(); // locals + v2 = ParseValue(); // arg marshalling + + // all functions must leave something on the opstack + instructionCount++; + EmitByte( &segment[CODESEG], OP_PUSH ); + + instructionCount++; + EmitByte( &segment[CODESEG], OP_LEAVE ); + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + + return 1; + } + return 0; +} + + +ASM(ADDRESS) +{ + int v; + if ( !strcmp( token, "address" ) ) { +STAT("ADDRESS"); + Parse(); + v = ParseExpression(); + +/* Addresses are 32 bits wide, and therefore go into data segment. */ + HackToSegment( DATASEG ); + EmitInt( currentSegment, v ); + return 1; + } + return 0; +} + +ASM(EXPORT) +{ + linkentry_t *lent; + char name[1024]; + + if ( !strcmp( token, "export" ) ) { +STAT("EXPORT"); + if (passNumber > 0) return 1; + Parse(); + strcpy(name, token); +//printf("Exporting :%d:_%s as %s\n", currentFileIndex, name, name); + if ((lent = linktree_find(linktree, HashString(name), name))) { + CodeError("ERROR: Symbol %s already exported as %s\n", lent->from_name, lent->to_name); + } else { + lent = malloc(sizeof(linkentry_t)); + lent->from_name = strdup(name); + MangleSymbol(name, lent->from_name); + lent->to_name = strdup(name); + lent->hash = HashString(lent->from_name); + linktree = linktree_insert(linktree, lent); + } + return 1; + } + return 0; +} + +ASM(IMPORT) +{ + linkentry_t *lent; + char name[1024]; + + if ( !strcmp( token, "import" ) ) { +STAT("IMPORT"); + if (passNumber > 0) return 1; + Parse(); + strcpy(name, token); +//printf("Importing %s as :%d:_%s\n", name, currentFileIndex, name); + lent = malloc(sizeof(linkentry_t)); + lent->to_name = strdup(name); + MangleSymbol(name, lent->to_name); + lent->from_name = strdup(name); + lent->hash = HashString(lent->from_name); + linktree = linktree_insert(linktree, lent); + return 1; + } + return 0; +} + +ASM(CODE) +{ + if ( !strcmp( token, "code" ) ) { +STAT("CODE"); + currentSegment = &segment[CODESEG]; + return 1; + } + return 0; +} + +ASM(BSS) +{ + if ( !strcmp( token, "bss" ) ) { +STAT("BSS"); + currentSegment = &segment[BSSSEG]; + return 1; + } + return 0; +} + +ASM(DATA) +{ + if ( !strcmp( token, "data" ) ) { +STAT("DATA"); + currentSegment = &segment[DATASEG]; + return 1; + } + return 0; +} - hash = HashString( sym ); - for ( s = symbols ; s ; s = s->next ) { - if ( hash == s->hash && !strcmp( sym, s->name ) ) { - return s->segment->segmentBase + s->value; - } +ASM(LIT) +{ + if ( !strcmp( token, "lit" ) ) { +STAT("LIT"); + currentSegment = &segment[LITSEG]; + return 1; } + return 0; +} - CodeError( "ERROR: symbol %s undefined\n", sym ); - passNumber = 0; - DefineSymbol( sym, 0 ); // so more errors aren't printed - passNumber = 1; +ASM(LINE) +{ + if ( !strcmp( token, "line" ) ) { +STAT("LINE"); + return 1; + } return 0; } +ASM(FILE) +{ + if ( !strcmp( token, "file" ) ) { +STAT("FILE"); + return 1; + } + return 0; +} -/* -============== -ExtractLine +ASM(EQU) +{ + linkentry_t *lent; + char name[1024]; -Extracts the next line from the given text block. -If a full line isn't parsed, returns NULL -Otherwise returns the updated parse pointer -=============== -*/ -char *ExtractLine( char *data ) { - int i; + if ( !strcmp( token, "equ" ) ) { +STAT("EQU"); + Parse(); + strcpy( name, token ); + Parse(); + DefineSymbol( name, atoi(token) ); - currentFileLine++; - lineParseOffset = 0; - token[0] = 0; + /* export. */ + lent = malloc(sizeof(linkentry_t)); + lent->from_name = strdup(name); + MangleSymbol(name, lent->from_name); + lent->to_name = strdup(name); + lent->hash = HashString(lent->from_name); + linktree = linktree_insert(linktree, lent); - if ( data[0] == 0 ) { - lineBuffer[0] = 0; - return NULL; + return 1; } + return 0; +} - for ( i = 0 ; i < MAX_LINE_LENGTH ; i++ ) { - if ( data[i] == 0 || data[i] == '\n' ) { - break; - } - } - if ( i == MAX_LINE_LENGTH ) { - CodeError( "MAX_LINE_LENGTH" ); - return data; +ASM(ALIGN) +{ + int v; + if ( !strcmp( token, "align" ) ) { +STAT("ALIGN"); + v = ParseValue(); + currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 ); + return 1; } - memcpy( lineBuffer, data, i ); - lineBuffer[i] = 0; - data += i; - if ( data[0] == '\n' ) { - data++; + return 0; +} + +ASM(SKIP) +{ + int v; + if ( !strcmp( token, "skip" ) ) { +STAT("SKIP"); + v = ParseValue(); + currentSegment->imageUsed += v; + return 1; } - return data; + return 0; } +ASM(BYTE) +{ + int i, v, v2; + if ( !strcmp( token, "byte" ) ) { +STAT("BYTE"); + v = ParseValue(); + v2 = ParseValue(); -/* -============== -Parse + if ( v == 1 ) { +/* Character (1-byte) values go into lit(eral) segment. */ + HackToSegment( LITSEG ); + } else if ( v == 4 ) { +/* 32-bit (4-byte) values go into data segment. */ + HackToSegment( DATASEG ); + } else if ( v == 2 ) { +/* and 16-bit (2-byte) values will cause q3asm to barf. */ + CodeError( "16 bit initialized data not supported" ); + } -Parse a token out of linebuffer -============== -*/ -qboolean Parse( void ) { - int c; - int len; - - len = 0; - token[0] = 0; - - // skip whitespace - while ( lineBuffer[ lineParseOffset ] <= ' ' ) { - if ( lineBuffer[ lineParseOffset ] == 0 ) { - return qfalse; + // emit little endien + for ( i = 0 ; i < v ; i++ ) { + EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */ + v2 >>= 8; } - lineParseOffset++; + return 1; } + return 0; +} - // skip ; comments - c = lineBuffer[ lineParseOffset ]; - if ( c == ';' ) { - return qfalse; + // code labels are emited as instruction counts, not byte offsets, + // because the physical size of the code will change with + // different run time compilers and we want to minimize the + // size of the required translation table +ASM(LABEL) +{ + if ( !strncmp( token, "LABEL", 5 ) ) { +STAT("LABEL"); + Parse(); + if ( currentSegment == &segment[CODESEG] ) { + DefineSymbol( token, instructionCount ); + } else { + DefineSymbol( token, currentSegment->imageUsed ); + } + return 1; } - - - // parse a regular word - do { - token[len] = c; - len++; - lineParseOffset++; - c = lineBuffer[ lineParseOffset ]; - } while (c>32); - - token[len] = 0; - return qtrue; + return 0; } -/* -============== -ParseValue -============== -*/ -int ParseValue( void ) { - Parse(); - return atoi( token ); -} + +#endif /* Q3ASM_TURBO */ + -/* -============== -ParseExpression -============== -*/ -int ParseExpression(void) { - int i, j; - char sym[MAX_LINE_LENGTH]; - int v; - if ( token[0] == '-' ) { - i = 1; - } else { - i = 0; - } - for ( ; i < MAX_LINE_LENGTH ; i++ ) { - if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) { - break; - } - } - memcpy( sym, token, i ); - sym[i] = 0; - if ( ( sym[0] >= '0' && sym[0] <= '9' ) || sym[0] == '-' ) { - v = atoi( sym ); - } else { - v = LookupSymbol( sym ); - } - // parse add / subtract offsets - while ( token[i] != 0 ) { - for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) { - if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) { - break; - } - } - memcpy( sym, token+i+1, j-i-1 ); - sym[j-i-1] = 0; - if ( token[i] == '+' ) { - v += atoi( sym ); - } - if ( token[i] == '-' ) { - v -= atoi( sym ); - } - i = j; - } - return v; -} -/* -============== -HackToSegment -BIG HACK: I want to put all 32 bit values in the data -segment so they can be byte swapped, and all char data in the lit -segment, but switch jump tables are emited in the lit segment and -initialized strng variables are put in the data segment. -I can change segments here, but I also need to fixup the -label that was just defined -Note that the lit segment is read-write in the VM, so strings -aren't read only as in some architectures. -============== -*/ -void HackToSegment( segmentName_t seg ) { - if ( currentSegment == &segment[seg] ) { - return; - } - currentSegment = &segment[seg]; - if ( passNumber == 0 ) { - lastSymbol->segment = currentSegment; - lastSymbol->value = currentSegment->imageUsed; - } -} /* ============== @@ -523,7 +1713,11 @@ ============== */ void AssembleLine( void ) { +#ifndef Q3ASM_TURBO int v, v2; +#else /* Q3ASM_TURBO */ + sourceOps_t *op; +#endif /* Q3ASM_TURBO */ int i; int hash; @@ -534,6 +1728,7 @@ hash = HashString( token ); +#ifndef Q3ASM_TURBO for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) { if ( hash == opcodesHash[i] && !strcmp( token, sourceOps[i].name ) ) { int opcode; @@ -775,6 +1970,122 @@ } return; } +#else /* Q3ASM_TURBO */ +/* + Opcode search using hash table. + Since the opcodes stays mostly fixed, this may benefit even more from a tree. + Always with the tree :) + -PH +*/ + i = 0; /* stops whining. */ + op = optree_find(optree, hash, token); + if (op) { + int opcode; + int expression; + + if ( op->opcode == OP_UNDEF ) { + CodeError( "Undefined opcode: %s\n", token ); + } + if ( op->opcode == OP_IGNORE ) { + return; // we ignore most conversions + } + + // sign extensions need to check next parm + opcode = op->opcode; + if ( opcode == OP_SEX8 ) { + Parse(); + if ( token[0] == '1' ) { + opcode = OP_SEX8; + } else if ( token[0] == '2' ) { + opcode = OP_SEX16; + } else { + CodeError( "Bad sign extension: %s\n", token ); + return; + } + } + + // check for expression + Parse(); + if ( token[0] && op->opcode != OP_CVIF + && op->opcode != OP_CVFI ) { + expression = ParseExpression(); + + // code like this can generate non-dword block copies: + // auto char buf[2] = " "; + // we are just going to round up. This might conceivably + // be incorrect if other initialized chars follow. + if ( opcode == OP_BLOCK_COPY ) { + expression = ( expression + 3 ) & ~3; + } + + EmitByte( &segment[CODESEG], opcode ); + EmitInt( &segment[CODESEG], expression ); + } else { + EmitByte( &segment[CODESEG], opcode ); + } + + instructionCount++; + return; + } + +/* This falls through if an assembly opcode is not found. -PH */ + +/* The following should be sorted in sequence of statistical frequency, most frequent first. -PH */ +/* +Empirical frequency statistics from FI 2001.01.23: + 109892 STAT ADDRL + 72188 STAT BYTE + 51150 STAT LINE + 50906 STAT ARG + 43704 STAT IMPORT + 34902 STAT LABEL + 32066 STAT ADDRF + 23704 STAT CALL + 7720 STAT POP + 7256 STAT RET + 5198 STAT ALIGN + 3292 STAT EXPORT + 2878 STAT PROC + 2878 STAT ENDPROC + 2812 STAT ADDRESS + 738 STAT SKIP + 374 STAT EQU + 280 STAT CODE + 176 STAT LIT + 102 STAT FILE + 100 STAT BSS + 68 STAT DATA + + -PH +*/ + +#undef ASM +#define ASM(O) if (TryAssemble##O ()) return; + + ASM(ADDRL) + ASM(BYTE) + ASM(LINE) + ASM(ARG) + ASM(IMPORT) + ASM(LABEL) + ASM(ADDRF) + ASM(CALL) + ASM(POP) + ASM(RET) + ASM(ALIGN) + ASM(EXPORT) + ASM(PROC) + ASM(ENDPROC) + ASM(ADDRESS) + ASM(SKIP) + ASM(EQU) + ASM(CODE) + ASM(LIT) + ASM(FILE) + ASM(BSS) + ASM(DATA) + +#endif /* Q3ASM_TURBO */ CodeError( "Unknown token: %s\n", token ); } @@ -785,11 +2096,25 @@ ============== */ void InitTables( void ) { +#ifndef Q3ASM_TURBO int i; for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) { opcodesHash[i] = HashString( sourceOps[i].name ); } +#else /* Q3ASM_TURBO */ + int i; + + symtree = NULL; + optree = NULL; + linktree = NULL; + + for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) { + opcodesHash[i] = HashString( sourceOps[i].name ); + sourceOps[i].hash = opcodesHash[i]; + optree = optree_insert(optree, sourceOps + i); + } +#endif /* Q3ASM_TURBO */ } @@ -803,15 +2128,20 @@ symbol_t *s; char imageName[MAX_OS_PATH]; int seg; +#ifdef Q3ASM_TURBO + char *p; +#endif /* Q3ASM_TURBO */ strcpy( imageName, outputFilename ); StripExtension( imageName ); strcat( imageName, ".map" ); - printf( "Writing %s...\n", imageName ); + report( "Writing %s...\n", imageName ); + f = SafeOpenWrite( imageName ); for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) { for ( s = symbols ; s ; s = s->next ) { +#ifndef Q3ASM_TURBO if ( s->name[0] == '$' ) { continue; // skip locals } @@ -819,6 +2149,21 @@ continue; } fprintf( f, "%i %8x %s\n", seg, s->value, s->name ); +#else /* Q3ASM_TURBO */ + p = strchr(s->name, '_'); + if (!p) { + p = s->name; + } else { + p++; + } + if (p[0] == '$') { + continue; /* local. skip. */ + } + if (&segment[seg] != s->segment) { + continue; /* Belongs to another segment. skip. */ + } + fprintf(f, "%i %8x %s\n", seg, s->value, p); +#endif /* Q3ASM_TURBO */ } } fclose( f ); @@ -834,20 +2179,22 @@ vmHeader_t header; FILE *f; - printf( "%i total errors\n", errorCount ); + report( "%i total errors\n", errorCount ); + strcpy( imageName, outputFilename ); StripExtension( imageName ); strcat( imageName, ".qvm" ); remove( imageName ); - printf( "code segment: %7i\n", segment[CODESEG].imageUsed ); - printf( "data segment: %7i\n", segment[DATASEG].imageUsed ); - printf( "lit segment: %7i\n", segment[LITSEG].imageUsed ); - printf( "bss segment: %7i\n", segment[BSSSEG].imageUsed ); - printf( "instruction count: %i\n", instructionCount ); + report( "code segment: %7i\n", segment[CODESEG].imageUsed ); + report( "data segment: %7i\n", segment[DATASEG].imageUsed ); + report( "lit segment: %7i\n", segment[LITSEG].imageUsed ); + report( "bss segment: %7i\n", segment[BSSSEG].imageUsed ); + report( "instruction count: %i\n", instructionCount ); + if ( errorCount != 0 ) { - printf( "Not writing a file due to errors\n" ); + report( "Not writing a file due to errors\n" ); return; } @@ -860,7 +2207,7 @@ header.litLength = segment[LITSEG].imageUsed; header.bssLength = segment[BSSSEG].imageUsed; - printf( "Writing to %s\n", imageName ); + report( "Writing to %s\n", imageName ); CreatePath( imageName ); f = SafeOpenWrite( imageName ); @@ -881,7 +2228,7 @@ char filename[MAX_OS_PATH]; char *ptr; - printf( "outputFilename: %s\n", outputFilename ); + report( "outputFilename: %s\n", outputFilename ); for ( i = 0 ; i < numAsmFiles ; i++ ) { strcpy( filename, asmFileNames[ i ] ); @@ -903,7 +2250,7 @@ currentFileIndex = i; currentFileName = asmFileNames[ i ]; currentFileLine = 0; - printf("pass %i: %s\n", passNumber, currentFileName ); + report("pass %i: %s\n", passNumber, currentFileName ); ptr = asmFiles[i]; while ( ptr ) { ptr = ExtractLine( ptr ); @@ -915,6 +2262,11 @@ for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3; } +#ifdef Q3ASM_TURBO + if (passNumber == 0) { + sort_symbols(); + } +#endif /* Q3ASM_TURBO */ } // reserve the stack in bss @@ -976,11 +2328,23 @@ // _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler if ( argc < 2 ) { +#ifndef Q3ASM_TURBO Error( "usage: q3asm [-o output] or q3asm -f \n" ); +#else /* Q3ASM_TURBO */ + Error("Usage: %s [OPTION]... [FILES]...\n\ +Assemble LCC bytecode assembly to Q3VM bytecode.\n\ +\n\ + -o OUTPUT Write assembled output to file OUTPUT.qvm\n\ + -f LISTFILE Read options and list of files to assemble from LISTFILE\n\ + -b BUCKETS Set symbol hash table to BUCKETS buckets\n\ +", argv[0]); +#endif /* Q3ASM_TURBO */ } start = I_FloatTime (); +#ifndef Q3ASM_TURBO InitTables(); +#endif /* !Q3ASM_TURBO */ // default filename is "q3asm" strcpy( outputFilename, "q3asm" ); @@ -994,6 +2358,7 @@ if ( i == argc - 1 ) { Error( "-o must preceed a filename" ); } +/* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */ strcpy( outputFilename, argv[ i+1 ] ); i++; continue; @@ -1007,6 +2372,29 @@ i++; continue; } + +#ifdef Q3ASM_TURBO +// if (!strcmp(argv[i], "-b")) { +// if (i == argc - 1) { +// Error("-b requires an argument"); +// } +// i++; +// symtablelen = atoi(argv[i]); +// continue; +// } +#endif /* Q3ASM_TURBO */ + + if( !strcmp( argv[ i ], "-v" ) ) + { +/* Verbosity option added by Timbo, 2002.09.14. +By default (no -v option), q3asm remains silent except for critical errors. +Verbosity turns on all messages, error or not. +Motivation: not wanting to scrollback for pages to find asm error. +*/ + q3asmVerbose = qtrue; + continue; + } + Error( "Unknown option: %s", argv[i] ); } @@ -1016,10 +2404,29 @@ numAsmFiles++; } +#ifdef Q3ASM_TURBO + InitTables(); +#endif /* Q3ASM_TURBO */ Assemble(); +#ifdef Q3ASM_TURBO + { + symbol_t *s; + + for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ; + + if (q3asmVerbose) + { + report("%d symbols defined\n", i); + symtree_stats(symtree); + optree_stats(optree); + linktree_stats(linktree); + } + } +#endif /* Q3ASM_TURBO */ + end = I_FloatTime (); - printf ("%5.0f seconds elapsed\n", end-start); + report ("%5.0f seconds elapsed\n", end-start); return 0; }