aboutsummaryrefslogtreecommitdiffstats
path: root/code/tools
diff options
context:
space:
mode:
authortma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea>2005-10-05 17:59:10 +0000
committertma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea>2005-10-05 17:59:10 +0000
commit7fe2b280297f57c03b366e445920940142d083c4 (patch)
treeb1301a8833ae4f0aeb0a541e9b80eebbe83008cb /code/tools
parent8305a120fda7c4039d3b8e4f40ceb6cc14015f21 (diff)
downloadioquake3-aero-7fe2b280297f57c03b366e445920940142d083c4.tar.gz
ioquake3-aero-7fe2b280297f57c03b366e445920940142d083c4.zip
* Applied q3asm-turbo patches from http://www.icculus.org/~phaethon/q3/q3asm-turbo/q3asm-turbo.html
* Added -m option to q3asm to write a map file (which is now disabled by default) * q3asm now returns an error code on failure git-svn-id: svn://svn.icculus.org/quake3/trunk@141 edf5b092-35ff-0310-97b2-ce42778d08ea
Diffstat (limited to 'code/tools')
-rw-r--r--code/tools/asm/Makefile4
-rw-r--r--code/tools/asm/q3asm.c1091
2 files changed, 1076 insertions, 19 deletions
diff --git a/code/tools/asm/Makefile b/code/tools/asm/Makefile
index ec314f1..06c67ad 100644
--- a/code/tools/asm/Makefile
+++ b/code/tools/asm/Makefile
@@ -8,12 +8,12 @@ else
endif
CC=gcc
-CFLAGS=-O2 -Wall -Werror -fno-strict-aliasing
+Q3ASM_CFLAGS=-O2 -Wall -Werror -fno-strict-aliasing
default: q3asm
q3asm: q3asm.c cmdlib.c
- $(CC) $(CFLAGS) -o $@ $^
+ $(CC) $(Q3ASM_CFLAGS) -o $@ $^
clean:
rm -f q3asm *~ *.o
diff --git a/code/tools/asm/q3asm.c b/code/tools/asm/q3asm.c
index c6aa1f0..a99c9f0 100644
--- a/code/tools/asm/q3asm.c
+++ b/code/tools/asm/q3asm.c
@@ -25,7 +25,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "qfiles.h"
/* MSVC-ism fix. */
+#ifdef _WIN32
#define atoi(s) strtoul(s,NULL,10)
+#endif
+
+/* 19079 total symbols in FI, 2002 Jan 23 */
+#define Q3ASM_TURBO
+#define DEFAULT_HASHTABLE_SIZE 2048
char outputFilename[MAX_OS_PATH];
@@ -149,6 +155,21 @@ typedef struct symbol_s {
int value;
} symbol_t;
+#ifdef Q3ASM_TURBO
+typedef struct hashchain_s {
+ void *data;
+ struct hashchain_s *next;
+} hashchain_t;
+
+typedef struct hashtable_s {
+ int buckets;
+ hashchain_t **table;
+} hashtable_t;
+
+int symtablelen = DEFAULT_HASHTABLE_SIZE;
+hashtable_t *symtable;
+hashtable_t *optable;
+#endif /* Q3ASM_TURBO */
segment_t segment[NUM_SEGMENTS];
segment_t *currentSegment;
@@ -157,9 +178,11 @@ int passNumber;
int numSymbols;
int errorCount;
+qboolean optionVerbose = qfalse;
+qboolean optionWriteMapFile = qfalse;
symbol_t *symbols;
-symbol_t *lastSymbol;
+symbol_t *lastSymbol = 0; /* Most recent symbol defined. */
#define MAX_ASM_FILES 256
@@ -202,11 +225,252 @@ sourceOps_t sourceOps[] = {
int opcodesHash[ NUM_SOURCE_OPS ];
+#ifdef Q3ASM_TURBO
+
+int
+vreport (const char* fmt, va_list vp)
+{
+ if (optionVerbose != 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;
+}
+
+/* The chain-and-bucket hash table. -PH */
+
+void
+hashtable_init (hashtable_t *H, int buckets)
+{
+ H->buckets = buckets;
+ H->table = calloc(H->buckets, sizeof(*(H->table)));
+ return;
+}
+
+hashtable_t *
+hashtable_new (int buckets)
+{
+ hashtable_t *H;
+
+ H = malloc(sizeof(hashtable_t));
+ hashtable_init(H, buckets);
+ return H;
+}
+
+/* No destroy/destructor. No need. */
+
+void
+hashtable_add (hashtable_t *H, int hashvalue, void *datum)
+{
+ hashchain_t *hc, **hb;
+
+ hashvalue = (abs(hashvalue) % H->buckets);
+ hb = &(H->table[hashvalue]);
+ if (*hb == 0)
+ {
+ /* Empty bucket. Create new one. */
+ *hb = calloc(1, sizeof(**hb));
+ hc = *hb;
+ }
+ else
+ {
+ /* Get hc to point to last node in chain. */
+ for (hc = *hb; hc && hc->next; hc = hc->next);
+ hc->next = calloc(1, sizeof(*hc));
+ hc = hc->next;
+ }
+ hc->data = datum;
+ hc->next = 0;
+ return;
+}
+
+hashchain_t *
+hashtable_get (hashtable_t *H, int hashvalue)
+{
+ hashvalue = (abs(hashvalue) % H->buckets);
+ return (H->table[hashvalue]);
+}
+
+void
+hashtable_stats (hashtable_t *H)
+{
+ int len, empties, longest, nodes;
+ int i;
+ float meanlen;
+ hashchain_t *hc;
+
+ report("Stats for hashtable %08X", H);
+ empties = 0;
+ longest = 0;
+ nodes = 0;
+ for (i = 0; i < H->buckets; i++)
+ {
+ if (H->table[i] == 0)
+ { empties++; continue; }
+ for (hc = H->table[i], len = 0; hc; hc = hc->next, len++);
+ if (len > longest) { longest = len; }
+ nodes += len;
+ }
+ meanlen = (float)(nodes) / (H->buckets - empties);
+#if 0
+/* Long stats display */
+ report(" Total buckets: %d\n", H->buckets);
+ report(" Total stored nodes: %d\n", nodes);
+ report(" Longest chain: %d\n", longest);
+ report(" Empty chains: %d\n", empties);
+ report(" Mean non-empty chain length: %f\n", meanlen);
+#else //0
+/* Short stats display */
+ report(", %d buckets, %d nodes", H->buckets, nodes);
+ report("\n");
+ report(" Longest chain: %d, empty chains: %d, mean non-empty: %f", longest, empties, meanlen);
+#endif //0
+ report("\n");
+}
+
+
+/* Kludge. */
+/* Check if symbol already exists. */
+/* Returns 0 if symbol does NOT already exist, non-zero otherwise. */
+int
+hashtable_symbol_exists (hashtable_t *H, int hash, char *sym)
+{
+ hashchain_t *hc;
+ symbol_t *s;
+
+ hash = (abs(hash) % H->buckets);
+ hc = H->table[hash];
+ if (hc == 0)
+ {
+ /* Empty chain means this symbol has not yet been defined. */
+ return 0;
+ }
+ for (; hc; hc = hc->next)
+ {
+ s = (symbol_t*)hc->data;
+// if ((hash == s->hash) && (strcmp(sym, s->name) == 0))
+/* We _already_ know the hash is the same. That's why we're probing! */
+ if (strcmp(sym, s->name) == 0)
+ {
+ /* Symbol collisions -- symbol already exists. */
+ return 1;
+ }
+ }
+ return 0; /* Can't find collision. */
+}
+
+
+
+
+/* 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. */
+}
+
+
+
+
+/*
+ Problem:
+ BYTE values are specified as signed decimal string.
+ A properly functional atoi() will cap large signed values at 0x7FFFFFFF.
+ Negative word values are often specified as very large decimal values by lcc.
+ Therefore, values that should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using atoi().
+ Bad.
+
+ This function is one big evil hack to work around this problem.
+*/
+/* FIXME: Find out maximum token length for VC++ -PH */
+int
+ThingToConvertDecimalIntoSigned32SoThatAtoiDoesntCapAt7FFFFFFF (const char *s)
+{
+ /* Variable `l' should be an integer variant larger than 32 bits.
+ On gnu-x86, "long long" is 64 bits. -PH
+ */
+ long long int l;
+ union {
+ unsigned int u;
+ signed int i;
+ } retval;
+
+ l = atoll(s);
+ /* Now smash to signed 32 bits accordingly. */
+ if (l < 0) {
+ retval.i = (int)l;
+ } else {
+ retval.u = (unsigned int)l;
+ }
+ return retval.i; /* <- union hackage. I feel dirty with this. -PH */
+}
+
+/* Programmer Attribute #1: laziness */
+#ifndef _WIN32
+#define atoi ThingToConvertDecimalIntoSigned32SoThatAtoiDoesntCapAt7FFFFFFF
+#endif
+
+
+#endif /* Q3ASM_TURBO */
+
/*
=============
HashString
=============
*/
+#ifndef Q3ASM_TURBO
int HashString( char *s ) {
int v = 0;
@@ -216,6 +480,31 @@ int HashString( char *s ) {
}
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 */
/*
@@ -228,7 +517,7 @@ void CodeError( char *fmt, ... ) {
errorCount++;
- printf( "%s:%i ", currentFileName, currentFileLine );
+ report( "%s:%i ", currentFileName, currentFileLine );
va_start( argptr,fmt );
vprintf( fmt,argptr );
@@ -272,6 +561,7 @@ Symbols can only be defined on pass 0
============
*/
void DefineSymbol( char *sym, int value ) {
+#ifndef Q3ASM_TURBO
symbol_t *s, *after;
char expanded[MAX_LINE_LENGTH];
int hash;
@@ -279,7 +569,7 @@ void DefineSymbol( char *sym, int value ) {
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
@@ -321,6 +611,53 @@ void DefineSymbol( char *sym, int value ) {
}
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;
+ }
+
+ // add the file prefix to local symbols to guarantee unique
+ if ( sym[0] == '$' ) {
+ sprintf( expanded, "%s_%i", sym, currentFileIndex );
+ sym = expanded;
+ }
+
+ hash = HashString( sym );
+
+ if (hashtable_symbol_exists(symtable, 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;
+
+ hashtable_add(symtable, hash, 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 */
}
@@ -332,6 +669,7 @@ Symbols can only be evaluated on pass 1
============
*/
int LookupSymbol( char *sym ) {
+#ifndef Q3ASM_TURBO
symbol_t *s;
char expanded[MAX_LINE_LENGTH];
int hash;
@@ -358,6 +696,43 @@ int LookupSymbol( char *sym ) {
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;
+ hashchain_t *hc;
+
+ 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 );
+
+/*
+ Hand optimization by PhaethonH
+
+ Using a hash table with chain/bucket for lookups alone sped up q3asm by almost 3x for me.
+ -PH
+*/
+ for (hc = hashtable_get(symtable, hash); hc; hc = hc->next) {
+ s = (symbol_t*)hc->data; /* ugly typecasting, but it's fast! */
+ 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;
+#endif /* Q3ASM_TURBO */
}
@@ -371,6 +746,7 @@ Otherwise returns the updated parse pointer
===============
*/
char *ExtractLine( char *data ) {
+#ifndef Q3ASM_TURBO
int i;
currentFileLine++;
@@ -398,6 +774,38 @@ char *ExtractLine( char *data ) {
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 */
}
@@ -409,6 +817,7 @@ Parse a token out of linebuffer
==============
*/
qboolean Parse( void ) {
+#ifndef Q3ASM_TURBO
int c;
int len;
@@ -440,6 +849,35 @@ qboolean Parse( void ) {
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 */
}
@@ -460,6 +898,7 @@ ParseExpression
==============
*/
int ParseExpression(void) {
+#ifndef Q3ASM_TURBO
int i, j;
char sym[MAX_LINE_LENGTH];
int v;
@@ -506,6 +945,59 @@ int ParseExpression(void) {
}
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 */
}
@@ -537,6 +1029,366 @@ void HackToSegment( segmentName_t seg ) {
}
}
+
+
+
+
+
+
+#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)
+{
+ if ( !strcmp( token, "export" ) ) {
+STAT("EXPORT");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(IMPORT)
+{
+ if ( !strcmp( token, "import" ) ) {
+STAT("IMPORT");
+ 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;
+}
+
+ASM(LIT)
+{
+ if ( !strcmp( token, "lit" ) ) {
+STAT("LIT");
+ currentSegment = &segment[LITSEG];
+ return 1;
+ }
+ return 0;
+}
+
+ASM(LINE)
+{
+ if ( !strcmp( token, "line" ) ) {
+STAT("LINE");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(FILE)
+{
+ if ( !strcmp( token, "file" ) ) {
+STAT("FILE");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(EQU)
+{
+ char name[1024];
+ if ( !strcmp( token, "equ" ) ) {
+STAT("EQU");
+ Parse();
+ strcpy( name, token );
+ Parse();
+ DefineSymbol( name, atoi(token) );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(ALIGN)
+{
+ int v;
+ if ( !strcmp( token, "align" ) ) {
+STAT("ALIGN");
+ v = ParseValue();
+ currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(SKIP)
+{
+ int v;
+ if ( !strcmp( token, "skip" ) ) {
+STAT("SKIP");
+ v = ParseValue();
+ currentSegment->imageUsed += v;
+ return 1;
+ }
+ return 0;
+}
+
+ASM(BYTE)
+{
+ int i, v, v2;
+ if ( !strcmp( token, "byte" ) ) {
+STAT("BYTE");
+ v = ParseValue();
+ v2 = ParseValue();
+
+ 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" );
+ }
+
+ // emit little endien
+ for ( i = 0 ; i < v ; i++ ) {
+ EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */
+ v2 >>= 8;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+ // 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;
+ }
+ return 0;
+}
+
+
+
+#endif /* Q3ASM_TURBO */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
==============
AssembleLine
@@ -544,7 +1396,12 @@ AssembleLine
==============
*/
void AssembleLine( void ) {
+#ifndef Q3ASM_TURBO
int v, v2;
+#else /* Q3ASM_TURBO */
+ hashchain_t *hc;
+ sourceOps_t *op;
+#endif /* Q3ASM_TURBO */
int i;
int hash;
@@ -555,6 +1412,7 @@ void AssembleLine( void ) {
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;
@@ -796,6 +1654,124 @@ void AssembleLine( void ) {
}
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
+*/
+ for (hc = hashtable_get(optable, hash); hc; hc = hc->next) {
+ op = (sourceOps_t*)(hc->data);
+ i = op - sourceOps;
+ if ((hash == opcodesHash[i]) && (!strcmp(token, op->name))) {
+ 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 );
}
@@ -806,11 +1782,23 @@ InitTables
==============
*/
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;
+
+ symtable = hashtable_new(symtablelen);
+ optable = hashtable_new(100); /* There's hardly 100 opcodes anyway. */
+
+ for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
+ opcodesHash[i] = HashString( sourceOps[i].name );
+ hashtable_add(optable, opcodesHash[i], sourceOps + i);
+ }
+#endif /* Q3ASM_TURBO */
}
@@ -829,7 +1817,8 @@ void WriteMapFile( void ) {
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 ) {
@@ -855,20 +1844,22 @@ void WriteVmFile( void ) {
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;
}
@@ -881,7 +1872,7 @@ void WriteVmFile( void ) {
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 );
@@ -902,7 +1893,7 @@ void Assemble( void ) {
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 ] );
@@ -924,7 +1915,7 @@ void Assemble( void ) {
currentFileIndex = i;
currentFileName = asmFileNames[ i ];
currentFileLine = 0;
- printf("pass %i: %s\n", passNumber, currentFileName );
+ report("pass %i: %s\n", passNumber, currentFileName );
fflush( NULL );
ptr = asmFiles[i];
while ( ptr ) {
@@ -937,6 +1928,11 @@ void Assemble( void ) {
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
@@ -948,7 +1944,9 @@ void Assemble( void ) {
WriteVmFile();
// write the map file even if there were errors
- WriteMapFile();
+ if( optionWriteMapFile ) {
+ WriteMapFile();
+ }
}
@@ -998,11 +1996,24 @@ int main( int argc, char **argv ) {
// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
if ( argc < 2 ) {
+#ifndef Q3ASM_TURBO
Error( "usage: q3asm [-o output] <files> or q3asm -f <listfile>\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\
+ -v Verbose compilation report\n\
+", argv[0]);
+#endif /* Q3ASM_TURBO */
}
start = I_FloatTime ();
+#ifndef Q3ASM_TURBO
InitTables();
+#endif /* !Q3ASM_TURBO */
// default filename is "q3asm"
strcpy( outputFilename, "q3asm" );
@@ -1016,6 +2027,7 @@ int main( int argc, char **argv ) {
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;
@@ -1029,6 +2041,33 @@ int main( int argc, char **argv ) {
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.
+*/
+ optionVerbose = qtrue;
+ continue;
+ }
+
+ if( !strcmp( argv[ i ], "-m" ) ) {
+ optionWriteMapFile = qtrue;
+ continue;
+ }
+
Error( "Unknown option: %s", argv[i] );
}
@@ -1038,11 +2077,29 @@ int main( int argc, char **argv ) {
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 (optionVerbose)
+ {
+ report("%d symbols defined\n", i);
+ hashtable_stats(symtable);
+ hashtable_stats(optable);
+ }
+ }
+#endif /* Q3ASM_TURBO */
+
end = I_FloatTime ();
- printf ("%5.0f seconds elapsed\n", end-start);
+ report ("%5.0f seconds elapsed\n", end-start);
- return 0;
+ return errorCount;
}