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