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