Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

vm.c (18794B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 // vm.c -- virtual machine
     23 
     24 /*
     25 
     26 
     27 intermix code and data
     28 symbol table
     29 
     30 a dll has one imported function: VM_SystemCall
     31 and one exported function: Perform
     32 
     33 
     34 */
     35 
     36 #include "vm_local.h"
     37 
     38 
     39 vm_t	*currentVM = NULL; // bk001212
     40 vm_t	*lastVM    = NULL; // bk001212
     41 int		vm_debugLevel;
     42 
     43 #define	MAX_VM		3
     44 vm_t	vmTable[MAX_VM];
     45 
     46 
     47 void VM_VmInfo_f( void );
     48 void VM_VmProfile_f( void );
     49 
     50 
     51 // converts a VM pointer to a C pointer and
     52 // checks to make sure that the range is acceptable
     53 void	*VM_VM2C( vmptr_t p, int length ) {
     54 	return (void *)p;
     55 }
     56 
     57 void VM_Debug( int level ) {
     58 	vm_debugLevel = level;
     59 }
     60 
     61 /*
     62 ==============
     63 VM_Init
     64 ==============
     65 */
     66 void VM_Init( void ) {
     67 	Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2
     68 	Cvar_Get( "vm_game", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2
     69 	Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE );		// !@# SHIP WITH SET TO 2
     70 
     71 	Cmd_AddCommand ("vmprofile", VM_VmProfile_f );
     72 	Cmd_AddCommand ("vminfo", VM_VmInfo_f );
     73 
     74 	Com_Memset( vmTable, 0, sizeof( vmTable ) );
     75 }
     76 
     77 
     78 /*
     79 ===============
     80 VM_ValueToSymbol
     81 
     82 Assumes a program counter value
     83 ===============
     84 */
     85 const char *VM_ValueToSymbol( vm_t *vm, int value ) {
     86 	vmSymbol_t	*sym;
     87 	static char		text[MAX_TOKEN_CHARS];
     88 
     89 	sym = vm->symbols;
     90 	if ( !sym ) {
     91 		return "NO SYMBOLS";
     92 	}
     93 
     94 	// find the symbol
     95 	while ( sym->next && sym->next->symValue <= value ) {
     96 		sym = sym->next;
     97 	}
     98 
     99 	if ( value == sym->symValue ) {
    100 		return sym->symName;
    101 	}
    102 
    103 	Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
    104 
    105 	return text;
    106 }
    107 
    108 /*
    109 ===============
    110 VM_ValueToFunctionSymbol
    111 
    112 For profiling, find the symbol behind this value
    113 ===============
    114 */
    115 vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
    116 	vmSymbol_t	*sym;
    117 	static vmSymbol_t	nullSym;
    118 
    119 	sym = vm->symbols;
    120 	if ( !sym ) {
    121 		return &nullSym;
    122 	}
    123 
    124 	while ( sym->next && sym->next->symValue <= value ) {
    125 		sym = sym->next;
    126 	}
    127 
    128 	return sym;
    129 }
    130 
    131 
    132 /*
    133 ===============
    134 VM_SymbolToValue
    135 ===============
    136 */
    137 int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
    138 	vmSymbol_t	*sym;
    139 
    140 	for ( sym = vm->symbols ; sym ; sym = sym->next ) {
    141 		if ( !strcmp( symbol, sym->symName ) ) {
    142 			return sym->symValue;
    143 		}
    144 	}
    145 	return 0;
    146 }
    147 
    148 
    149 /*
    150 =====================
    151 VM_SymbolForCompiledPointer
    152 =====================
    153 */
    154 const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) {
    155 	int			i;
    156 
    157 	if ( code < (void *)vm->codeBase ) {
    158 		return "Before code block";
    159 	}
    160 	if ( code >= (void *)(vm->codeBase + vm->codeLength) ) {
    161 		return "After code block";
    162 	}
    163 
    164 	// find which original instruction it is after
    165 	for ( i = 0 ; i < vm->codeLength ; i++ ) {
    166 		if ( (void *)vm->instructionPointers[i] > code ) {
    167 			break;
    168 		}
    169 	}
    170 	i--;
    171 
    172 	// now look up the bytecode instruction pointer
    173 	return VM_ValueToSymbol( vm, i );
    174 }
    175 
    176 
    177 
    178 /*
    179 ===============
    180 ParseHex
    181 ===============
    182 */
    183 int	ParseHex( const char *text ) {
    184 	int		value;
    185 	int		c;
    186 
    187 	value = 0;
    188 	while ( ( c = *text++ ) != 0 ) {
    189 		if ( c >= '0' && c <= '9' ) {
    190 			value = value * 16 + c - '0';
    191 			continue;
    192 		}
    193 		if ( c >= 'a' && c <= 'f' ) {
    194 			value = value * 16 + 10 + c - 'a';
    195 			continue;
    196 		}
    197 		if ( c >= 'A' && c <= 'F' ) {
    198 			value = value * 16 + 10 + c - 'A';
    199 			continue;
    200 		}
    201 	}
    202 
    203 	return value;
    204 }
    205 
    206 /*
    207 ===============
    208 VM_LoadSymbols
    209 ===============
    210 */
    211 void VM_LoadSymbols( vm_t *vm ) {
    212 	int		len;
    213 	char	*mapfile, *text_p, *token;
    214 	char	name[MAX_QPATH];
    215 	char	symbols[MAX_QPATH];
    216 	vmSymbol_t	**prev, *sym;
    217 	int		count;
    218 	int		value;
    219 	int		chars;
    220 	int		segment;
    221 	int		numInstructions;
    222 
    223 	// don't load symbols if not developer
    224 	if ( !com_developer->integer ) {
    225 		return;
    226 	}
    227 
    228 	COM_StripExtension( vm->name, name );
    229 	Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
    230 	len = FS_ReadFile( symbols, (void **)&mapfile );
    231 	if ( !mapfile ) {
    232 		Com_Printf( "Couldn't load symbol file: %s\n", symbols );
    233 		return;
    234 	}
    235 
    236 	numInstructions = vm->instructionPointersLength >> 2;
    237 
    238 	// parse the symbols
    239 	text_p = mapfile;
    240 	prev = &vm->symbols;
    241 	count = 0;
    242 
    243 	while ( 1 ) {
    244 		token = COM_Parse( &text_p );
    245 		if ( !token[0] ) {
    246 			break;
    247 		}
    248 		segment = ParseHex( token );
    249 		if ( segment ) {
    250 			COM_Parse( &text_p );
    251 			COM_Parse( &text_p );
    252 			continue;		// only load code segment values
    253 		}
    254 
    255 		token = COM_Parse( &text_p );
    256 		if ( !token[0] ) {
    257 			Com_Printf( "WARNING: incomplete line at end of file\n" );
    258 			break;
    259 		}
    260 		value = ParseHex( token );
    261 
    262 		token = COM_Parse( &text_p );
    263 		if ( !token[0] ) {
    264 			Com_Printf( "WARNING: incomplete line at end of file\n" );
    265 			break;
    266 		}
    267 		chars = strlen( token );
    268 		sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
    269 		*prev = sym;
    270 		prev = &sym->next;
    271 		sym->next = NULL;
    272 
    273 		// convert value from an instruction number to a code offset
    274 		if ( value >= 0 && value < numInstructions ) {
    275 			value = vm->instructionPointers[value];
    276 		}
    277 
    278 		sym->symValue = value;
    279 		Q_strncpyz( sym->symName, token, chars + 1 );
    280 
    281 		count++;
    282 	}
    283 
    284 	vm->numSymbols = count;
    285 	Com_Printf( "%i symbols parsed from %s\n", count, symbols );
    286 	FS_FreeFile( mapfile );
    287 }
    288 
    289 /*
    290 ============
    291 VM_DllSyscall
    292 
    293 Dlls will call this directly
    294 
    295  rcg010206 The horror; the horror.
    296 
    297   The syscall mechanism relies on stack manipulation to get it's args.
    298    This is likely due to C's inability to pass "..." parameters to
    299    a function in one clean chunk. On PowerPC Linux, these parameters
    300    are not necessarily passed on the stack, so while (&arg[0] == arg)
    301    is true, (&arg[1] == 2nd function parameter) is not necessarily
    302    accurate, as arg's value might have been stored to the stack or
    303    other piece of scratch memory to give it a valid address, but the
    304    next parameter might still be sitting in a register.
    305 
    306   Quake's syscall system also assumes that the stack grows downward,
    307    and that any needed types can be squeezed, safely, into a signed int.
    308 
    309   This hack below copies all needed values for an argument to a
    310    array in memory, so that Quake can get the correct values. This can
    311    also be used on systems where the stack grows upwards, as the
    312    presumably standard and safe stdargs.h macros are used.
    313 
    314   As for having enough space in a signed int for your datatypes, well,
    315    it might be better to wait for DOOM 3 before you start porting.  :)
    316 
    317   The original code, while probably still inherently dangerous, seems
    318    to work well enough for the platforms it already works on. Rather
    319    than add the performance hit for those platforms, the original code
    320    is still in use there.
    321 
    322   For speed, we just grab 15 arguments, and don't worry about exactly
    323    how many the syscall actually needs; the extra is thrown away.
    324  
    325 ============
    326 */
    327 int QDECL VM_DllSyscall( int arg, ... ) {
    328 #if ((defined __linux__) && (defined __powerpc__))
    329   // rcg010206 - see commentary above
    330   int args[16];
    331   int i;
    332   va_list ap;
    333   
    334   args[0] = arg;
    335   
    336   va_start(ap, arg);
    337   for (i = 1; i < sizeof (args) / sizeof (args[i]); i++)
    338     args[i] = va_arg(ap, int);
    339   va_end(ap);
    340   
    341   return currentVM->systemCall( args );
    342 #else // original id code
    343 	return currentVM->systemCall( &arg );
    344 #endif
    345 }
    346 
    347 /*
    348 =================
    349 VM_Restart
    350 
    351 Reload the data, but leave everything else in place
    352 This allows a server to do a map_restart without changing memory allocation
    353 =================
    354 */
    355 vm_t *VM_Restart( vm_t *vm ) {
    356 	vmHeader_t	*header;
    357 	int			length;
    358 	int			dataLength;
    359 	int			i;
    360 	char		filename[MAX_QPATH];
    361 
    362 	// DLL's can't be restarted in place
    363 	if ( vm->dllHandle ) {
    364 		char	name[MAX_QPATH];
    365 	    int			(*systemCall)( int *parms );
    366 		
    367 		systemCall = vm->systemCall;	
    368 		Q_strncpyz( name, vm->name, sizeof( name ) );
    369 
    370 		VM_Free( vm );
    371 
    372 		vm = VM_Create( name, systemCall, VMI_NATIVE );
    373 		return vm;
    374 	}
    375 
    376 	// load the image
    377 	Com_Printf( "VM_Restart()\n", filename );
    378 	Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
    379 	Com_Printf( "Loading vm file %s.\n", filename );
    380 	length = FS_ReadFile( filename, (void **)&header );
    381 	if ( !header ) {
    382 		Com_Error( ERR_DROP, "VM_Restart failed.\n" );
    383 	}
    384 
    385 	// byte swap the header
    386 	for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) {
    387 		((int *)header)[i] = LittleLong( ((int *)header)[i] );
    388 	}
    389 
    390 	// validate
    391 	if ( header->vmMagic != VM_MAGIC
    392 		|| header->bssLength < 0 
    393 		|| header->dataLength < 0 
    394 		|| header->litLength < 0 
    395 		|| header->codeLength <= 0 ) {
    396 		VM_Free( vm );
    397 		Com_Error( ERR_FATAL, "%s has bad header", filename );
    398 	}
    399 
    400 	// round up to next power of 2 so all data operations can
    401 	// be mask protected
    402 	dataLength = header->dataLength + header->litLength + header->bssLength;
    403 	for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
    404 	}
    405 	dataLength = 1 << i;
    406 
    407 	// clear the data
    408 	Com_Memset( vm->dataBase, 0, dataLength );
    409 
    410 	// copy the intialized data
    411 	Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
    412 
    413 	// byte swap the longs
    414 	for ( i = 0 ; i < header->dataLength ; i += 4 ) {
    415 		*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
    416 	}
    417 
    418 	// free the original file
    419 	FS_FreeFile( header );
    420 
    421 	return vm;
    422 }
    423 
    424 /*
    425 ================
    426 VM_Create
    427 
    428 If image ends in .qvm it will be interpreted, otherwise
    429 it will attempt to load as a system dll
    430 ================
    431 */
    432 
    433 #define	STACK_SIZE	0x20000
    434 
    435 vm_t *VM_Create( const char *module, int (*systemCalls)(int *), 
    436 				vmInterpret_t interpret ) {
    437 	vm_t		*vm;
    438 	vmHeader_t	*header;
    439 	int			length;
    440 	int			dataLength;
    441 	int			i, remaining;
    442 	char		filename[MAX_QPATH];
    443 
    444 	if ( !module || !module[0] || !systemCalls ) {
    445 		Com_Error( ERR_FATAL, "VM_Create: bad parms" );
    446 	}
    447 
    448 	remaining = Hunk_MemoryRemaining();
    449 
    450 	// see if we already have the VM
    451 	for ( i = 0 ; i < MAX_VM ; i++ ) {
    452 		if (!Q_stricmp(vmTable[i].name, module)) {
    453 			vm = &vmTable[i];
    454 			return vm;
    455 		}
    456 	}
    457 
    458 	// find a free vm
    459 	for ( i = 0 ; i < MAX_VM ; i++ ) {
    460 		if ( !vmTable[i].name[0] ) {
    461 			break;
    462 		}
    463 	}
    464 
    465 	if ( i == MAX_VM ) {
    466 		Com_Error( ERR_FATAL, "VM_Create: no free vm_t" );
    467 	}
    468 
    469 	vm = &vmTable[i];
    470 
    471 	Q_strncpyz( vm->name, module, sizeof( vm->name ) );
    472 	vm->systemCall = systemCalls;
    473 
    474 	// never allow dll loading with a demo
    475 	if ( interpret == VMI_NATIVE ) {
    476 		if ( Cvar_VariableValue( "fs_restrict" ) ) {
    477 			interpret = VMI_COMPILED;
    478 		}
    479 	}
    480 
    481 	if ( interpret == VMI_NATIVE ) {
    482 		// try to load as a system dll
    483 		Com_Printf( "Loading dll file %s.\n", vm->name );
    484 		vm->dllHandle = Sys_LoadDll( module, vm->fqpath , &vm->entryPoint, VM_DllSyscall );
    485 		if ( vm->dllHandle ) {
    486 			return vm;
    487 		}
    488 
    489 		Com_Printf( "Failed to load dll, looking for qvm.\n" );
    490 		interpret = VMI_COMPILED;
    491 	}
    492 
    493 	// load the image
    494 	Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
    495 	Com_Printf( "Loading vm file %s.\n", filename );
    496 	length = FS_ReadFile( filename, (void **)&header );
    497 	if ( !header ) {
    498 		Com_Printf( "Failed.\n" );
    499 		VM_Free( vm );
    500 		return NULL;
    501 	}
    502 
    503 	// byte swap the header
    504 	for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) {
    505 		((int *)header)[i] = LittleLong( ((int *)header)[i] );
    506 	}
    507 
    508 	// validate
    509 	if ( header->vmMagic != VM_MAGIC
    510 		|| header->bssLength < 0 
    511 		|| header->dataLength < 0 
    512 		|| header->litLength < 0 
    513 		|| header->codeLength <= 0 ) {
    514 		VM_Free( vm );
    515 		Com_Error( ERR_FATAL, "%s has bad header", filename );
    516 	}
    517 
    518 	// round up to next power of 2 so all data operations can
    519 	// be mask protected
    520 	dataLength = header->dataLength + header->litLength + header->bssLength;
    521 	for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
    522 	}
    523 	dataLength = 1 << i;
    524 
    525 	// allocate zero filled space for initialized and uninitialized data
    526 	vm->dataBase = Hunk_Alloc( dataLength, h_high );
    527 	vm->dataMask = dataLength - 1;
    528 
    529 	// copy the intialized data
    530 	Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
    531 
    532 	// byte swap the longs
    533 	for ( i = 0 ; i < header->dataLength ; i += 4 ) {
    534 		*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
    535 	}
    536 
    537 	// allocate space for the jump targets, which will be filled in by the compile/prep functions
    538 	vm->instructionPointersLength = header->instructionCount * 4;
    539 	vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high );
    540 
    541 	// copy or compile the instructions
    542 	vm->codeLength = header->codeLength;
    543 
    544 	if ( interpret >= VMI_COMPILED ) {
    545 		vm->compiled = qtrue;
    546 		VM_Compile( vm, header );
    547 	} else {
    548 		vm->compiled = qfalse;
    549 		VM_PrepareInterpreter( vm, header );
    550 	}
    551 
    552 	// free the original file
    553 	FS_FreeFile( header );
    554 
    555 	// load the map file
    556 	VM_LoadSymbols( vm );
    557 
    558 	// the stack is implicitly at the end of the image
    559 	vm->programStack = vm->dataMask + 1;
    560 	vm->stackBottom = vm->programStack - STACK_SIZE;
    561 
    562 	Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
    563 
    564 	return vm;
    565 }
    566 
    567 /*
    568 ==============
    569 VM_Free
    570 ==============
    571 */
    572 void VM_Free( vm_t *vm ) {
    573 
    574 	if ( vm->dllHandle ) {
    575 		Sys_UnloadDll( vm->dllHandle );
    576 		Com_Memset( vm, 0, sizeof( *vm ) );
    577 	}
    578 #if 0	// now automatically freed by hunk
    579 	if ( vm->codeBase ) {
    580 		Z_Free( vm->codeBase );
    581 	}
    582 	if ( vm->dataBase ) {
    583 		Z_Free( vm->dataBase );
    584 	}
    585 	if ( vm->instructionPointers ) {
    586 		Z_Free( vm->instructionPointers );
    587 	}
    588 #endif
    589 	Com_Memset( vm, 0, sizeof( *vm ) );
    590 
    591 	currentVM = NULL;
    592 	lastVM = NULL;
    593 }
    594 
    595 void VM_Clear(void) {
    596 	int i;
    597 	for (i=0;i<MAX_VM; i++) {
    598 		if ( vmTable[i].dllHandle ) {
    599 			Sys_UnloadDll( vmTable[i].dllHandle );
    600 		}
    601 		Com_Memset( &vmTable[i], 0, sizeof( vm_t ) );
    602 	}
    603 	currentVM = NULL;
    604 	lastVM = NULL;
    605 }
    606 
    607 void *VM_ArgPtr( int intValue ) {
    608 	if ( !intValue ) {
    609 		return NULL;
    610 	}
    611 	// bk001220 - currentVM is missing on reconnect
    612 	if ( currentVM==NULL )
    613 	  return NULL;
    614 
    615 	if ( currentVM->entryPoint ) {
    616 		return (void *)(currentVM->dataBase + intValue);
    617 	}
    618 	else {
    619 		return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
    620 	}
    621 }
    622 
    623 void *VM_ExplicitArgPtr( vm_t *vm, int intValue ) {
    624 	if ( !intValue ) {
    625 		return NULL;
    626 	}
    627 
    628 	// bk010124 - currentVM is missing on reconnect here as well?
    629 	if ( currentVM==NULL )
    630 	  return NULL;
    631 
    632 	//
    633 	if ( vm->entryPoint ) {
    634 		return (void *)(vm->dataBase + intValue);
    635 	}
    636 	else {
    637 		return (void *)(vm->dataBase + (intValue & vm->dataMask));
    638 	}
    639 }
    640 
    641 
    642 /*
    643 ==============
    644 VM_Call
    645 
    646 
    647 Upon a system call, the stack will look like:
    648 
    649 sp+32	parm1
    650 sp+28	parm0
    651 sp+24	return value
    652 sp+20	return address
    653 sp+16	local1
    654 sp+14	local0
    655 sp+12	arg1
    656 sp+8	arg0
    657 sp+4	return stack
    658 sp		return address
    659 
    660 An interpreted function will immediately execute
    661 an OP_ENTER instruction, which will subtract space for
    662 locals from sp
    663 ==============
    664 */
    665 #define	MAX_STACK	256
    666 #define	STACK_MASK	(MAX_STACK-1)
    667 
    668 int	QDECL VM_Call( vm_t *vm, int callnum, ... ) {
    669 	vm_t	*oldVM;
    670 	int		r;
    671 	int i;
    672 	int args[16];
    673 	va_list ap;
    674 
    675 
    676 	if ( !vm ) {
    677 		Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
    678 	}
    679 
    680 	oldVM = currentVM;
    681 	currentVM = vm;
    682 	lastVM = vm;
    683 
    684 	if ( vm_debugLevel ) {
    685 	  Com_Printf( "VM_Call( %i )\n", callnum );
    686 	}
    687 
    688 	// if we have a dll loaded, call it directly
    689 	if ( vm->entryPoint ) {
    690 		//rcg010207 -  see dissertation at top of VM_DllSyscall() in this file.
    691 		va_start(ap, callnum);
    692 		for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) {
    693 			args[i] = va_arg(ap, int);
    694 		}
    695 		va_end(ap);
    696 
    697 		r = vm->entryPoint( callnum,  args[0],  args[1],  args[2], args[3],
    698                             args[4],  args[5],  args[6], args[7],
    699                             args[8],  args[9], args[10], args[11],
    700                             args[12], args[13], args[14], args[15]);
    701 	} else if ( vm->compiled ) {
    702 		r = VM_CallCompiled( vm, &callnum );
    703 	} else {
    704 		r = VM_CallInterpreted( vm, &callnum );
    705 	}
    706 
    707 	if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL
    708 	  currentVM = oldVM;
    709 	return r;
    710 }
    711 
    712 //=================================================================
    713 
    714 static int QDECL VM_ProfileSort( const void *a, const void *b ) {
    715 	vmSymbol_t	*sa, *sb;
    716 
    717 	sa = *(vmSymbol_t **)a;
    718 	sb = *(vmSymbol_t **)b;
    719 
    720 	if ( sa->profileCount < sb->profileCount ) {
    721 		return -1;
    722 	}
    723 	if ( sa->profileCount > sb->profileCount ) {
    724 		return 1;
    725 	}
    726 	return 0;
    727 }
    728 
    729 /*
    730 ==============
    731 VM_VmProfile_f
    732 
    733 ==============
    734 */
    735 void VM_VmProfile_f( void ) {
    736 	vm_t		*vm;
    737 	vmSymbol_t	**sorted, *sym;
    738 	int			i;
    739 	double		total;
    740 
    741 	if ( !lastVM ) {
    742 		return;
    743 	}
    744 
    745 	vm = lastVM;
    746 
    747 	if ( !vm->numSymbols ) {
    748 		return;
    749 	}
    750 
    751 	sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
    752 	sorted[0] = vm->symbols;
    753 	total = sorted[0]->profileCount;
    754 	for ( i = 1 ; i < vm->numSymbols ; i++ ) {
    755 		sorted[i] = sorted[i-1]->next;
    756 		total += sorted[i]->profileCount;
    757 	}
    758 
    759 	qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
    760 
    761 	for ( i = 0 ; i < vm->numSymbols ; i++ ) {
    762 		int		perc;
    763 
    764 		sym = sorted[i];
    765 
    766 		perc = 100 * (float) sym->profileCount / total;
    767 		Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
    768 		sym->profileCount = 0;
    769 	}
    770 
    771 	Com_Printf("    %9.0f total\n", total );
    772 
    773 	Z_Free( sorted );
    774 }
    775 
    776 /*
    777 ==============
    778 VM_VmInfo_f
    779 
    780 ==============
    781 */
    782 void VM_VmInfo_f( void ) {
    783 	vm_t	*vm;
    784 	int		i;
    785 
    786 	Com_Printf( "Registered virtual machines:\n" );
    787 	for ( i = 0 ; i < MAX_VM ; i++ ) {
    788 		vm = &vmTable[i];
    789 		if ( !vm->name[0] ) {
    790 			break;
    791 		}
    792 		Com_Printf( "%s : ", vm->name );
    793 		if ( vm->dllHandle ) {
    794 			Com_Printf( "native\n" );
    795 			continue;
    796 		}
    797 		if ( vm->compiled ) {
    798 			Com_Printf( "compiled on load\n" );
    799 		} else {
    800 			Com_Printf( "interpreted\n" );
    801 		}
    802 		Com_Printf( "    code length : %7i\n", vm->codeLength );
    803 		Com_Printf( "    table length: %7i\n", vm->instructionPointersLength );
    804 		Com_Printf( "    data length : %7i\n", vm->dataMask + 1 );
    805 	}
    806 }
    807 
    808 /*
    809 ===============
    810 VM_LogSyscalls
    811 
    812 Insert calls to this while debugging the vm compiler
    813 ===============
    814 */
    815 void VM_LogSyscalls( int *args ) {
    816 	static	int		callnum;
    817 	static	FILE	*f;
    818 
    819 	if ( !f ) {
    820 		f = fopen("syscalls.log", "w" );
    821 	}
    822 	callnum++;
    823 	fprintf(f, "%i: %i (%i) = %i %i %i %i\n", callnum, args - (int *)currentVM->dataBase,
    824 		args[0], args[1], args[2], args[3], args[4] );
    825 }
    826 
    827 
    828 
    829 #ifdef oDLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM
    830 int	VM_CallCompiled( vm_t *vm, int *args ) {
    831   return(0); 
    832 }
    833 
    834 void VM_Compile( vm_t *vm, vmHeader_t *header ) {}
    835 #endif // DLL_ONLY