q3rally/engine/code/tools/asm/q3asm.c
zturtleman c55f11d8b9 ioquake3 resync to revision 3393 from 3385.
OpenGL2: Use ri.Error instead of Com_Error in tr_vbo.c
Fix Team Arena server refresh time format
Fix -1 (unlimited) ammo decreasing ammo time remaining
Correct spelling mistakes
Fix invalid model frame developer warnings in Team Arena
2017-12-03 10:09:52 +00:00

1647 lines
34 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "../../qcommon/q_platform.h"
#include "cmdlib.h"
#include "mathlib.h"
#include "../../qcommon/qfiles.h"
/* 19079 total symbols in FI, 2002 Jan 23 */
#define DEFAULT_HASHTABLE_SIZE 2048
char outputFilename[MAX_OS_PATH];
// the zero page size is just used for detecting run time faults
#define ZERO_PAGE_SIZE 0 // 256
typedef enum {
OP_UNDEF,
OP_IGNORE,
OP_BREAK,
OP_ENTER,
OP_LEAVE,
OP_CALL,
OP_PUSH,
OP_POP,
OP_CONST,
OP_LOCAL,
OP_JUMP,
//-------------------
OP_EQ,
OP_NE,
OP_LTI,
OP_LEI,
OP_GTI,
OP_GEI,
OP_LTU,
OP_LEU,
OP_GTU,
OP_GEU,
OP_EQF,
OP_NEF,
OP_LTF,
OP_LEF,
OP_GTF,
OP_GEF,
//-------------------
OP_LOAD1,
OP_LOAD2,
OP_LOAD4,
OP_STORE1,
OP_STORE2,
OP_STORE4, // *(stack[top-1]) = stack[yop
OP_ARG,
OP_BLOCK_COPY,
//-------------------
OP_SEX8,
OP_SEX16,
OP_NEGI,
OP_ADD,
OP_SUB,
OP_DIVI,
OP_DIVU,
OP_MODI,
OP_MODU,
OP_MULI,
OP_MULU,
OP_BAND,
OP_BOR,
OP_BXOR,
OP_BCOM,
OP_LSH,
OP_RSHI,
OP_RSHU,
OP_NEGF,
OP_ADDF,
OP_SUBF,
OP_DIVF,
OP_MULF,
OP_CVIF,
OP_CVFI
} opcode_t;
typedef struct {
int imageBytes; // after decompression
int entryPoint;
int stackBase;
int stackSize;
} executableHeader_t;
typedef enum {
CODESEG,
DATASEG, // initialized 32 bit data, will be byte swapped
LITSEG, // strings
BSSSEG, // 0 filled
JTRGSEG, // pseudo-segment that contains only jump table targets
NUM_SEGMENTS
} segmentName_t;
#define MAX_IMAGE 0x400000
typedef struct {
byte image[MAX_IMAGE];
int imageUsed;
int segmentBase; // only valid on second pass
} segment_t;
typedef struct symbol_s {
struct symbol_s *next;
int hash;
segment_t *segment;
char *name;
int value;
} symbol_t;
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;
segment_t segment[NUM_SEGMENTS];
segment_t *currentSegment;
int passNumber;
int numSymbols;
int errorCount;
typedef struct options_s {
qboolean verbose;
qboolean writeMapFile;
qboolean vanillaQ3Compatibility;
} options_t;
options_t options = { 0 };
symbol_t *symbols;
symbol_t *lastSymbol = 0; /* Most recent symbol defined. */
#define MAX_ASM_FILES 256
int numAsmFiles;
char *asmFiles[MAX_ASM_FILES];
char *asmFileNames[MAX_ASM_FILES];
int currentFileIndex;
char *currentFileName;
int currentFileLine;
//int stackSize = 16384;
int stackSize = 0x10000;
// we need to convert arg and ret instructions to
// stores to the local stack frame, so we need to track the
// characteristics of the current functions stack frame
int currentLocals; // bytes of locals needed by this function
int currentArgs; // bytes of largest argument list called from this function
int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call
#define MAX_LINE_LENGTH 1024
char lineBuffer[MAX_LINE_LENGTH];
int lineParseOffset;
char token[MAX_LINE_LENGTH];
int instructionCount;
typedef struct {
char *name;
int opcode;
} sourceOps_t;
sourceOps_t sourceOps[] = {
#include "opstrings.h"
};
#define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) )
int opcodesHash[ NUM_SOURCE_OPS ];
static int vreport (const char* fmt, va_list vp)
{
if (options.verbose != qtrue)
return 0;
return vprintf(fmt, vp);
}
static 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 */
static void hashtable_init (hashtable_t *H, int buckets)
{
H->buckets = buckets;
H->table = calloc(H->buckets, sizeof(*(H->table)));
}
static 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. */
static 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;
}
static hashchain_t *hashtable_get (hashtable_t *H, int hashvalue)
{
hashvalue = (abs(hashvalue) % H->buckets);
return (H->table[hashvalue]);
}
static 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. */
static 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. */
static 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
*/
static void sort_symbols ()
{
int i, elems;
symbol_t *s;
symbol_t **symlist;
if(!symbols)
return;
//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. */
}
#ifdef _MSC_VER
#define INT64 __int64
#define atoi64 _atoi64
#else
#define INT64 long long int
#define atoi64 atoll
#endif
/*
Problem:
BYTE values are specified as signed decimal string. A properly functional
atoip() 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.
*/
static int atoiNoCap (const char *s)
{
INT64 l;
union {
unsigned int u;
signed int i;
} retval;
l = atoi64(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 */
}
/*
=============
HashString
=============
*/
/* Default hash function of Kazlib 1.19, slightly modified. */
static 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 acc;
}
/*
============
CodeError
============
*/
static void CodeError( char *fmt, ... ) {
va_list argptr;
errorCount++;
fprintf( stderr, "%s:%i ", currentFileName, currentFileLine );
va_start( argptr,fmt );
vfprintf( stderr, fmt, argptr );
va_end( argptr );
}
/*
============
EmitByte
============
*/
static void EmitByte( segment_t *seg, int v ) {
if ( seg->imageUsed >= MAX_IMAGE ) {
Error( "MAX_IMAGE" );
}
seg->image[ seg->imageUsed ] = v;
seg->imageUsed++;
}
/*
============
EmitInt
============
*/
static void EmitInt( segment_t *seg, int v ) {
if ( seg->imageUsed >= MAX_IMAGE - 4) {
Error( "MAX_IMAGE" );
}
seg->image[ seg->imageUsed ] = v & 255;
seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255;
seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255;
seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255;
seg->imageUsed += 4;
}
/*
============
DefineSymbol
Symbols can only be defined on pass 0
============
*/
static void DefineSymbol( char *sym, int value ) {
/* 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;
}
}
/*
============
LookupSymbol
Symbols can only be evaluated on pass 1
============
*/
static int LookupSymbol( char *sym ) {
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;
}
/*
==============
ExtractLine
Extracts the next line from the given text block.
If a full line isn't parsed, returns NULL
Otherwise returns the updated parse pointer
===============
*/
static char *ExtractLine( char *data ) {
/* 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;
}
/*
==============
Parse
Parse a token out of linebuffer
==============
*/
static qboolean Parse( void ) {
/* 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;
}
/*
==============
ParseValue
==============
*/
static int ParseValue( void ) {
Parse();
return atoiNoCap( token );
}
/*
==============
ParseExpression
==============
*/
static int ParseExpression(void) {
/* 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 = atoiNoCap(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 += atoiNoCap(sym);
break;
case '-':
v -= atoiNoCap(sym);
break;
}
i = j;
}
return v;
}
/*
==============
HackToSegment
BIG HACK: I want to put all 32 bit values in the data
segment so they can be byte swapped, and all char data in the lit
segment, but switch jump tables are emitted in the lit segment and
initialized strng variables are put in the data segment.
I can change segments here, but I also need to fixup the
label that was just defined
Note that the lit segment is read-write in the VM, so strings
aren't read only as in some architectures.
==============
*/
static void HackToSegment( segmentName_t seg ) {
if ( currentSegment == &segment[seg] ) {
return;
}
currentSegment = &segment[seg];
if ( passNumber == 0 ) {
lastSymbol->segment = currentSegment;
lastSymbol->value = currentSegment->imageUsed;
}
}
//#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)
{
if ( !strcmp( token, "endproc" ) ) {
STAT("ENDPROC");
Parse(); // skip the function name
ParseValue(); // locals
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 );
if( passNumber == 1 && token[ 0 ] == '$' ) // crude test for labels
EmitInt( &segment[ JTRGSEG ], 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, atoiNoCap(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 emitted 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;
}
/*
==============
AssembleLine
==============
*/
static void AssembleLine( void ) {
hashchain_t *hc;
sourceOps_t *op;
int i;
int hash;
Parse();
if ( !token[0] ) {
return;
}
hash = HashString( token );
/*
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)
CodeError( "Unknown token: %s\n", token );
}
/*
==============
InitTables
==============
*/
void InitTables( void ) {
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);
}
}
/*
==============
WriteMapFile
==============
*/
static void WriteMapFile( void ) {
FILE *f;
symbol_t *s;
char imageName[MAX_OS_PATH];
int seg;
strcpy( imageName, outputFilename );
StripExtension( imageName );
strcat( imageName, ".map" );
report( "Writing %s...\n", imageName );
f = SafeOpenWrite( imageName );
for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) {
for ( s = symbols ; s ; s = s->next ) {
if ( s->name[0] == '$' ) {
continue; // skip locals
}
if ( &segment[seg] != s->segment ) {
continue;
}
fprintf( f, "%i %8x %s\n", seg, s->value, s->name );
}
}
fclose( f );
}
/*
===============
WriteVmFile
===============
*/
static void WriteVmFile( void ) {
char imageName[MAX_OS_PATH];
vmHeader_t header;
FILE *f;
int headerSize;
report( "%i total errors\n", errorCount );
strcpy( imageName, outputFilename );
StripExtension( imageName );
strcat( imageName, ".qvm" );
remove( imageName );
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 ) {
report( "Not writing a file due to errors\n" );
return;
}
if( !options.vanillaQ3Compatibility ) {
header.vmMagic = VM_MAGIC_VER2;
headerSize = sizeof( header );
} else {
header.vmMagic = VM_MAGIC;
// Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility.
// (I know this isn't strictly correct due to padding, but then platforms
// that pad wouldn't be able to write a correct header anyway). Note: if
// vmHeader_t changes, this needs to be adjusted too.
headerSize = sizeof( header ) - sizeof( header.jtrgLength );
}
header.instructionCount = instructionCount;
header.codeOffset = headerSize;
header.codeLength = segment[CODESEG].imageUsed;
header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed;
header.dataLength = segment[DATASEG].imageUsed;
header.litLength = segment[LITSEG].imageUsed;
header.bssLength = segment[BSSSEG].imageUsed;
header.jtrgLength = segment[JTRGSEG].imageUsed;
report( "Writing to %s\n", imageName );
#ifdef Q3_BIG_ENDIAN
{
int i;
// byte swap the header
for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
((int *)&header)[i] = LittleLong( ((int *)&header)[i] );
}
}
#endif
CreatePath( imageName );
f = SafeOpenWrite( imageName );
SafeWrite( f, &header, headerSize );
SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
if( !options.vanillaQ3Compatibility ) {
SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed );
}
fclose( f );
}
/*
===============
Assemble
===============
*/
static void Assemble( void ) {
int i;
char filename[MAX_OS_PATH];
char *ptr;
report( "outputFilename: %s\n", outputFilename );
for ( i = 0 ; i < numAsmFiles ; i++ ) {
strcpy( filename, asmFileNames[ i ] );
DefaultExtension( filename, ".asm" );
LoadFile( filename, (void **)&asmFiles[i] );
}
// assemble
for ( passNumber = 0 ; passNumber < 2 ; passNumber++ ) {
segment[LITSEG].segmentBase = segment[DATASEG].imageUsed;
segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed;
segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed;
for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
segment[i].imageUsed = 0;
}
segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly
instructionCount = 0;
for ( i = 0 ; i < numAsmFiles ; i++ ) {
currentFileIndex = i;
currentFileName = asmFileNames[ i ];
currentFileLine = 0;
report("pass %i: %s\n", passNumber, currentFileName );
fflush( NULL );
ptr = asmFiles[i];
while ( ptr ) {
ptr = ExtractLine( ptr );
AssembleLine();
}
}
// align all segment
for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3;
}
if (passNumber == 0) {
sort_symbols();
}
}
// reserve the stack in bss
DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed );
segment[BSSSEG].imageUsed += stackSize;
DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed );
// write the image
WriteVmFile();
// write the map file even if there were errors
if( options.writeMapFile ) {
WriteMapFile();
}
}
/*
=============
ParseOptionFile
=============
*/
static void ParseOptionFile( const char *filename ) {
char expanded[MAX_OS_PATH];
char *text, *text_p;
strcpy( expanded, filename );
DefaultExtension( expanded, ".q3asm" );
LoadFile( expanded, (void **)&text );
if ( !text ) {
return;
}
text_p = text;
while( ( text_p = COM_Parse( text_p ) ) != 0 ) {
if ( !strcmp( com_token, "-o" ) ) {
// allow output override in option file
text_p = COM_Parse( text_p );
if ( text_p ) {
strcpy( outputFilename, com_token );
}
continue;
}
asmFileNames[ numAsmFiles ] = copystring( com_token );
numAsmFiles++;
}
}
static void ShowHelp( char *argv0 ) {
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.q3asm\n\
-b BUCKETS Set symbol hash table to BUCKETS buckets\n\
-m Generate a mapfile for each OUTPUT.qvm\n\
-v Verbose compilation report\n\
-vq3 Produce a qvm file compatible with Q3 1.32b\n\
-h --help -? Show this help\n\
", argv0);
}
/*
==============
main
==============
*/
int main( int argc, char **argv ) {
int i;
double start, end;
// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
if ( argc < 2 ) {
ShowHelp( argv[0] );
}
start = I_FloatTime ();
// default filename is "q3asm"
strcpy( outputFilename, "q3asm" );
numAsmFiles = 0;
for ( i = 1 ; i < argc ; i++ ) {
if ( argv[i][0] != '-' ) {
break;
}
if( !strcmp( argv[ i ], "-h" ) ||
!strcmp( argv[ i ], "--help" ) ||
!strcmp( argv[ i ], "-?") ) {
ShowHelp( argv[0] );
}
if ( !strcmp( argv[i], "-o" ) ) {
if ( i == argc - 1 ) {
Error( "-o must precede 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;
}
if ( !strcmp( argv[i], "-f" ) ) {
if ( i == argc - 1 ) {
Error( "-f must precede a filename" );
}
ParseOptionFile( argv[ i+1 ] );
i++;
continue;
}
if (!strcmp(argv[i], "-b")) {
if (i == argc - 1) {
Error("-b requires an argument");
}
i++;
symtablelen = atoiNoCap(argv[i]);
continue;
}
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.
*/
options.verbose = qtrue;
continue;
}
if( !strcmp( argv[ i ], "-m" ) ) {
options.writeMapFile = qtrue;
continue;
}
if( !strcmp( argv[ i ], "-vq3" ) ) {
options.vanillaQ3Compatibility = qtrue;
continue;
}
Error( "Unknown option: %s", argv[i] );
}
// the rest of the command line args are asm files
for ( ; i < argc ; i++ ) {
asmFileNames[ numAsmFiles ] = copystring( argv[ i ] );
numAsmFiles++;
}
// In some case it Segfault without this check
if ( numAsmFiles == 0 ) {
Error( "No file to assemble" );
}
InitTables();
Assemble();
{
symbol_t *s;
for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ;
if (options.verbose)
{
report("%d symbols defined\n", i);
hashtable_stats(symtable);
hashtable_stats(optable);
}
}
end = I_FloatTime ();
report ("%5.0f seconds elapsed\n", end-start);
return errorCount;
}