ioq3quest/q3asm/q3asm.c

1048 lines
21 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "cmdlib.h"
#include "mathlib.h"
#include "qfiles.h"
/* MSVC-ism fix. */
#define atoi(s) strtoul(s,NULL,10)
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
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;
segment_t segment[NUM_SEGMENTS];
segment_t *currentSegment;
int passNumber;
int numSymbols;
int errorCount;
symbol_t *symbols;
symbol_t *lastSymbol;
#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 ];
/*
=============
HashString
=============
*/
int HashString( char *s ) {
int v = 0;
while ( *s ) {
v += *s;
s++;
}
return v;
}
/*
============
CodeError
============
*/
void CodeError( char *fmt, ... ) {
va_list argptr;
errorCount++;
printf( "%s:%i ", currentFileName, currentFileLine );
va_start( argptr,fmt );
vprintf( fmt,argptr );
va_end( argptr );
}
/*
============
EmitByte
============
*/
void EmitByte( segment_t *seg, int v ) {
if ( seg->imageUsed >= MAX_IMAGE ) {
Error( "MAX_IMAGE" );
}
seg->image[ seg->imageUsed ] = v;
seg->imageUsed++;
}
/*
============
EmitInt
============
*/
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
============
*/
void DefineSymbol( char *sym, int value ) {
symbol_t *s, *after;
char expanded[MAX_LINE_LENGTH];
int hash;
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
if (!Q_stricmp(sym, "vmMain"))
if (value)
Error( "vmMain must be the first symbol in the qvm (got offset %d)\n", value );
// add the file prefix to local symbols to guarantee unique
if ( sym[0] == '$' ) {
sprintf( expanded, "%s_%i", sym, currentFileIndex );
sym = expanded;
}
hash = HashString( sym );
for ( s = symbols ; s ; s = s->next ) {
if ( hash == s->hash && !strcmp( sym, s->name ) ) {
CodeError( "Multiple definitions for %s\n", sym );
return;
}
}
s = malloc( sizeof( *s ) );
s->name = copystring( sym );
s->hash = hash;
s->value = value;
s->segment = currentSegment;
lastSymbol = s; /* for the move-to-lit-segment byteswap hack */
// insert it in order
if ( !symbols || s->value < symbols->value ) {
s->next = symbols;
symbols = s;
return;
}
for ( after = symbols ; after->next && after->next->value < value ; after = after->next ) {
}
s->next = after->next;
after->next = s;
}
/*
============
LookupSymbol
Symbols can only be evaluated on pass 1
============
*/
int LookupSymbol( char *sym ) {
symbol_t *s;
char expanded[MAX_LINE_LENGTH];
int hash;
if ( passNumber == 0 ) {
return 0;
}
// add the file prefix to local symbols to guarantee unique
if ( sym[0] == '$' ) {
sprintf( expanded, "%s_%i", sym, currentFileIndex );
sym = expanded;
}
hash = HashString( sym );
for ( s = symbols ; s ; s = s->next ) {
if ( hash == s->hash && !strcmp( sym, s->name ) ) {
return s->segment->segmentBase + s->value;
}
}
CodeError( "ERROR: symbol %s undefined\n", sym );
passNumber = 0;
DefineSymbol( sym, 0 ); // so more errors aren't printed
passNumber = 1;
return 0;
}
/*
==============
ExtractLine
Extracts the next line from the given text block.
If a full line isn't parsed, returns NULL
Otherwise returns the updated parse pointer
===============
*/
char *ExtractLine( char *data ) {
int i;
currentFileLine++;
lineParseOffset = 0;
token[0] = 0;
if ( data[0] == 0 ) {
lineBuffer[0] = 0;
return NULL;
}
for ( i = 0 ; i < MAX_LINE_LENGTH ; i++ ) {
if ( data[i] == 0 || data[i] == '\n' ) {
break;
}
}
if ( i == MAX_LINE_LENGTH ) {
CodeError( "MAX_LINE_LENGTH" );
return data;
}
memcpy( lineBuffer, data, i );
lineBuffer[i] = 0;
data += i;
if ( data[0] == '\n' ) {
data++;
}
return data;
}
/*
==============
Parse
Parse a token out of linebuffer
==============
*/
qboolean Parse( void ) {
int c;
int len;
len = 0;
token[0] = 0;
// skip whitespace
while ( lineBuffer[ lineParseOffset ] <= ' ' ) {
if ( lineBuffer[ lineParseOffset ] == 0 ) {
return qfalse;
}
lineParseOffset++;
}
// skip ; comments
c = lineBuffer[ lineParseOffset ];
if ( c == ';' ) {
return qfalse;
}
// parse a regular word
do {
token[len] = c;
len++;
lineParseOffset++;
c = lineBuffer[ lineParseOffset ];
} while (c>32);
token[len] = 0;
return qtrue;
}
/*
==============
ParseValue
==============
*/
int ParseValue( void ) {
Parse();
return atoi( token );
}
/*
==============
ParseExpression
==============
*/
int ParseExpression(void) {
int i, j;
char sym[MAX_LINE_LENGTH];
int v;
if ( token[0] == '-' ) {
i = 1;
} else {
i = 0;
}
for ( ; i < MAX_LINE_LENGTH ; i++ ) {
if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) {
break;
}
}
memcpy( sym, token, i );
sym[i] = 0;
if ( ( sym[0] >= '0' && sym[0] <= '9' ) || sym[0] == '-' ) {
v = atoi( sym );
} else {
v = LookupSymbol( sym );
}
// parse add / subtract offsets
while ( token[i] != 0 ) {
for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) {
if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) {
break;
}
}
memcpy( sym, token+i+1, j-i-1 );
sym[j-i-1] = 0;
if ( token[i] == '+' ) {
v += atoi( sym );
}
if ( token[i] == '-' ) {
v -= atoi( sym );
}
i = j;
}
return v;
}
/*
==============
HackToSegment
BIG HACK: I want to put all 32 bit values in the data
segment so they can be byte swapped, and all char data in the lit
segment, but switch jump tables are emited in the lit segment and
initialized strng variables are put in the data segment.
I can change segments here, but I also need to fixup the
label that was just defined
Note that the lit segment is read-write in the VM, so strings
aren't read only as in some architectures.
==============
*/
void HackToSegment( segmentName_t seg ) {
if ( currentSegment == &segment[seg] ) {
return;
}
currentSegment = &segment[seg];
if ( passNumber == 0 ) {
lastSymbol->segment = currentSegment;
lastSymbol->value = currentSegment->imageUsed;
}
}
/*
==============
AssembleLine
==============
*/
void AssembleLine( void ) {
int v, v2;
int i;
int hash;
Parse();
if ( !token[0] ) {
return;
}
hash = HashString( token );
for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
if ( hash == opcodesHash[i] && !strcmp( token, sourceOps[i].name ) ) {
int opcode;
int expression;
if ( sourceOps[i].opcode == OP_UNDEF ) {
CodeError( "Undefined opcode: %s\n", token );
}
if ( sourceOps[i].opcode == OP_IGNORE ) {
return; // we ignore most conversions
}
// sign extensions need to check next parm
opcode = sourceOps[i].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] && sourceOps[i].opcode != OP_CVIF
&& sourceOps[i].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;
}
}
// call instructions reset currentArgOffset
if ( !strncmp( token, "CALL", 4 ) ) {
EmitByte( &segment[CODESEG], OP_CALL );
instructionCount++;
currentArgOffset = 0;
return;
}
// arg is converted to a reversed store
if ( !strncmp( token, "ARG", 3 ) ) {
EmitByte( &segment[CODESEG], OP_ARG );
instructionCount++;
if ( 8 + currentArgOffset >= 256 ) {
CodeError( "currentArgOffset >= 256" );
return;
}
EmitByte( &segment[CODESEG], 8 + currentArgOffset );
currentArgOffset += 4;
return;
}
// ret just leaves something on the op stack
if ( !strncmp( token, "RET", 3 ) ) {
EmitByte( &segment[CODESEG], OP_LEAVE );
instructionCount++;
EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
return;
}
// pop is needed to discard the return value of
// a function
if ( !strncmp( token, "pop", 3 ) ) {
EmitByte( &segment[CODESEG], OP_POP );
instructionCount++;
return;
}
// address of a parameter is converted to OP_LOCAL
if ( !strncmp( token, "ADDRF", 5 ) ) {
instructionCount++;
Parse();
v = ParseExpression();
v = 16 + currentArgs + currentLocals + v;
EmitByte( &segment[CODESEG], OP_LOCAL );
EmitInt( &segment[CODESEG], v );
return;
}
// address of a local is converted to OP_LOCAL
if ( !strncmp( token, "ADDRL", 5 ) ) {
instructionCount++;
Parse();
v = ParseExpression();
v = 8 + currentArgs + v;
EmitByte( &segment[CODESEG], OP_LOCAL );
EmitInt( &segment[CODESEG], v );
return;
}
if ( !strcmp( token, "proc" ) ) {
char name[1024];
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;
}
if ( !strcmp( token, "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;
}
if ( !strcmp( token, "address" ) ) {
Parse();
v = ParseExpression();
HackToSegment( DATASEG );
EmitInt( currentSegment, v );
return;
}
if ( !strcmp( token, "export" ) ) {
return;
}
if ( !strcmp( token, "import" ) ) {
return;
}
if ( !strcmp( token, "code" ) ) {
currentSegment = &segment[CODESEG];
return;
}
if ( !strcmp( token, "bss" ) ) {
currentSegment = &segment[BSSSEG];
return;
}
if ( !strcmp( token, "data" ) ) {
currentSegment = &segment[DATASEG];
return;
}
if ( !strcmp( token, "lit" ) ) {
currentSegment = &segment[LITSEG];
return;
}
if ( !strcmp( token, "line" ) ) {
return;
}
if ( !strcmp( token, "file" ) ) {
return;
}
if ( !strcmp( token, "equ" ) ) {
char name[1024];
Parse();
strcpy( name, token );
Parse();
DefineSymbol( name, atoi(token) );
return;
}
if ( !strcmp( token, "align" ) ) {
v = ParseValue();
currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
return;
}
if ( !strcmp( token, "skip" ) ) {
v = ParseValue();
currentSegment->imageUsed += v;
return;
}
if ( !strcmp( token, "byte" ) ) {
v = ParseValue();
v2 = ParseValue();
if ( v == 1 ) {
HackToSegment( LITSEG );
} else if ( v == 4 ) {
HackToSegment( DATASEG );
} else if ( v == 2 ) {
CodeError( "16 bit initialized data not supported" );
}
// emit little endien
for ( i = 0 ; i < v ; i++ ) {
EmitByte( currentSegment, v2 );
v2 >>= 8;
}
return;
}
// 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
if ( !strncmp( token, "LABEL", 5 ) ) {
Parse();
if ( currentSegment == &segment[CODESEG] ) {
DefineSymbol( token, instructionCount );
} else {
DefineSymbol( token, currentSegment->imageUsed );
}
return;
}
CodeError( "Unknown token: %s\n", token );
}
/*
==============
InitTables
==============
*/
void InitTables( void ) {
int i;
for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
opcodesHash[i] = HashString( sourceOps[i].name );
}
}
/*
==============
WriteMapFile
==============
*/
void WriteMapFile( void ) {
FILE *f;
symbol_t *s;
char imageName[MAX_OS_PATH];
int seg;
strcpy( imageName, outputFilename );
StripExtension( imageName );
strcat( imageName, ".map" );
printf( "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
===============
*/
void WriteVmFile( void ) {
char imageName[MAX_OS_PATH];
vmHeader_t header;
FILE *f;
printf( "%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 );
if ( errorCount != 0 ) {
printf( "Not writing a file due to errors\n" );
return;
}
header.vmMagic = VM_MAGIC;
header.instructionCount = instructionCount;
header.codeOffset = sizeof( header );
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;
printf( "Writing to %s\n", imageName );
CreatePath( imageName );
f = SafeOpenWrite( imageName );
SafeWrite( f, &header, sizeof( header ) );
SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
fclose( f );
}
/*
===============
Assemble
===============
*/
void Assemble( void ) {
int i;
char filename[MAX_OS_PATH];
char *ptr;
printf( "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;
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;
printf("pass %i: %s\n", passNumber, currentFileName );
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;
}
}
// 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
WriteMapFile();
}
/*
=============
ParseOptionFile
=============
*/
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++;
}
}
/*
==============
main
==============
*/
int main( int argc, char **argv ) {
int i;
double start, end;
// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
if ( argc < 2 ) {
Error( "usage: q3asm [-o output] <files> or q3asm -f <listfile>\n" );
}
start = I_FloatTime ();
InitTables();
// default filename is "q3asm"
strcpy( outputFilename, "q3asm" );
numAsmFiles = 0;
for ( i = 1 ; i < argc ; i++ ) {
if ( argv[i][0] != '-' ) {
break;
}
if ( !strcmp( argv[i], "-o" ) ) {
if ( i == argc - 1 ) {
Error( "-o must preceed a filename" );
}
strcpy( outputFilename, argv[ i+1 ] );
i++;
continue;
}
if ( !strcmp( argv[i], "-f" ) ) {
if ( i == argc - 1 ) {
Error( "-f must preceed a filename" );
}
ParseOptionFile( argv[ i+1 ] );
i++;
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++;
}
Assemble();
end = I_FloatTime ();
printf ("%5.0f seconds elapsed\n", end-start);
return 0;
}