/* =========================================================================== 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] or q3asm -f \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; }