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 cvar_t *vm_minQvmHunkMegs;
39
40 vm_t *currentVM = NULL;
41 vm_t *lastVM = NULL;
42 int vm_debugLevel;
43
44 // used by Com_Error to get rid of running vm's before longjmp
45 static int forced_unload;
46
47 #define MAX_VM 3
48 vm_t vmTable[MAX_VM];
49
50
51 void VM_VmInfo_f( void );
52 void VM_VmProfile_f( void );
53
54
55
56 #if 0 // 64bit!
57 // converts a VM pointer to a C pointer and
58 // checks to make sure that the range is acceptable
59 void *VM_VM2C( vmptr_t p, int length ) {
60 return (void *)p;
61 }
62 #endif
63
VM_Debug(int level)64 void VM_Debug( int level ) {
65 vm_debugLevel = level;
66 }
67
68 /*
69 ==============
70 VM_Init
71 ==============
72 */
VM_Init(void)73 void VM_Init( void ) {
74 Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
75 Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
76 Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
77
78 vm_minQvmHunkMegs = Cvar_Get( "vm_minQvmHunkMegs", "2", CVAR_ARCHIVE );
79 Cvar_CheckRange( vm_minQvmHunkMegs, 0, 1024, qtrue );
80
81 Cmd_AddCommand("vmprofile", VM_VmProfile_f); // FIXME: doesn't print anything with +set developer 1
82 Cmd_AddCommand ("vminfo", VM_VmInfo_f );
83
84 Com_Memset( vmTable, 0, sizeof( vmTable ) );
85 }
86
87
88 /*
89 ===============
90 VM_ValueToSymbol
91
92 Assumes a program counter value
93 ===============
94 */
VM_ValueToSymbol(vm_t * vm,int value)95 const char *VM_ValueToSymbol( vm_t *vm, int value ) {
96 vmSymbol_t *sym = vm->symbols;
97 static char text[MAX_TOKEN_CHARS];
98
99 if ( !sym ) {
100 return "NO SYMBOLS";
101 }
102
103 // find the symbol
104 while ( sym->next && sym->next->symValue <= value ) {
105 sym = sym->next;
106 }
107
108 if ( value == sym->symValue ) {
109 return sym->symName;
110 }
111
112 Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
113
114 return text;
115 }
116
117 /*
118 ===============
119 VM_ValueToFunctionSymbol
120
121 For profiling, find the symbol behind this value
122 ===============
123 */
VM_ValueToFunctionSymbol(vm_t * vm,int value)124 vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
125 vmSymbol_t *sym = vm->symbols;
126 static vmSymbol_t nullSym;
127
128 if ( !sym ) {
129 return &nullSym;
130 }
131
132 while ( sym->next && sym->next->symValue <= value ) {
133 sym = sym->next;
134 }
135
136 return sym;
137 }
138
139
140 /*
141 ===============
142 VM_SymbolToValue
143 ===============
144 */
VM_SymbolToValue(vm_t * vm,const char * symbol)145 int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
146 vmSymbol_t *sym;
147
148 for ( sym = vm->symbols ; sym ; sym = sym->next ) {
149 if ( !strcmp( symbol, sym->symName ) ) {
150 return sym->symValue;
151 }
152 }
153 return 0;
154 }
155
156
157 /*
158 =====================
159 VM_SymbolForCompiledPointer
160 =====================
161 */
162 #if 0 // 64bit!
163 const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) {
164 int i;
165
166 if ( code < (void *)vm->codeBase ) {
167 return "Before code block";
168 }
169 if ( code >= (void *)(vm->codeBase + vm->codeLength) ) {
170 return "After code block";
171 }
172
173 // find which original instruction it is after
174 for ( i = 0 ; i < vm->codeLength ; i++ ) {
175 if ( (void *)vm->instructionPointers[i] > code ) {
176 break;
177 }
178 }
179 i--;
180
181 // now look up the bytecode instruction pointer
182 return VM_ValueToSymbol( vm, i );
183 }
184 #endif
185
186
187
188 /*
189 ===============
190 ParseHex
191 ===============
192 */
ParseHex(const char * text)193 int ParseHex( const char *text ) {
194 int value = 0;
195 int c;
196
197 while ( ( c = *text++ ) != 0 ) {
198 if ( c >= '0' && c <= '9' ) {
199 value = value * 16 + c - '0';
200 continue;
201 }
202 if ( c >= 'a' && c <= 'f' ) {
203 value = value * 16 + 10 + c - 'a';
204 continue;
205 }
206 if ( c >= 'A' && c <= 'F' ) {
207 value = value * 16 + 10 + c - 'A';
208 continue;
209 }
210 }
211
212 return value;
213 }
214
215 /*
216 ===============
217 VM_LoadSymbols
218 ===============
219 */
VM_LoadSymbols(vm_t * vm)220 void VM_LoadSymbols( vm_t *vm ) {
221 union {
222 char *c;
223 void *v;
224 } mapfile;
225 char *text_p, *token;
226 char name[MAX_QPATH];
227 char symbols[MAX_QPATH];
228 vmSymbol_t **prev, *sym;
229 int count;
230 int value;
231 int chars;
232 int segment;
233 int numInstructions;
234
235 // don't load symbols if not developer
236 if ( !com_developer->integer ) {
237 return;
238 }
239
240 COM_StripExtension(vm->name, name, sizeof(name));
241 Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
242 FS_ReadFile( symbols, &mapfile.v );
243 if ( !mapfile.c ) {
244 Com_Printf( "Couldn't load symbol file: %s\n", symbols );
245 return;
246 }
247
248 numInstructions = vm->instructionCount;
249
250 // parse the symbols
251 text_p = mapfile.c;
252 prev = &vm->symbols;
253 count = 0;
254
255 while ( 1 ) {
256 token = COM_Parse( &text_p );
257 if ( !token[0] ) {
258 break;
259 }
260 segment = ParseHex( token );
261 if ( segment ) {
262 COM_Parse( &text_p );
263 COM_Parse( &text_p );
264 continue; // only load code segment values
265 }
266
267 token = COM_Parse( &text_p );
268 if ( !token[0] ) {
269 Com_Printf( "WARNING: incomplete line at end of file\n" );
270 break;
271 }
272 value = ParseHex( token );
273
274 token = COM_Parse( &text_p );
275 if ( !token[0] ) {
276 Com_Printf( "WARNING: incomplete line at end of file\n" );
277 break;
278 }
279 chars = strlen( token );
280 sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
281 *prev = sym;
282 prev = &sym->next;
283 sym->next = NULL;
284
285 // convert value from an instruction number to a code offset
286 if ( value >= 0 && value < numInstructions ) {
287 value = vm->instructionPointers[value];
288 }
289
290 sym->symValue = value;
291 Q_strncpyz( sym->symName, token, chars + 1 );
292
293 count++;
294 }
295
296 vm->numSymbols = count;
297 Com_Printf( "%i symbols parsed from %s\n", count, symbols );
298 FS_FreeFile( mapfile.v );
299 }
300
301 /*
302 ============
303 VM_DllSyscall
304
305 Dlls will call this directly
306
307 rcg010206 The horror; the horror.
308
309 The syscall mechanism relies on stack manipulation to get its args.
310 This is likely due to C's inability to pass "..." parameters to
311 a function in one clean chunk. On PowerPC Linux, these parameters
312 are not necessarily passed on the stack, so while (&arg[0] == arg)
313 is true, (&arg[1] == 2nd function parameter) is not necessarily
314 accurate, as arg's value might have been stored to the stack or
315 other piece of scratch memory to give it a valid address, but the
316 next parameter might still be sitting in a register.
317
318 Quake's syscall system also assumes that the stack grows downward,
319 and that any needed types can be squeezed, safely, into a signed int.
320
321 This hack below copies all needed values for an argument to a
322 array in memory, so that Quake can get the correct values. This can
323 also be used on systems where the stack grows upwards, as the
324 presumably standard and safe stdargs.h macros are used.
325
326 As for having enough space in a signed int for your datatypes, well,
327 it might be better to wait for DOOM 3 before you start porting. :)
328
329 The original code, while probably still inherently dangerous, seems
330 to work well enough for the platforms it already works on. Rather
331 than add the performance hit for those platforms, the original code
332 is still in use there.
333
334 For speed, we just grab 15 arguments, and don't worry about exactly
335 how many the syscall actually needs; the extra is thrown away.
336
337 ============
338 */
VM_DllSyscall(intptr_t arg,...)339 intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
340 #if !id386 || defined __clang__
341 // rcg010206 - see commentary above
342 intptr_t args[MAX_VMSYSCALL_ARGS];
343 int i;
344 va_list ap;
345
346 args[0] = arg;
347
348 va_start(ap, arg);
349 for (i = 1; i < ARRAY_LEN (args); i++)
350 args[i] = va_arg(ap, intptr_t);
351 va_end(ap);
352
353 return currentVM->systemCall( args );
354 #else // original id code
355 return currentVM->systemCall( &arg );
356 #endif
357 }
358
359
360 /*
361 =================
362 VM_LoadQVM
363
364 Load a .qvm file
365 =================
366 */
VM_LoadQVM(vm_t * vm,qboolean alloc,qboolean unpure)367 vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean unpure)
368 {
369 int dataLength;
370 int i;
371 char filename[MAX_QPATH];
372 union {
373 vmHeader_t *h;
374 void *v;
375 } header;
376
377 // load the image
378 Com_sprintf( filename, sizeof(filename), "vm/%s.mp.qvm", vm->name );
379 Com_Printf( "Loading vm file %s...\n", filename );
380
381 FS_ReadFileDir(filename, vm->searchPath, unpure, &header.v);
382
383 if ( !header.h ) {
384 Com_Printf( "Failed.\n" );
385 VM_Free( vm );
386
387 Com_Printf(S_COLOR_YELLOW "Warning: Couldn't open VM file %s\n", filename);
388
389 return NULL;
390 }
391
392 // show where the qvm was loaded from
393 FS_Which(filename, vm->searchPath);
394
395 if( LittleLong( header.h->vmMagic ) == VM_MAGIC_VER2 ) {
396 Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" );
397
398 // byte swap the header
399 for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
400 ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
401 }
402
403 // validate
404 if ( header.h->jtrgLength < 0
405 || header.h->bssLength < 0
406 || header.h->dataLength < 0
407 || header.h->litLength < 0
408 || header.h->codeLength <= 0 )
409 {
410 VM_Free(vm);
411 FS_FreeFile(header.v);
412
413 Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
414 return NULL;
415 }
416 } else if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) {
417 // byte swap the header
418 // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size
419 for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) {
420 ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
421 }
422
423 // validate
424 if ( header.h->bssLength < 0
425 || header.h->dataLength < 0
426 || header.h->litLength < 0
427 || header.h->codeLength <= 0 )
428 {
429 VM_Free(vm);
430 FS_FreeFile(header.v);
431
432 Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
433 return NULL;
434 }
435 } else {
436 VM_Free( vm );
437 FS_FreeFile(header.v);
438
439 Com_Printf(S_COLOR_YELLOW "Warning: %s does not have a recognisable "
440 "magic number in its header\n", filename);
441 return NULL;
442 }
443
444 // round up to next power of 2 so all data operations can
445 // be mask protected
446 dataLength = header.h->dataLength + header.h->litLength +
447 header.h->bssLength;
448
449 vm->heapAlloc = vm->heapLength = dataLength - PROGRAM_STACK_SIZE;
450
451 dataLength += vm_minQvmHunkMegs->integer * 1024 * 1024;
452
453 for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
454 }
455 dataLength = 1 << i;
456
457 if(alloc)
458 {
459 // allocate zero filled space for initialized and uninitialized data
460 // leave some space beyond data mask so we can secure all mask operations
461 vm->dataAlloc = dataLength + 4;
462 vm->dataBase = Hunk_Alloc(vm->dataAlloc, h_high);
463 vm->dataMask = dataLength - 1;
464 }
465 else
466 {
467 // clear the data, but make sure we're not clearing more than allocated
468 if(vm->dataAlloc != dataLength + 4)
469 {
470 VM_Free(vm);
471 FS_FreeFile(header.v);
472
473 Com_Printf(S_COLOR_YELLOW "Warning: Data region size of %s not matching after "
474 "VM_Restart()\n", filename);
475 return NULL;
476 }
477
478 Com_Memset(vm->dataBase, 0, vm->dataAlloc);
479 }
480
481 // copy the intialized data
482 Com_Memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset,
483 header.h->dataLength + header.h->litLength );
484
485 // byte swap the longs
486 for ( i = 0 ; i < header.h->dataLength ; i += 4 ) {
487 *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
488 }
489
490 if(header.h->vmMagic == VM_MAGIC_VER2)
491 {
492 int previousNumJumpTableTargets = vm->numJumpTableTargets;
493
494 header.h->jtrgLength &= ~0x03;
495
496 vm->numJumpTableTargets = header.h->jtrgLength >> 2;
497 Com_Printf("Loading %d jump table targets\n", vm->numJumpTableTargets);
498
499 if(alloc)
500 {
501 vm->jumpTableTargets = Hunk_Alloc(header.h->jtrgLength, h_high);
502 }
503 else
504 {
505 if(vm->numJumpTableTargets != previousNumJumpTableTargets)
506 {
507 VM_Free(vm);
508 FS_FreeFile(header.v);
509
510 Com_Printf(S_COLOR_YELLOW "Warning: Jump table size of %s not matching after "
511 "VM_Restart()\n", filename);
512 return NULL;
513 }
514
515 Com_Memset(vm->jumpTableTargets, 0, header.h->jtrgLength);
516 }
517
518 Com_Memcpy(vm->jumpTableTargets, (byte *) header.h + header.h->dataOffset +
519 header.h->dataLength + header.h->litLength, header.h->jtrgLength);
520
521 // byte swap the longs
522 for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) {
523 *(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) );
524 }
525 }
526
527 return header.h;
528 }
529
530 /*
531 =================
532 VM_Restart
533
534 Reload the data, but leave everything else in place
535 This allows a server to do a map_restart without changing memory allocation
536
537 We need to make sure that servers can access unpure QVMs (not contained in any pak)
538 even if the client is pure, so take "unpure" as argument.
539 =================
540 */
VM_Restart(vm_t * vm,qboolean unpure)541 vm_t *VM_Restart(vm_t *vm, qboolean unpure)
542 {
543 vmHeader_t *header;
544
545 // DLL's can't be restarted in place
546 if ( vm->dllHandle ) {
547 char name[MAX_QPATH];
548 intptr_t (*systemCall)( intptr_t *parms );
549
550 systemCall = vm->systemCall;
551 Q_strncpyz( name, vm->name, sizeof( name ) );
552
553 VM_Free( vm );
554
555 vm = VM_Create( name, systemCall, VMI_NATIVE );
556 return vm;
557 }
558
559 // load the image
560 Com_Printf("VM_Restart()\n");
561
562 if(!(header = VM_LoadQVM(vm, qfalse, unpure)))
563 {
564 Com_Error(ERR_DROP, "VM_Restart failed");
565 return NULL;
566 }
567
568 // free the original file
569 FS_FreeFile(header);
570
571 return vm;
572 }
573
574 #ifndef DEDICATED
575 extern int cl_connectedToPureServer;
576 #endif
577
578 /*
579 ================
580 VM_Create
581
582 If image ends in .qvm it will be interpreted, otherwise
583 it will attempt to load as a system dll
584 ================
585 */
VM_Create(const char * module,intptr_t (* systemCalls)(intptr_t *),vmInterpret_t interpret)586 vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),
587 vmInterpret_t interpret ) {
588 vm_t *vm;
589 vmHeader_t *header;
590 int i, remaining, retval;
591 char filename[MAX_OSPATH];
592 void *startSearch = NULL;
593 qboolean pureServer, pureVM;
594
595 if ( !module || !module[0] || !systemCalls ) {
596 Com_Error( ERR_FATAL, "VM_Create: bad parms" );
597 }
598
599 remaining = Hunk_MemoryRemaining();
600
601 // see if we already have the VM
602 for ( i = 0 ; i < MAX_VM ; i++ ) {
603 if (!Q_stricmp(vmTable[i].name, module)) {
604 vm = &vmTable[i];
605 return vm;
606 }
607 }
608
609 // find a free vm
610 for ( i = 0 ; i < MAX_VM ; i++ ) {
611 if ( !vmTable[i].name[0] ) {
612 break;
613 }
614 }
615
616 if ( i == MAX_VM ) {
617 Com_Error( ERR_FATAL, "VM_Create: no free vm_t" );
618 }
619
620 vm = &vmTable[i];
621
622 Q_strncpyz(vm->name, module, sizeof(vm->name));
623
624 #ifdef DEDICATED
625 pureServer = qfalse;
626 #else
627 pureServer = (cl_connectedToPureServer) ? qtrue : qfalse;
628 #endif
629
630 pureVM = pureServer;
631
632 do
633 {
634 retval = FS_FindVM(&startSearch, filename, sizeof(filename), module, !pureVM, (interpret != VMI_NATIVE));
635
636 if(retval == VMI_COMPILED)
637 {
638 vm->searchPath = startSearch;
639 if((header = VM_LoadQVM(vm, qtrue, !pureVM)))
640 break;
641
642 // VM_Free overwrites the name on failed load
643 Q_strncpyz(vm->name, module, sizeof(vm->name));
644 }
645 else if(retval == VMI_NATIVE)
646 {
647 Com_Printf("Try loading dll file %s\n", filename);
648
649 vm->dllHandle = Sys_LoadGameDll(filename, &vm->entryPoint, VM_DllSyscall);
650
651 if(vm->dllHandle)
652 {
653 vm->systemCall = systemCalls;
654 return vm;
655 }
656
657 Com_Printf("Failed loading dll, trying next\n");
658 }
659 else if ( pureServer && pureVM )
660 {
661 Com_Printf( "Failed to find pure %s VM, trying unpure\n", module );
662 startSearch = NULL;
663 pureVM = qfalse;
664 retval = 1; // don't exit loop
665 }
666 } while(retval >= 0);
667
668 if(retval < 0)
669 return NULL;
670
671 vm->systemCall = systemCalls;
672
673 // allocate space for the jump targets, which will be filled in by the compile/prep functions
674 vm->instructionCount = header->instructionCount;
675 vm->instructionPointers = Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high);
676
677 // copy or compile the instructions
678 vm->codeLength = header->codeLength;
679
680 vm->compiled = qfalse;
681
682 #ifdef NO_VM_COMPILED
683 if(interpret >= VMI_COMPILED) {
684 Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n");
685 interpret = VMI_BYTECODE;
686 }
687 #else
688 if(interpret != VMI_BYTECODE)
689 {
690 vm->compiled = qtrue;
691 VM_Compile( vm, header );
692 }
693 #endif
694 // VM_Compile may have reset vm->compiled if compilation failed
695 if (!vm->compiled)
696 {
697 VM_PrepareInterpreter( vm, header );
698 }
699
700 // free the original file
701 FS_FreeFile( header );
702
703 // load the map file
704 VM_LoadSymbols( vm );
705
706 // the stack is implicitly at the end of the image
707 vm->programStack = vm->dataMask + 1;
708 vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE;
709
710 // allocate temporary memory down from the bottom of the stack
711 vm->heapAllocTop = vm->stackBottom;
712
713 Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
714
715 return vm;
716 }
717
718 /*
719 ==============
720 VM_Free
721 ==============
722 */
VM_Free(vm_t * vm)723 void VM_Free( vm_t *vm ) {
724
725 if(!vm) {
726 return;
727 }
728
729 if(vm->callLevel) {
730 if(!forced_unload) {
731 Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name );
732 return;
733 } else {
734 Com_Printf( "forcefully unloading %s vm\n", vm->name );
735 }
736 }
737
738 if(vm->destroy)
739 vm->destroy(vm);
740
741 if ( vm->dllHandle ) {
742 Sys_UnloadDll( vm->dllHandle );
743 Com_Memset( vm, 0, sizeof( *vm ) );
744 }
745 #if 0 // now automatically freed by hunk
746 if ( vm->codeBase ) {
747 Z_Free( vm->codeBase );
748 }
749 if ( vm->dataBase ) {
750 Z_Free( vm->dataBase );
751 }
752 if ( vm->instructionPointers ) {
753 Z_Free( vm->instructionPointers );
754 }
755 #endif
756 Com_Memset( vm, 0, sizeof( *vm ) );
757
758 currentVM = NULL;
759 lastVM = NULL;
760 }
761
VM_Clear(void)762 void VM_Clear(void) {
763 int i;
764 for (i=0;i<MAX_VM; i++) {
765 VM_Free(&vmTable[i]);
766 }
767 }
768
VM_Forced_Unload_Start(void)769 void VM_Forced_Unload_Start(void) {
770 forced_unload = 1;
771 }
772
VM_Forced_Unload_Done(void)773 void VM_Forced_Unload_Done(void) {
774 forced_unload = 0;
775 }
776
VM_ArgPtr(intptr_t intValue)777 void *VM_ArgPtr( intptr_t intValue ) {
778 if ( !intValue ) {
779 return NULL;
780 }
781 // currentVM is missing on reconnect
782 if ( currentVM==NULL )
783 return NULL;
784
785 if ( currentVM->entryPoint ) {
786 return (void *)(currentVM->dataBase + intValue);
787 }
788 else {
789 return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
790 }
791 }
792
VM_ExplicitArgPtr(vm_t * vm,intptr_t intValue)793 void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {
794 if ( !intValue ) {
795 return NULL;
796 }
797
798 // currentVM is missing on reconnect here as well?
799 if ( currentVM==NULL )
800 return NULL;
801
802 //
803 if ( vm->entryPoint ) {
804 return (void *)(vm->dataBase + intValue);
805 }
806 else {
807 return (void *)(vm->dataBase + (intValue & vm->dataMask));
808 }
809 }
810
VM_IsNative(vm_t * vm)811 qboolean VM_IsNative( vm_t *vm ) {
812 return ( vm && vm->dllHandle );
813 }
814
815
816 /*
817 ==============
818 VM_Call
819
820
821 Upon a system call, the stack will look like:
822
823 sp+32 parm1
824 sp+28 parm0
825 sp+24 return value
826 sp+20 return address
827 sp+16 local1
828 sp+14 local0
829 sp+12 arg1
830 sp+8 arg0
831 sp+4 return stack
832 sp return address
833
834 An interpreted function will immediately execute
835 an OP_ENTER instruction, which will subtract space for
836 locals from sp
837 ==============
838 */
839
VM_Call(vm_t * vm,intptr_t callnum,...)840 intptr_t QDECL VM_Call( vm_t *vm, intptr_t callnum, ... )
841 {
842 vm_t *oldVM;
843 intptr_t r;
844 int i;
845
846 // DHM - Nerve
847 #ifdef UPDATE_SERVER
848 return 0;
849 #endif
850
851 if(!vm || !vm->name[0])
852 Com_Error(ERR_FATAL, "VM_Call with NULL vm");
853
854 oldVM = currentVM;
855 currentVM = vm;
856 lastVM = vm;
857
858 if ( vm_debugLevel ) {
859 Com_Printf( "VM_Call( %d )\n", (int)callnum );
860 }
861
862 ++vm->callLevel;
863 // if we have a dll loaded, call it directly
864 if ( vm->entryPoint ) {
865 //rcg010207 - see dissertation at top of VM_DllSyscall() in this file.
866 intptr_t args[MAX_VMMAIN_ARGS-1];
867 va_list ap;
868 va_start(ap, callnum);
869 for (i = 0; i < ARRAY_LEN(args); i++) {
870 args[i] = va_arg(ap, intptr_t);
871 }
872 va_end(ap);
873
874 r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3],
875 args[4], args[5], args[6], args[7],
876 args[8], args[9], args[10], args[11]);
877 } else {
878 #if ( id386 || idsparc ) && !defined __clang__ // calling convention doesn't need conversion in some cases
879 #ifndef NO_VM_COMPILED
880 if ( vm->compiled )
881 r = VM_CallCompiled( vm, (int*)&callnum );
882 else
883 #endif
884 r = VM_CallInterpreted( vm, (int*)&callnum );
885 #else
886 struct {
887 int callnum;
888 int args[MAX_VMMAIN_ARGS-1];
889 } a;
890 va_list ap;
891
892 a.callnum = callnum;
893 va_start(ap, callnum);
894 for (i = 0; i < ARRAY_LEN(a.args); i++) {
895 a.args[i] = va_arg(ap, intptr_t);
896 }
897 va_end(ap);
898 #ifndef NO_VM_COMPILED
899 if ( vm->compiled )
900 r = VM_CallCompiled( vm, &a.callnum );
901 else
902 #endif
903 r = VM_CallInterpreted( vm, &a.callnum );
904 #endif
905 }
906 --vm->callLevel;
907
908 if ( oldVM != NULL )
909 currentVM = oldVM;
910 return r;
911 }
912
913 //=================================================================
914
VM_ProfileSort(const void * a,const void * b)915 static int QDECL VM_ProfileSort( const void *a, const void *b ) {
916 vmSymbol_t *sa, *sb;
917
918 sa = *(vmSymbol_t **)a;
919 sb = *(vmSymbol_t **)b;
920
921 if ( sa->profileCount < sb->profileCount ) {
922 return -1;
923 }
924 if ( sa->profileCount > sb->profileCount ) {
925 return 1;
926 }
927 return 0;
928 }
929
930 /*
931 ==============
932 VM_VmProfile_f
933
934 ==============
935 */
VM_VmProfile_f(void)936 void VM_VmProfile_f( void ) {
937 vm_t *vm;
938 vmSymbol_t **sorted, *sym;
939 int i;
940 double total;
941
942 if ( !lastVM ) {
943 return;
944 }
945
946 vm = lastVM;
947
948 if ( !vm->numSymbols ) {
949 Com_Printf("VM symbols not available\n");
950 return;
951 }
952
953 sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
954 sorted[0] = vm->symbols;
955 total = sorted[0]->profileCount;
956 for ( i = 1 ; i < vm->numSymbols ; i++ ) {
957 sorted[i] = sorted[i-1]->next;
958 total += sorted[i]->profileCount;
959 }
960
961 qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
962
963 for ( i = 0 ; i < vm->numSymbols ; i++ ) {
964 int perc;
965
966 sym = sorted[i];
967
968 perc = 100 * (float) sym->profileCount / total;
969 Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
970 sym->profileCount = 0;
971 }
972
973 Com_Printf(" %9.0f total\n", total );
974
975 Z_Free( sorted );
976 }
977
978 /*
979 ==============
980 VM_VmInfo_f
981
982 ==============
983 */
VM_VmInfo_f(void)984 void VM_VmInfo_f( void ) {
985 vm_t *vm;
986 int i;
987
988 Com_Printf( "Registered virtual machines:\n" );
989 for ( i = 0 ; i < MAX_VM ; i++ ) {
990 vm = &vmTable[i];
991 if ( !vm->name[0] ) {
992 break;
993 }
994 Com_Printf( "%s : ", vm->name );
995 if ( vm->dllHandle ) {
996 Com_Printf( "native\n" );
997 continue;
998 }
999 if ( vm->compiled ) {
1000 Com_Printf( "compiled on load\n" );
1001 } else {
1002 Com_Printf( "interpreted\n" );
1003 }
1004 Com_Printf( " code length : %7i\n", vm->codeLength );
1005 Com_Printf( " table length: %7i\n", vm->instructionCount*4 );
1006 Com_Printf( " data length : %7i\n", vm->dataMask + 1 );
1007 Com_Printf( " total memory: %7i\n", vm->stackBottom - vm->heapLength );
1008 Com_Printf( " free memory : %7i\n", vm->heapAllocTop - vm->heapAlloc );
1009 Com_Printf( " used permanent memory: %7i\n", vm->heapAlloc - vm->heapLength );
1010 Com_Printf( " used temporary memory: %7i\n", vm->stackBottom - vm->heapAllocTop );
1011 }
1012 }
1013
1014 /*
1015 ===============
1016 VM_LogSyscalls
1017
1018 Insert calls to this while debugging the vm compiler
1019 ===============
1020 */
VM_LogSyscalls(int * args)1021 void VM_LogSyscalls( int *args ) {
1022 static int callnum;
1023 static FILE *f;
1024
1025 if ( !f ) {
1026 f = fopen("syscalls.log", "w" );
1027 }
1028 callnum++;
1029 fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase),
1030 args[0], args[1], args[2], args[3], args[4] );
1031 }
1032
1033 /*
1034 =================
1035 VM_BlockCopy
1036 Executes a block copy operation within currentVM data space
1037 =================
1038 */
1039
VM_BlockCopy(unsigned int dest,unsigned int src,size_t n)1040 void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n)
1041 {
1042 unsigned int dataMask = currentVM->dataMask;
1043
1044 if ((dest & dataMask) != dest
1045 || (src & dataMask) != src
1046 || ((dest + n) & dataMask) != dest + n
1047 || ((src + n) & dataMask) != src + n)
1048 {
1049 Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!");
1050 }
1051
1052 Com_Memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n);
1053 }
1054
1055 /*
1056 =================
1057 VM_GetTempMemory
1058
1059 Use for passing data for qvms, use VM_ExplicitArgPtr
1060 to get engine writeable address
1061 =================
1062 */
VM_GetTempMemory(vm_t * vm,int size,const void * initData)1063 unsigned VM_GetTempMemory( vm_t *vm, int size, const void *initData ) {
1064 int allocSize;
1065
1066 if ( vm->dllHandle ) {
1067 return 0;
1068 }
1069
1070 // align addresses
1071 allocSize = ( size + 31 ) & ~31;
1072
1073 if ( vm->heapAllocTop - allocSize <= vm->heapAlloc ) {
1074 return 0;
1075 }
1076
1077 vm->heapAllocTop -= allocSize;
1078
1079 if ( initData ) {
1080 Com_Memcpy( vm->dataBase + vm->heapAllocTop, initData, size );
1081 } else {
1082 Com_Memset( vm->dataBase + vm->heapAllocTop, 0, size );
1083 }
1084
1085 return vm->heapAllocTop;
1086 }
1087
1088 /*
1089 =================
1090 VM_FreeTempMemory
1091
1092 Must free temporary memory in reverse order of allocating.
1093 =================
1094 */
VM_FreeTempMemory(vm_t * vm,unsigned qvmPointer,int size,void * outData)1095 void VM_FreeTempMemory( vm_t *vm, unsigned qvmPointer, int size, void *outData ) {
1096 int allocSize;
1097
1098 if ( vm->dllHandle ) {
1099 return;
1100 }
1101
1102 // align addresses
1103 allocSize = ( size + 31 ) & ~31;
1104
1105 if ( vm->heapAllocTop + allocSize > vm->stackBottom ) {
1106 Com_Error( ERR_DROP, "Tried to free too much QVM temporary memory!");
1107 }
1108
1109 if ( outData ) {
1110 Com_Memcpy( outData, vm->dataBase + vm->heapAllocTop, size );
1111 }
1112
1113 Com_Memset( vm->dataBase + vm->heapAllocTop, 0, size );
1114
1115 vm->heapAllocTop += allocSize;
1116 }
1117
1118 #if 0 // ZTM: new unused code for allocating unfreeable memory in dlls and qvms.
1119 /*
1120 =================
1121 QVM_Alloc
1122 =================
1123 */
1124 unsigned int QVM_Alloc( vm_t *vm, int size ) {
1125 unsigned int pointer;
1126 int allocSize;
1127
1128 // align addresses
1129 allocSize = ( size + 31 ) & ~31;
1130
1131 if ( vm->heapAlloc + allocSize > vm->heapAllocTop ) {
1132 Com_Error( ERR_DROP, "QVM_Alloc: %s failed on allocation of %i bytes", vm->name, size );
1133 return 0;
1134 }
1135
1136 pointer = vm->heapAlloc;
1137 vm->heapAlloc += allocSize;
1138
1139 Com_Memset( vm->dataBase + pointer, 0, size );
1140
1141 return pointer;
1142 }
1143
1144 /*
1145 =================
1146 VM_ExplicitAlloc
1147 =================
1148 */
1149 intptr_t VM_ExplicitAlloc( vm_t *vm, int size ) {
1150 intptr_t ptr;
1151
1152 if (size < 1)
1153 Com_Error( ERR_DROP, "VM %s tried to allocate %d bytes of memory", vm->name, size );
1154
1155 if ( vm->dllHandle ) {
1156 ptr = (intptr_t)Hunk_Alloc( size, h_high );
1157 } else {
1158 ptr = QVM_Alloc( vm, size );
1159 }
1160
1161 return ptr;
1162 }
1163
1164 /*
1165 =================
1166 VM_Alloc
1167 =================
1168 */
1169 intptr_t VM_Alloc( int size ) {
1170 return VM_ExplicitAlloc( currentVM, size );
1171 }
1172 #endif
1173
1174