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