1 /* *WARNING* *FIXME* -or- see some SIGFPEs
2  * This file will execute math operations specified by the user in external
3  * configuration files.  It does almost no error checking.  It seems that
4  * the FPU in the Pentium (and hopefully all other x86 chips) will happily
5  * do things like x/0, tan(pi/2.0), sqrt(-1) and so on.  The result will
6  * be garbage but the program won't die and no SIGFPE will happen.  But
7  * I'm not sure if this is the case on all platforms.
8  *
9  * Note that invalid integer operations like x/0 or x%0 will cause a SIGFPE,
10  * and since I don't know how to handle that, the one integer operation below
11  * (mod) does a check for mod 0 and returns 0.
12  */
13 
14 #include "ExprVirtualMachine.h"
15 
16 #include <math.h>
17 #ifdef UNIX_X
18 #include "trunc.h"
19 #endif
20 #include <stdlib.h>
21 
22 
23 #define __addInst( opcode, data16 )		long op = (opcode) | (data16);	\
24 										mProgram.Append( &op, 4 );
25 
26 
27 #define REG_IN_USE	0x1
28 #define REG_USED	0x2
29 
30 ExprUserFcn ExprVirtualMachine::sZeroFcn = { 0, 0 };
31 
32 #define _fetch( r, val )	switch ( (r) ) {					\
33 								case 0:		val = FR0;	break;	\
34 								case 1:		val = FR1;	break;	\
35 								case 2:		val = FR2;	break;	\
36 								case 3:		val = FR3;	break;	\
37 								case 4:		val = FR4;	break;	\
38 								case 5:		val = FR5;	break;	\
39 								case 6:		val = FR6;	break;	\
40 								case 7:		val = FR7;	break;	\
41 								default:	val = mVirtFR[ r - NUM_PHYS_REGS ];	\
42 							}
43 								/*
44 								case 8:		val = FR8;	break;	\
45 								case 9:		val = FR9;	break;	\
46 								case 10:	val = FR10;	break;	\
47 								case 11:	val = FR11;	break;	\
48 								case 12:	val = FR12;	break;	\
49 								case 13:	val = FR13;	break;	\
50 								case 14:	val = FR14;	break;	\
51 								case 15:	val = FR15;	break;	\
52 								default:	val = 0;	break;	\
53 							}*/
54 
55 #define _store( r, val )	switch ( (r) ) {					\
56 								case 0:		FR0 = val;	break;	\
57 								case 1:		FR1 = val;	break;	\
58 								case 2:		FR2 = val;	break;	\
59 								case 3:		FR3 = val;	break;	\
60 								case 4:		FR4 = val;	break;	\
61 								case 5:		FR5 = val;	break;	\
62 								case 6:		FR6 = val;	break;	\
63 								case 7:		FR7 = val;	break;	\
64 								default:	mVirtFR[ r - NUM_PHYS_REGS ] = val; \
65 							}
66 							/*
67 								case 8:		FR8 = val;	break;	\
68 								case 9:		FR9 = val;	break;	\
69 								case 10:	FR10 = val;	break;	\
70 								case 11:	FR11 = val;	break;	\
71 								case 12:	FR12 = val;	break;	\
72 								case 13:	FR13 = val;	break;	\
73 								case 14:	FR14 = val;	break;	\
74 								case 15:	FR15 = val;	break;	\
75 							}*/
76 
77 
78 
79 
80 #define _exeFn( r )		switch ( subop ) {							\
81 							case cSQRT:	r = sqrt( r );	break;		\
82 							case cABS:	r = fabs( r );	break;		\
83 							case cSIN:	r = sin( r );	break;		\
84 							case cCOS:	r = cos( r );	break;		\
85 							case cSEED: i = *((long*) &r);						\
86 										size = i % 31;							\
87 										srand( ( i << size ) | ( i >> ( 32 - size ) )  ); 	break;				\
88 							case cTAN:	r = tan( r );	break;		\
89 							case cSGN:	r = ( r >= 0 ) ? 1 : -1;	break;		\
90 							case cLOG:	r = log( r );	break;		\
91 							case cEXP:	r = exp( r );	break;		\
92 							case cSQR:	r = r * r;		break;		\
93 							case cATAN:	r = atan( r );	break;		\
94 							case cTRNC:	r = trunc( r );	break;		\
95 							case cWRAP:	r = r -  floor( r );  break; 	\
96 							case cRND:	r = r * ( (float) rand() ) / ( (float) RAND_MAX ); break;	\
97 							case cSQWV:	r = ( r >= -1 && r <= 1 ) ? 1 : 0;	break;	\
98 							case cTRWV: r = fabs( .5 * r ); r = 2 * ( r - floor( r ) );  if ( r > 1 ) r = 2 - r;	break;	\
99 							case cPOS:	if ( r < 0 ) r = 0;			break;		\
100 							case cCLIP:	if ( r < 0 ) r = 0; else if ( r > 1 ) r = 1; break;	\
101 							case cFLOR: r = floor( r );	break;		\
102 						}
103 
104 
105 #define _exeOp( r1, r2 ) 	switch ( subop ) {						\
106 								case '+':	r1 += r2;		break;	\
107 								case '-':	r1 -= r2;		break;	\
108 								case '/':	r1 /= r2;		break;	\
109 								case '*':	r1 *= r2;		break;	\
110 								case '^':	r1 = pow( r1, r2 );					break;	\
111 								case '%':	{ long tt = r2; r1 = (tt != 0) ? (( (long) r1 ) % tt) : 0.0; 	break; }	\
112 							}
113 
ExprVirtualMachine()114 ExprVirtualMachine::ExprVirtualMachine() {
115 	mPCStart	= 0;
116 	mPCEnd		= 0;
117 
118 	sZeroFcn.mNumFcnBins = 1;
119 	sZeroFcn.mFcn[ 0 ] = 0;
120 }
121 
122 
FindGlobalFreeReg()123 int ExprVirtualMachine::FindGlobalFreeReg() {
124 	int reg = 1;
125 
126 	// Look for a global free register
127 	while ( ( mRegColor[ reg ] & REG_USED ) && reg < NUM_REGS )
128 		reg++;
129 
130 
131 	return reg;
132 }
133 
134 
AllocReg()135 int ExprVirtualMachine::AllocReg() {
136 	int reg = 0;
137 
138 	// Look for a free register (ie, find one not in use right now)...
139 	while ( ( mRegColor[ reg ] & REG_IN_USE ) && reg < NUM_REGS )
140 		reg++;
141 
142 	// Color it
143 	if ( reg < NUM_REGS )
144 		mRegColor[ reg ] = REG_IN_USE | REG_USED;
145 
146 	return reg;
147 }
148 
149 
150 
DeallocReg(int inReg)151 void ExprVirtualMachine::DeallocReg( int inReg ) {
152 
153 	// Clear the bit that says the reg is allocated
154 	mRegColor[ inReg ] &= ~REG_IN_USE;
155 }
156 
157 
DoOp(int inReg,int inReg2,char inOpCode)158 void ExprVirtualMachine::DoOp( int inReg, int inReg2, char inOpCode ) {
159 
160 	__addInst( OP_OPER, ( inOpCode << 16 ) | ( inReg2 << 8 ) | inReg )
161 }
162 
163 
164 
165 
166 
Move(int inReg,int inDestReg)167 void ExprVirtualMachine::Move( int inReg, int inDestReg ) {
168 
169 	if ( inDestReg != inReg ) {
170 		__addInst( OP_MOVE, ( inDestReg << 8 ) | inReg )
171 	}
172 }
173 
174 
175 
176 
Loadi(float inVal,int inReg)177 void ExprVirtualMachine::Loadi( float inVal, int inReg ) {
178 
179 	__addInst( OP_LOADIMMED, inReg )
180 	mProgram.Append( &inVal, sizeof( float ) );
181 }
182 
183 
Loadi(float * inVal,int inReg)184 void ExprVirtualMachine::Loadi( float* inVal, int inReg ) {
185 
186 	__addInst( OP_LOAD, inReg )
187 	mProgram.Append( &inVal, 4 );
188 }
189 
190 
191 
UserFcnOp(int inReg,ExprUserFcn ** inFcn)192 void ExprVirtualMachine::UserFcnOp( int inReg, ExprUserFcn** inFcn ) {
193 
194 	if ( inFcn ) {
195 		__addInst( OP_USER_FCN, inReg )
196 		mProgram.Append( &inFcn, 4 );  }
197 	else
198 		Loadi( 0.0, inReg );
199 }
200 
201 
202 
MathOp(int inReg,char inFcnCode)203 void ExprVirtualMachine::MathOp( int inReg, char inFcnCode ) {
204 
205 	if ( inFcnCode ) {
206 		__addInst( OP_MATHOP, ( inFcnCode << 16 ) | inReg )
207 	}
208 }
209 
210 
211 
212 
213 
214 
215 
Clear()216 void ExprVirtualMachine::Clear() {
217 
218 	// Init register coloring
219 	for ( int i = 0; i < NUM_REGS; i++ )
220 		mRegColor[ i ] = 0;
221 
222 	mProgram.Wipe();
223 }
224 
225 
226 
PrepForExecution()227 void ExprVirtualMachine::PrepForExecution() {
228 	mPCStart	= mProgram.getCStr();
229 	mPCEnd		= mPCStart + mProgram.length();
230 }
231 
232 
233 // An inst looks like:
234 // 0-7: 	Inst opcode
235 // 8-15:	Sub opcode
236 // 16-23:	Source Reg
237 // 24-31:	Dest Register number
238 
Execute()239 float ExprVirtualMachine::Execute/*_Inline*/() {
240 	register float	FR0, FR1, FR2, FR3, FR4, FR5, FR6, FR7; // FR8, FR9, FR10, FR11, FR12, FR13, FR14, FR15;
241 	register float	v1, v2;
242 	const char*	PC	= mPCStart;
243 	const char*	end	= mPCEnd;
244 	unsigned long	inst, opcode, subop, size, i, r2, r1;
245 	float			mVirtFR[ NUM_REGS - NUM_PHYS_REGS ];
246 
247 	while ( PC < end ) {
248 		inst = *((long*) PC);
249 		PC += 4;
250 
251 		opcode = inst & 0xFF000000;
252 		r1 = inst & 0xFF;
253 		r2 = ( inst >> 8 ) & 0xFF;
254 
255 		if ( opcode == OP_LOADIMMED ) {
256 			v1 = *((float*) PC);
257 			PC += 4; }
258 		else if ( opcode == OP_LOAD ) {
259 			v1 = **((float**) PC);
260 			PC += 4; }
261 		else {
262 
263 			_fetch( r1, v1 )
264 
265 			switch ( opcode ) {
266 
267 				case OP_OPER:
268 					subop = ( inst >> 16 ) & 0xFF;
269 					_fetch( r2, v2 )
270 					_exeOp( v1, v2 )
271 					break;
272 
273 				case OP_MATHOP:
274 					subop = ( inst >> 16 ) & 0xFF;
275 					_exeFn( v1 )
276 					break;
277 
278 				case OP_MOVE:
279 					r1 = r2;
280 					break;
281 
282 				case OP_USER_FCN:
283 				  {
284 					ExprUserFcn* fcn = **((ExprUserFcn***) PC);
285 					size = fcn -> mNumFcnBins;
286 					i = v1 * size;
287 					if ( i >= 0 && i < size )
288 						v1 = fcn -> mFcn[ i ];
289 					else if ( i < 0 )
290 						v1 = fcn -> mFcn[ 0 ];
291 					else
292 						v1 = fcn -> mFcn[ size - 1 ];
293 					PC += 4;
294 					break;
295 				  }
296 				case OP_WLINEAR:
297 				case OP_WEIGHT:
298 					_fetch( r2, v2 )
299 					float temp = **((float**) PC);
300 					if ( opcode == OP_WEIGHT ) {
301 						v1 = temp * v2 + ( 1.0 - temp ) * v1;
302 						PC += 4; }
303 					else {
304 						v1 = **((float**) PC) * v1 + **((float**) PC+4) * v2;
305 						PC += 8;
306 					}
307 					break;
308 
309 			}
310 		}
311 		_store( r1, v1 )
312 	}
313 
314 	return FR0;
315 }
316 
317 
318 
319 
320 
Chain(ExprVirtualMachine & inVM,float * inC1,float * inC2)321 void ExprVirtualMachine::Chain( ExprVirtualMachine& inVM, float* inC1, float* inC2 ) {
322 	int tempReg = inVM.FindGlobalFreeReg();
323 
324 	// Move the output of this VM to a reg that won't get overwritten by inVM
325 	Move( 0, tempReg );
326 
327 	// Now execute inVM (we know it won't touch tempReg)
328 	mProgram.Append( inVM.mProgram );
329 
330 	// Use the special weight op that combines the two outputs via an embedded addr to a 0 to 1 value
331 	// Note that the output is moved to register 0
332 	if ( inC2 ) {
333 		__addInst( OP_WLINEAR, ( tempReg << 8 ) | 0 )
334 		mProgram.Append( &inC1, 4 );
335 		mProgram.Append( &inC2, 4 ); }
336 	else {
337 		__addInst( OP_WEIGHT, ( tempReg << 8 ) | 0 )
338 		mProgram.Append( &inC1, 4 );
339 	}
340 
341 	// The reg coloring for this VM is the OR of the two's coloring
342 	for ( int i = 0; i < NUM_REGS; i++ )
343 		mRegColor[ i ] |= inVM.mRegColor[ i ];
344 
345 	PrepForExecution();
346 }
347 
348 
349 
Assign(ExprVirtualMachine & inExpr)350 void ExprVirtualMachine::Assign( ExprVirtualMachine& inExpr ) {
351 
352 	mProgram.Assign( inExpr.mProgram );
353 
354 	for ( int i = 0; i < NUM_REGS; i++ )
355 		mRegColor[ i ] = inExpr.mRegColor[ i ];
356 
357 	PrepForExecution();
358 }
359 
360 
361 
362 /*
363 void ExprVirtualMachine::Neg() {
364 
365 	__addInst( OP_NEG, 0 )
366 }
367 */
368 
369 /*
370 void ExprVirtualMachine::Optimize() {
371 	char*		base = mProgram.getCStr();
372 	long*		PC	= (long*) mProgram.getCStr();
373 	long*		end	= (long*) (((char*) PC) + mProgram.length());
374 	long		reg, opcode;
375 	long*		start;
376 
377 	while ( PC < end ) {
378 		opcode = (*PC) & 0xFF000000;
379 		start = PC;
380 		PC++;
381 
382 		// Maintain the PC
383 		if ( opcode == OP_LOADIMMED )
384 			PC += 2;
385 		else if (  opcode == OP_LOAD )
386 			PC++;
387 
388 		// Look for a 'Load into r0, <Math op>, Move from r0' sequence
389 		if ( opcode == OP_LOADIMMED || opcode == OP_LOAD ) {
390 			opcode = (*PC) & 0xFF000000;
391 			if ( opcode == OP_MOVE_FR0 ) {
392 				reg = *PC & 0xFF;										// Extract the final dest register
393 				*start = (*start) | reg;								// Change the load so it loads right into the reg it needs to
394 				mProgram.Remove( 1 + ( ((char*) PC) - base ), 4 ); }	// Delete the move from fr0 inst
395 				// ??
396 			else if ( opcode == OP_MATHOP ) {
397 				opcode = (*(PC + 1)) & 0xFF000000;
398 				if ( opcode == OP_MOVE_FR0 ) {
399 					reg = *(PC + 1) & 0xFF;								// Extract the final dest register
400 					*start = (*start) | reg;							// Change the load so it loads right into the reg it needs to
401 					*PC = (*PC) | reg;									// Change the math op so it operates on the proper reg (see above)
402 					mProgram.Remove( 5 + ( ((char*) PC) - base ), 4 );	// Delete the move from fr0 inst
403 				}
404 			}  // ??
405 		}
406 	}
407 
408 	// Minimzing pushes/pops via stack analysis
409 	StackReduction( 0, mProgram.length() );
410 }
411 
412 
413 
414 
415 long ExprVirtualMachine::StackReduction( long inStartPC, long inEndPC ) {
416 	long regsInUse = 0, opcode, fcnDepth = 0, pushLoc, reg, regsToPush;
417 	long PC = inStartPC, progLen, subRegs, *inst;
418 	char* base = mProgram.getCStr();
419 
420 	while ( PC < inEndPC ) {
421 		reg = *((long*) (PC + base));
422 		opcode = reg & 0xFF000000;		// Extract the opcode
423 		reg &= 0xFF;					// Extract the dest reg
424 
425 		// We're only interested in root level pop/pushes (ie, when fcnDepth == 0)
426 		switch ( opcode ) {
427 
428 			case OP_MASSPUSH:
429 				if ( fcnDepth == 0 ) {
430 					pushLoc = PC;
431 					regsInUse = reg;	// We know what's in use by what the compiler wanted us to push
432 				}
433 				fcnDepth++;
434 				break;
435 
436 			case OP_MASSPOP:
437 				fcnDepth--;
438 				break;
439 
440 			case OP_LOADIMMED:
441 				PC += 4;
442 			case OP_LOAD:
443 				PC += 4;
444 		}
445 
446 		// see what regs are in use--skip over insts not at the root level
447 		if ( fcnDepth == 0 ) {
448 			switch ( opcode ) {
449 
450 				case OP_OPER:
451 					regsInUse |= ( 2 << reg );
452 				case OP_LOADIMMED:
453 				case OP_LOAD:
454 				case OP_MATHOP:
455 				case OP_MOVEUP:
456 				case OP_MOVE_FR0:
457 					regsInUse |= ( 1 << reg );
458 				break;
459 
460 				// Catch the leaving a fcn at the root level
461 				case OP_MASSPOP:
462 
463 					// Get the regs that get sub-used (ie, used between pushLoc and PC)
464 					progLen = mProgram.length();
465 					subRegs = StackReduction( pushLoc + 4, PC );
466 
467 					// StackReduction() may have elminated instructions, so adjust our PC
468 					PC		-= progLen - mProgram.length();
469 					inEndPC -= progLen - mProgram.length();
470 
471 					// Reassign what regs get pushed then popped.  We must push the regs that get used in the sub fcn and we use here
472 					regsToPush = subRegs & regsInUse;
473 					if ( regsToPush ) {
474 						inst = (long*) (base + pushLoc);
475 						*inst = OP_MASSPUSH | regsToPush;		// Reassign the push
476 						inst = (long*) (base + PC);
477 						*inst = OP_MASSPOP | regsToPush; }		// Reassign the pop
478 
479 					// If no regs need to get pushed, delete the pop and push insts
480 					else {
481 						mProgram.Remove( PC + 1, 4 );
482 						mProgram.Remove( pushLoc + 1, 4 );
483 						PC		-= 8;
484 						inEndPC	-= 8;
485 					}
486 					break;
487 			}
488 		}
489 
490 		// Move the PC along, for we just looked at an instruction
491 		PC += 4;
492 	}
493 
494 	return regsInUse;
495 }
496 
497 */
498 
499 
500 
501