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