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