diff options
| author | tma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea> | 2005-10-05 17:59:10 +0000 | 
|---|---|---|
| committer | tma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea> | 2005-10-05 17:59:10 +0000 | 
| commit | 7fe2b280297f57c03b366e445920940142d083c4 (patch) | |
| tree | b1301a8833ae4f0aeb0a541e9b80eebbe83008cb /code/tools | |
| parent | 8305a120fda7c4039d3b8e4f40ceb6cc14015f21 (diff) | |
| download | ioquake3-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/Makefile | 4 | ||||
| -rw-r--r-- | code/tools/asm/q3asm.c | 1091 | 
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;  }  | 
