r141 - trunk/code/tools/asm

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Wed Oct 5 13:59:11 EDT 2005


Author: tma
Date: 2005-10-05 13:59:10 -0400 (Wed, 05 Oct 2005)
New Revision: 141

Modified:
   trunk/code/tools/asm/Makefile
   trunk/code/tools/asm/q3asm.c
Log:
* 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


Modified: trunk/code/tools/asm/Makefile
===================================================================
--- trunk/code/tools/asm/Makefile	2005-10-05 14:50:45 UTC (rev 140)
+++ trunk/code/tools/asm/Makefile	2005-10-05 17:59:10 UTC (rev 141)
@@ -8,12 +8,12 @@
 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

Modified: trunk/code/tools/asm/q3asm.c
===================================================================
--- trunk/code/tools/asm/q3asm.c	2005-10-05 14:50:45 UTC (rev 140)
+++ trunk/code/tools/asm/q3asm.c	2005-10-05 17:59:10 UTC (rev 141)
@@ -25,8 +25,14 @@
 #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];
 
 // the zero page size is just used for detecting run time faults
@@ -149,7 +155,22 @@
 	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		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 @@
 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,8 +480,33 @@
 	}
 	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 */
+
+
 /*
 ============
 CodeError
@@ -228,7 +517,7 @@
 
 	errorCount++;
 
-	printf( "%s:%i ", currentFileName, currentFileLine );
+	report( "%s:%i ", currentFileName, currentFileLine );
 
 	va_start( argptr,fmt );
 	vprintf( fmt,argptr );
@@ -272,6 +561,7 @@
 ============
 */
 void DefineSymbol( char *sym, int value ) {
+#ifndef Q3ASM_TURBO
 	symbol_t	*s, *after;
 	char		expanded[MAX_LINE_LENGTH];
 	int			hash;
@@ -279,7 +569,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
@@ -321,6 +611,53 @@
 	}
 	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 @@
 ============
 */
 int LookupSymbol( char *sym ) {
+#ifndef Q3ASM_TURBO
 	symbol_t	*s;
 	char		expanded[MAX_LINE_LENGTH];
 	int			hash;
@@ -358,6 +696,43 @@
 	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 @@
 ===============
 */
 char *ExtractLine( char *data ) {
+#ifndef Q3ASM_TURBO
 	int			i;
 
 	currentFileLine++;
@@ -398,6 +774,38 @@
 		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 @@
 ==============
 */
 qboolean Parse( void ) {
+#ifndef Q3ASM_TURBO
 	int		c;
 	int		len;
 	
@@ -440,6 +849,35 @@
 	
 	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 @@
 ==============
 */
 int	ParseExpression(void) {
+#ifndef Q3ASM_TURBO
 	int		i, j;
 	char	sym[MAX_LINE_LENGTH];
 	int		v;
@@ -506,6 +945,59 @@
 	}
 
 	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,14 +1029,379 @@
 	}
 }
 
+
+
+
+
+
+
+#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
 
 ==============
 */
 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 @@
 
 	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,7 +1654,125 @@
 		}
 		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 @@
 ==============
 */
 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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 			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 @@
 		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 @@
 	WriteVmFile();
 
 	// write the map file even if there were errors
-	WriteMapFile();
+	if( optionWriteMapFile ) {
+		WriteMapFile();
+	}
 }
 
 
@@ -998,11 +1996,24 @@
 //	_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 @@
 			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 @@
 			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 @@
 		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;
 }
 




More information about the quake3-commits mailing list