1 /*
2 Code generation for the VM language
3 */
4 /*
5 * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or (at
10 * your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301 USA.
21 */
22
23 #include "compat.h"
24 #include <assert.h>
25
26 #include "dvdauthor.h"
27 #include "da-internal.h"
28 #include "dvdvm.h"
29
30
31 struct vm_statement *dvd_vm_parsed_cmd;
32
33 /* arbitrary implementation limits--should be adequate, given
34 the restrictions on the length of instruction sequences */
35 #define MAXLABELS 200
36 #define MAXGOTOS 200
37
38 struct dvdlabel {
39 char *lname; /* label name */
40 unsigned char *code;
41 /* pointer into buf where label is defined or where goto instruction needs fixup */
42 };
43
44 static struct dvdlabel labels[MAXLABELS];
45 static struct dvdlabel gotos[MAXGOTOS];
46 static int numlabels=0, numgotos=0;
47
negatecompare(int compareop)48 static int negatecompare(int compareop)
49 /* returns the comparison with the opposite result. Assumes the op isn't BC ("&"). */
50 {
51 return compareop ^ 1 ^ ((compareop & 4) >> 1);
52 /* EQ <=> NE, GE <=> LT, GT <=> LE */
53 } /*negatecompare*/
54
swapcompare(int compareop)55 static int swapcompare(int compareop)
56 /* returns the equivalent comparison with the operands swapped. */
57 {
58 if (compareop < 4) /* BC, EQ, NE unchanged */
59 return compareop;
60 else
61 return compareop ^ 3; /* GE <=> LT, GT <=> LE */
62 } /*swapcompare*/
63
compile_usesreg(const struct vm_statement * cs,int target)64 static bool compile_usesreg(const struct vm_statement *cs, int target)
65 /* does cs reference the specified register. */
66 {
67 while (cs)
68 {
69 if (cs->op == VM_VAL)
70 return cs->i1 == target - 256;
71 if (compile_usesreg(cs->param, target))
72 return true;
73 cs = cs->next;
74 } /*while*/
75 return false;
76 } /*compile_usesreg*/
77
nexttarget(int t)78 static int nexttarget(int t)
79 /* returns the next register after t in the range I have reserved. Will fail
80 if it's all used, or if I haven't got a reserved range. */
81 {
82 if (!allowallreg)
83 {
84 if (t < 13)
85 return 13;
86 t++;
87 if (t < 16)
88 return t;
89 } /*if*/
90 fprintf(stderr,"ERR: Expression is too complicated, ran out of registers\n");
91 exit(1);
92 } /*nexttarget*/
93
94 // like nexttarget, but takes a VM_VAL argument
nextval(int t)95 static int nextval(int t)
96 {
97 if (t < -128)
98 return nexttarget(t + 256) - 256; /* it's a register, return next available register */
99 else
100 return nexttarget(-1) - 256; /* not a register, return first available register */
101 } /*nextval*/
102
compileop(unsigned char * buf,int target,int op,int val)103 static unsigned char *compileop(unsigned char *buf, int target, int op, int val)
104 /* compiles a command to set the target GPRM to the result of the specified operation
105 on it and the specified value. */
106 {
107 if (op == VM_VAL && target == val + 256)
108 return buf; /* setting register to its same value => noop */
109 write8(buf, val >= 0 ? 0x70 : 0x60, 0x00, 0x00, target, val >= 0 ? (val >> 8) : 0x00, val, 0x00, 0x00);
110 /* target op= val (op to be filled in below) */
111 switch(op)
112 {
113 case VM_VAL: /* simple assignment */
114 buf[0] |= 1;
115 break;
116 case VM_ADD:
117 buf[0] |= 3;
118 break;
119 case VM_SUB:
120 buf[0] |= 4;
121 break;
122 case VM_MUL:
123 buf[0] |= 5;
124 break;
125 case VM_DIV:
126 buf[0] |= 6;
127 break;
128 case VM_MOD:
129 buf[0] |= 7;
130 break;
131 case VM_RND:
132 buf[0] |= 8;
133 break;
134 case VM_AND:
135 buf[0] |= 9;
136 break;
137 case VM_OR:
138 buf[0] |= 10;
139 break;
140 case VM_XOR:
141 buf[0] |= 11;
142 break;
143 default:
144 fprintf(stderr, "ERR: Unknown op in compileop: %d\n", op);
145 exit(1);
146 } /*switch*/
147 return buf + 8;
148 } /*compileop*/
149
issprmval(const struct vm_statement * v)150 static int issprmval(const struct vm_statement *v)
151 /* is operand v a reference to an SPRM value. */
152 {
153 return v->op == VM_VAL && v->i1 >= -128 && v->i1 < 0;
154 } /*issprmval*/
155
compileexpr(unsigned char * buf,int target,struct vm_statement * cs)156 static unsigned char *compileexpr(unsigned char *buf, int target, struct vm_statement *cs)
157 /* generates code to put the the value of an expression cs into GPRM target.
158 Returns pointer to after generated code. */
159 {
160 struct vm_statement *v, **vp;
161 bool isassoc, canusesprm;
162 if (cs->op == VM_VAL) /* simple value reference */
163 return compileop(buf, target, VM_VAL, cs->i1); /* assign value to target */
164
165 isassoc = /* associative operator--just so happens these are also commutative */
166 cs->op == VM_ADD
167 ||
168 cs->op == VM_MUL
169 ||
170 cs->op == VM_AND
171 ||
172 cs->op == VM_OR
173 ||
174 cs->op == VM_XOR;
175 canusesprm = cs->op == VM_AND || cs->op == VM_OR || cs->op == VM_XOR;
176 /* operations where the source may be an SPRM (also VM_VAL, but that was already dealt with) */
177
178 if (isassoc)
179 {
180 /* if one of the source operands is the destination register, move it to the front */
181 for (vp = &cs->param->next; *vp; vp = &(vp[0]->next))
182 if (vp[0]->op == VM_VAL && vp[0]->i1 == target - 256)
183 {
184 v = *vp;
185 *vp = v->next; /* take out from its place in chain */
186 v->next = cs->param;
187 cs->param = v; /* and put the VM_VAL op on front of chain */
188 break;
189 } /*if*/
190 } /*if*/
191
192 if (compile_usesreg(cs->param->next, target))
193 {
194 /* cannot evaluate cs->param directly into target, because target is used
195 in evaluation */
196 const int t2 = nexttarget(target); /* need another register */
197 buf = compileexpr(buf, t2, cs); /* evaluate expr into t2 */
198 write8(buf, 0x61, 0x00, 0x00, target, 0x00, t2, 0x00, 0x00);
199 /* and then move value of t2 to target */
200 buf += 8;
201 if (t2 == 15)
202 /* just zapped a reserved register whose value might be misinterpreted elsewhere */
203 {
204 write8(buf, 0x71, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00);
205 /* g15 = 0 */
206 buf += 8;
207 } /*if*/
208 return buf;
209 } /*if*/
210
211 if (isassoc && cs->param->op == VM_VAL && cs->param->i1 != target - 256)
212 {
213 // if the first param is a value, then try to move a complex operation farther up or an SPRM access (if SPRM ops are not allowed)
214 for (vp = &cs->param->next; *vp; vp = &(vp[0]->next))
215 if (vp[0]->op != VM_VAL || canusesprm < issprmval(vp[0]))
216 {
217 v = *vp;
218 *vp = v->next; /* take out from its place in chain */
219 v->next = cs->param;
220 cs->param = v; /* and put the SPRM/non-VM_VAL op on front of chain */
221 break;
222 } /*if*/
223 } /*if*/
224
225 // special case -- rnd where the parameter is a reg or value
226 if (cs->op == VM_RND && cs->param->op == VM_VAL)
227 {
228 assert(cs->param->next == 0); /* only one operand */
229 return compileop(buf, target, cs->op, cs->param->i1);
230 } /*if*/
231
232 buf = compileexpr(buf, target, cs->param); /* use target for first/only operand */
233 if (cs->op == VM_RND)
234 {
235 assert(cs->param->next == 0); /* only one operand */
236 return compileop(buf, target, cs->op, target - 256);
237 /* operand from target, result into target */
238 }
239 else /* all other operators take two operands */
240 {
241 for (v = cs->param->next; v; v = v->next) /* process chain of operations */
242 {
243 if (v->op == VM_VAL && canusesprm >= issprmval(v))
244 buf = compileop(buf, target, cs->op, v->i1);
245 /* can simply put function value straight into target */
246 else
247 {
248 const int t2 = nexttarget(target);
249 buf = compileexpr(buf, t2, v); /* put value of v into t2 */
250 buf = compileop(buf, target, cs->op, t2 - 256);
251 /* then value of that and target operated on by op into target */
252 if (t2 == 15)
253 {
254 /* just zapped a reserved register whose value might be misinterpreted elsewhere */
255 write8(buf, 0x71, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00);
256 /* g15 = 0 */
257 buf += 8;
258 } /*if*/
259 } /*if*/
260 } /*for*/
261 } /*if*/
262 return buf;
263 } /*compileexpr*/
264
compilebool(const unsigned char * obuf,unsigned char * buf,struct vm_statement * cs,const unsigned char * iftrue,const unsigned char * iffalse)265 static unsigned char *compilebool
266 (
267 const unsigned char *obuf, /* start of buffer, for calculating instruction numbers */
268 unsigned char *buf, /* where to insert compiled instructions */
269 struct vm_statement *cs, /* expression to compile */
270 const unsigned char *iftrue, /* branch target for true */
271 const unsigned char *iffalse /* branch target for false */
272 )
273 /* compiles an expression that returns a true/false result, branching to iftrue or
274 iffalse depending. Returns pointer to after generated code. */
275 {
276 switch (cs->op)
277 {
278 case VM_EQ:
279 case VM_NE:
280 case VM_GTE:
281 case VM_GT:
282 case VM_LTE:
283 case VM_LT:
284 { /* the two operands are cs->param and cs->param->next */
285 int r1, r2, op;
286 op = cs->op - VM_EQ + 2;
287 /* convert to comparison encoding that can be inserted directly into instruction */
288 if (cs->param->op == VM_VAL)
289 r1 = cs->param->i1; /* value already here */
290 else /* cs->param is something more complex */
291 {
292 r1 = nextval(0); /* temporary place to put it */
293 buf = compileexpr(buf, r1 & 15, cs->param); /* put it there */
294 } /*if*/
295 /* at this point, r1 is literal/register containing first operand */
296 if (cs->param->next->op == VM_VAL && (r1 < 0 || cs->param->next->i1 < 0))
297 /* if one operand is a register and the other is a simple literal or register,
298 I can combine them directly */
299 r2 = cs->param->next->i1;
300 else /* not so simple */
301 {
302 r2 = nextval(r1);
303 buf = compileexpr(buf, r2 & 15, cs->param->next);
304 } /*if*/
305 /* at this point, r2 is literal/register containing second operand */
306 if (r1 >= 0)
307 {
308 /* literal value--swap with r2 */
309 const int t = r1;
310 r1 = r2;
311 r2 = t;
312 op = swapcompare(op);
313 } /*if*/
314 if (iffalse > iftrue)
315 {
316 /* make false branch the one earlier in buffer, in the hope I can fall through to it */
317 /* really only worth doing this if iffalse - buf == 8 */
318 const unsigned char * const t = iftrue;
319 iftrue = iffalse;
320 iffalse = t;
321 op = negatecompare(op);
322 } /*if*/
323 if (r2 >= 0)
324 write8(buf, 0x00, 0x81 | (op << 4), 0x00, r1, r2 >> 8, r2,0x00, 0x00);
325 /* if r1 op r2 then goto true branch */
326 else /* r1 and r2 both registers */
327 write8(buf, 0x00, 0x01 | (op << 4), 0x00, r1, 0x00, r2, 0x00, 0x00);
328 /* if r1 op r2 then goto true branch */
329 buf[7] = (iftrue - obuf) / 8 + 1; /* branch target instr nr */
330 buf += 8;
331 if (iffalse > buf)
332 {
333 /* can't fallthrough for false branch */
334 write8(buf, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
335 /* goto false branch */
336 buf[7] = (iffalse - obuf) / 8 + 1; /* branch target */
337 buf += 8;
338 } /*if*/
339 } /*case*/
340 break;
341
342 case VM_LOR:
343 case VM_LAND:
344 {
345 const int op = cs->op;
346 cs = cs->param;
347 while (cs->next) /* process chain of operands, doing short-cut evaluation */
348 {
349 /* compile this operand, branching to next or to final true/false destination
350 as appropriate */
351 unsigned char * n = buf + 8;
352 /* where to continue chain--assume it'll compile to one instruction to begin with */
353 while (true) /* should loop no more than twice */
354 {
355 unsigned char * const nn = compilebool
356 (
357 /*obuf =*/ obuf,
358 /*buf =*/ buf,
359 /*cs =*/ cs,
360 /*iftrue =*/
361 op == VM_LAND ?
362 n /* continue AND-chain as long as conditions are true */
363 :
364 iftrue, /* no need to continue OR-chain on true */
365 /*iffalse =*/
366 op == VM_LOR ?
367 n /* continue OR-chain as long as conditions are false */
368 :
369 iffalse /* no need to continue AND-chain on false */
370 );
371 if (nn == n)
372 break;
373 /* too many instructions generated to fit */
374 n = nn; /* try again leaving this much room */
375 } /*while*/
376 buf = n;
377 cs = cs->next;
378 } /*while*/
379 buf = compilebool(obuf, buf, cs, iftrue, iffalse);
380 }
381 break;
382
383 case VM_NOT:
384 return compilebool(obuf, buf, cs->param, iffalse, iftrue);
385 /* re-enter myself with the operands swapped */
386
387 default:
388 fprintf(stderr, "ERR: Unknown bool op: %d\n", cs->op);
389 exit(1);
390 } /*switch*/
391 return buf;
392 } /*compilebool*/
393
394 // NOTE: curgroup is passed separately from curpgc, because in FPC, curpgc==NULL, but curgroup!=NULL
compilecs(const unsigned char * obuf,unsigned char * buf,const struct workset * ws,const struct pgcgroup * curgroup,const struct pgc * curpgc,const struct vm_statement * cs,vtypes ismenu)395 static unsigned char *compilecs
396 (
397 const unsigned char *obuf,
398 unsigned char *buf,
399 const struct workset *ws,
400 const struct pgcgroup *curgroup,
401 const struct pgc *curpgc,
402 const struct vm_statement *cs,
403 vtypes ismenu /* needed to decide what kinds of jumps/calls are allowed */
404 )
405 /* compiles a parse tree into naive VM instructions: no optimization of conditionals,
406 and no fixup of gotos; these tasks are left to caller. */
407 {
408 bool lastif = false;
409 while (cs)
410 {
411 if (cs->op != VM_NOP)
412 lastif = false; /* no need for dummy target for last branch, I'll be providing a real one */
413 /* actually check for VM_NOP is unnecessary so long as no construct will
414 generate one */
415 switch (cs->op)
416 {
417 case VM_SET: /* cs->i1 is destination, cs->param is source */
418 switch (cs->i1)
419 {
420 case 0:
421 case 1:
422 case 2:
423 case 3:
424 case 4:
425 case 5:
426 case 6:
427 case 7:
428 case 8:
429 case 9:
430 case 10:
431 case 11:
432 case 12:
433 case 13:
434 case 14:
435 case 15: // set GPRM
436 buf = compileexpr(buf, cs->i1, cs->param);
437 break;
438
439 case 32 + 0:
440 case 32 + 1:
441 case 32 + 2:
442 case 32 + 3:
443 case 32 + 4:
444 case 32 + 5:
445 case 32 + 6:
446 case 32 + 7:
447 case 32 + 8:
448 case 32 + 9:
449 case 32 + 10:
450 case 32 + 11:
451 case 32 + 12:
452 case 32 + 13:
453 case 32 + 14:
454 case 32 + 15: // set GPRM, counter mode
455 if (cs->param->op == VM_VAL)
456 { // we can set counters to system registers
457 const int v = cs->param->i1; /* reg/literal value to set */
458 if (v < 0)
459 write8(buf, 0x43, 0x00, 0x00, v,0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
460 /* SetGPRMMD indirect */
461 else
462 write8(buf, 0x53, 0x00, v / 256, v, 0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
463 /* SetGPRMMD direct */
464 buf += 8;
465 }
466 else /* not so simple */
467 {
468 const int r = nexttarget(0); /* temporary place to put value */
469 buf = compileexpr(buf, r, cs->param); /* put it there */
470 write8(buf, 0x43, 0x00, 0x00, r, 0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
471 /* SetGPRMMD indirect to r */
472 buf += 8;
473 } /*if*/
474 break;
475
476 case 128 + 1: // audio
477 case 128 + 2: // subtitle
478 case 128 + 3: // angle
479 if (cs->param->op == VM_VAL && !(cs->param->i1 >= -128 && cs->param->i1 < 0))
480 {
481 const int v = cs->param->i1;
482 write8(buf, v < 0 ? 0x41 : 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
483 /* SetSTN indirect/direct */
484 buf[(cs->i1 - 128) + 2] = 128 | v; // doesn't matter whether this is a register or a value
485 /* put new value/regnr into audio/subpicture/angle field as appropriate */
486 buf += 8;
487 }
488 else /* complex expression */
489 {
490 const int r = nexttarget(0);
491 buf = compileexpr(buf, r, cs->param);
492 write8(buf, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
493 /* SetSTN indirect */
494 buf[(cs->i1 - 128) + 2] = 128 | r;
495 /* put regnr containing new value into audio/subpicture/angle field as appropriate */
496 buf += 8;
497 } /*if*/
498 break;
499
500 case 128 + 8: // button
501 if (cs->param->op == VM_VAL && !(cs->param->i1 >= -128 && cs->param->i1 < 0))
502 {
503 const int v = cs->param->i1;
504 if (v > 0 && (v & 1023) != 0)
505 fprintf
506 (
507 stderr,
508 "WARN: Button value is %d, but it should be a multiple of 1024\n"
509 "WARN: (button #1=1024, #2=2048, etc)\n",
510 v
511 );
512 if (v < 0)
513 write8(buf, 0x46, 0x00, 0x00, 0x00, 0x00, v, 0x00, 0x00);
514 /* SetHL_BTNN indirect */
515 else
516 write8(buf, 0x56, 0x00, 0x00, 0x00, v / 256, v, 0x00, 0x00);
517 /* SetHL_BTNN direct */
518 buf += 8;
519 }
520 else /* complex expression */
521 {
522 const int r = nexttarget(0);
523 buf = compileexpr(buf, r, cs->param);
524 write8(buf, 0x46, 0x00, 0x00, 0x00, 0x00, r, 0x00, 0x00);
525 /* SetHL_BTNN indirect */
526 buf += 8;
527 } /*if*/
528 break;
529
530 default:
531 fprintf(stderr, "ERR: Cannot set SPRM %d\n", cs->i1 - 128);
532 return 0;
533 } /*switch*/
534 break;
535
536 case VM_IF: /* if-statement */
537 {
538 unsigned char * iftrue = buf + 8; /* initially try putting true branch here */
539 const unsigned char * iffalse = buf + 16; /* initially try putting false branch here */
540 unsigned char * end = buf + 16; /* initially assuming code will end here */
541 while (true) /* should loop no more than twice */
542 {
543 unsigned char *lp, *ib, *e;
544 lp = compilecs(obuf, iftrue, ws, curgroup, curpgc, cs->param->next->param, ismenu);
545 /* the if-true part */
546 if (cs->param->next->next)
547 {
548 /* there's an else-part */
549 e = compilecs(obuf, lp + 8, ws, curgroup, curpgc, cs->param->next->next, ismenu);
550 /* compile the else-part, leaving room for following instr */
551 write8(lp, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, (e - obuf ) / 8 + 1);
552 /* insert a goto at the end of the if-true part to branch over the else-part */
553 lp += 8; /* include in true branch */
554 }
555 else
556 e = lp; /* code ends with true branch */
557 ib = compilebool(obuf, buf, cs->param, iftrue, iffalse);
558 /* put condition test at start */
559 if (!lp)
560 return 0;
561 /* at this point, ib is just after the condition test, lp is just after
562 the true branch, and e is just after the end of all the code */
563 if (ib == iftrue && lp == iffalse)
564 break; /* all fitted nicely */
565 /* didn't leave enough room for pieces next to each other, try again */
566 iftrue = ib; /* enough room for condition code */
567 iffalse = lp; /* enough room for true branch */
568 end = e;
569 } /*while*/
570 buf = end;
571 lastif = true; // make sure reference statement is generated
572 }
573 break;
574
575 case VM_LABEL:
576 {
577 int i;
578 for (i = 0; i < numlabels; i++)
579 if (!strcasecmp(labels[i].lname, cs->s1))
580 {
581 fprintf(stderr, "ERR: Duplicate label '%s'\n", cs->s1);
582 return 0;
583 } /*if; for*/
584 if (numlabels == MAXLABELS)
585 {
586 fprintf(stderr, "ERR: Too many labels\n");
587 return 0;
588 } /*if*/
589 labels[numlabels].lname = cs->s1;
590 labels[numlabels].code = buf; /* where label points to */
591 numlabels++;
592 lastif = true; // make sure reference statement is generated
593 }
594 break;
595
596 case VM_GOTO:
597 if (numgotos == MAXGOTOS)
598 {
599 fprintf(stderr, "ERR: Too many gotos\n");
600 return 0;
601 } /*if*/
602 gotos[numgotos].lname = cs->s1;
603 gotos[numgotos].code = buf; /* point to instruction so it can be fixed up later */
604 numgotos++;
605 write8(buf, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
606 buf += 8;
607 break;
608
609 case VM_BREAK:
610 write8(buf, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
611 buf += 8;
612 break;
613
614 case VM_EXIT:
615 write8(buf, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
616 buf += 8;
617 break;
618
619 case VM_LINK:
620 write8(buf, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, cs->i1);
621 buf += 8;
622 break;
623
624 case VM_JUMP:
625 {
626 int i1 = cs->i1; /* if nonzero, 1 for VMGM, or titleset nr + 1 */
627 int i2 = cs->i2; /* menu number or menu entry ID + 120 or title number + 128 */
628 /* cs->i3 is PGC number if nonzero and less than 65536; or chapter number + 65536;
629 or program number + 131072; or cell number + 196608 */
630
631 /* check for various disallowed combinations */
632 if (i1 == 1 && ismenu == VTYPE_VMGM)
633 {
634 // VMGM VMGM NOPGC NOCH
635 // VMGM VMGM NOPGC CHXX
636 // VMGM VMGM MPGC NOCH
637 // VMGM VMGM MPGC CHXX
638 // VMGM VMGM MEPGC NOCH
639 // VMGM VMGM MEPGC CHXX
640 // VMGM VMGM TPGC NOCH
641 // VMGM VMGM TPGC CHXX
642 i1 = 0; /* no need to explicitly specify VMGM */
643 } /*if*/
644 if
645 (
646 (
647 i2 > 0 && i2 < 128 /* jump to non-entry menu */
648 ||
649 i2 == 0 && i1 == 1 /* jump to VMGM */
650 )
651 &&
652 ismenu == VTYPE_VTS
653 )
654 {
655 // VTS NONE MPGC NOCH
656 // VTS VMGM MPGC NOCH
657 // VTS TS MPGC NOCH
658 // VTS NONE MPGC CHXX
659 // VTS VMGM MPGC CHXX
660 // VTS TS MPGC CHXX
661 // VTS NONE MEPGC NOCH
662 // VTS VMGM MEPGC NOCH
663 // VTS TS MEPGC NOCH
664 // VTS NONE MEPGC CHXX
665 // VTS VMGM MEPGC CHXX
666 // VTS TS MEPGC CHXX
667 // VTS VMGM NOPGC NOCH
668 // VTS VMGM NOPGC CHXX
669
670 fprintf(stderr, "ERR: Cannot jump to a menu from a title, use 'call' instead\n");
671 return 0;
672 } /*if*/
673 if
674 (
675 i2 > 0
676 &&
677 i2 < 128 /* jump to non-entry menu */
678 &&
679 (cs->i3 & 65535) /* PGC/chapter/cell/program specified */
680 &&
681 ismenu != VTYPE_VTS
682 )
683 {
684 // VMGM NONE MPGC CHXX
685 // VMGM TS MPGC CHXX
686 // VMGM NONE MEPGC CHXX
687 // VMGM TS MEPGC CHXX
688 // VTSM NONE MPGC CHXX
689 // VTSM VMGM MPGC CHXX
690 // VTSM TS MPGC CHXX
691 // VTSM NONE MEPGC CHXX
692 // VTSM VMGM MEPGC CHXX
693 // VTSM TS MEPGC CHXX
694 fprintf(stderr, "ERR: Cannot specify chapter when jumping to another menu\n");
695 return 0;
696 } /*if*/
697 if (i1 /*VMGM/titleset*/ && !i2 /*no PGC*/)
698 {
699 // VTSM VMGM NOPGC CHXX
700 // VTS TS NOPGC CHXX
701 // VTSM TS NOPGC CHXX
702 // VMGM TS NOPGC CHXX
703 // VTS TS NOPGC NOCH
704 // VTSM TS NOPGC NOCH
705 // VMGM TS NOPGC NOCH
706 fprintf(stderr, "ERR: Cannot omit menu/title if specifying vmgm/titleset\n");
707 return 0;
708 } /*if*/
709 if
710 (
711 !i1 /*same VMGM/titleset*/
712 &&
713 !i2 /*same PGC*/
714 &&
715 !(cs->i3 & 65535) /*no PGC/chapter/cell/program*/
716 )
717 {
718 // VTS NONE NOPGC NOCH
719 // VTSM NONE NOPGC NOCH
720 // VMGM NONE NOPGC NOCH
721 fprintf(stderr, "ERR: Nop jump statement\n");
722 return 0;
723 } /*if*/
724 if
725 (
726 i2 == 121 /*jump to FPC*/
727 &&
728 (
729 i1 >= 2 /*titleset*/
730 ||
731 (i1 == 0 /*current VMGM/titleset*/ && ismenu != VTYPE_VMGM)
732 )
733 )
734 {
735 fprintf(stderr, "ERR: VMGM must be specified with FPC\n");
736 return 0;
737 } /*if*/
738
739 // *** ACTUAL COMPILING
740 if
741 (
742 i1 >= 2 /*titleset*/
743 &&
744 i2 >= 120
745 &&
746 i2 < 128 /*entry PGC*/
747 )
748 {
749 // VTSM TS MEPGC NOCH
750 // VMGM TS MEPGC NOCH
751 if (i2 == 120) /* "default" entry means "root" */
752 i2 = 123;
753 write8(buf, 0x30, 0x06, 0x00, 0x01, i1 - 1, 0x80 + (i2 - 120), 0x00, 0x00); buf += 8; // JumpSS VTSM vts 1 menu
754 }
755 else if
756 (
757 i1 >= 2 /*jump to titleset*/
758 ||
759 i1 == 1 /*jump to VMGM*/ && i2 >= 128 /*title*/
760 ||
761 ismenu == VTYPE_VMGM && i2 >= 128 /*title*/ && (cs->i3 & 65535) != 0 /*chapter/program/cell*/
762 )
763 {
764 // VMGM TS TPGC CHXX
765 // VTSM TS MPGC NOCH
766 // VMGM TS MPGC NOCH
767 // VTS TS TPGC NOCH
768 // VTSM TS TPGC NOCH
769 // VMGM TS TPGC NOCH
770 // VTS TS TPGC CHXX
771 // VTSM TS TPGC CHXX
772 // VTS VMGM TPGC NOCH
773 // VTSM VMGM TPGC NOCH
774 // VTS VMGM TPGC CHXX
775 // VTSM VMGM TPGC CHXX
776 // VMGM NONE TPGC CHXX
777 if (jumppad)
778 {
779 if (!i1)
780 i1 = 1; /* make VMGM explicit */
781 write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, i1, 0x00, 0x00);
782 /* g15 = i2 << 8 | i1 */
783 buf += 8;
784 write8(buf, 0x71, 0x00, 0x00, 0x0E, 0x00, cs->i3, 0x00, 0x00);
785 /* g14 = cs->i3 */
786 buf += 8;
787 write8(buf, 0x30, ismenu != VTYPE_VTS ? 0x06 : 0x08, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00);
788 /* JumpSS/CallSS VMGM menu entry title */
789 buf += 8;
790 }
791 else
792 {
793 /* JumpTT allows directly jumping from VMGM to any titleset, but that
794 requires global title numbers, not titleset-local title numbers,
795 and I only work these out later when putting together the final VMGM,
796 which is why I can't handle it here without the jumppad */
797 fprintf(stderr, "ERR: That form of jumping is not allowed\n");
798 return 0;
799 } /*if*/
800 }
801 else if (i1 == 1 /*jump to VMGM*/ || i2 == 121 /*jump to FPC*/)
802 {
803 // VTSM VMGM NOPGC NOCH
804 // VTSM VMGM MPGC NOCH
805 // VTSM VMGM MEPGC NOCH
806 // cannot error check jumps to the vmgm menu
807 if (!i2 || i2 == 120)
808 i2 = 122; /* must be title menu */
809 if (i2 < 120)
810 write8(buf, 0x30, 0x06, 0x00, i2, 0x00, 0xC0, 0x00, 0x00); // JumpSS VMGM pgcn
811 else
812 write8(buf, 0x30, 0x06, 0x00, 0x00, 0x00, i2 == 121 ? 0 : (0x40 + i2 - 120), 0x00, 0x00); // JumpSS FP or JumpSS VMGM menu
813 buf += 8;
814 }
815 else if (!i1 && !i2 && (cs->i3 & 65535))
816 {
817 int numc;
818 const char *des;
819
820 // VTS NONE NOPGC CHXX
821 // VTSM NONE NOPGC CHXX
822 // VMGM NONE NOPGC CHXX
823 if (curpgc == 0)
824 {
825 fprintf(stderr, "ERR: Cannot jump to a chapter from FPC\n");
826 return 0;
827 } /*if*/
828 if (cs->i3 >> 16 == 1 && ismenu != VTYPE_VTS)
829 {
830 fprintf(stderr, "ERR: Menus do not have chapters\n");
831 return 0;
832 } /*if*/
833 switch (cs->i3 >> 16)
834 {
835 case 0:
836 numc = curgroup->numpgcs;
837 des = "pgc";
838 break;
839 case 1:
840 numc = curpgc->numchapters;
841 des = "chapter";
842 break;
843 case 2:
844 numc = curpgc->numprograms;
845 des = "program";
846 break;
847 case 3:
848 numc = curpgc->numcells;
849 des = "cell";
850 break;
851 default: /* shouldn't occur! */
852 numc = 0;
853 des = "<err>";
854 break;
855 } /*switch*/
856 if ((cs->i3 & 65535) > numc)
857 {
858 fprintf(stderr, "ERR: Cannot jump to %s %d, only %d exist\n", des, cs->i3 & 65535, numc);
859 return 0;
860 } /*if*/
861 write8(buf, 0x20, 0x04 + (cs->i3 >> 16), 0x00, 0x00, 0x00, 0x00, 0x00, cs->i3); // LinkPGCN pgcn, LinkPTTN pttn, LinkPGCN pgn, or LinkCN cn
862 buf += 8;
863 }
864 else if (i2 < 128) /* menu */
865 {
866 // VTSM NONE MPGC NOCH
867 // VMGM NONE MPGC NOCH
868 // VTSM NONE MEPGC NOCH
869 // VMGM NONE MEPGC NOCH
870 if (!curgroup)
871 {
872 fprintf(stderr,"ERR: Cannot jump to menu; none exist\n");
873 return 0;
874 }
875 else if (i2 >= 120 && i2 < 128) /* menu entry */
876 {
877 int i;
878 for (i = 0; i < curgroup->numpgcs; i++)
879 if (curgroup->pgcs[i]->entries & (1 << (i2 - 120)))
880 {
881 i2 = i + 1;
882 break;
883 } /*if; for*/
884 if (i2 >= 120)
885 {
886 fprintf(stderr, "ERR: Cannot find PGC with entry %s\n", entries[i2 - 120]);
887 return 0;
888 } /*if*/
889 }
890 else /* non-entry menu */
891 {
892 if (i2 > curgroup->numpgcs)
893 {
894 fprintf(stderr, "ERR: Cannot jump to menu PGC #%d, only %d exist\n", i2, curgroup->numpgcs);
895 return 0;
896 } /*if*/
897 } /*if*/
898 if (ismenu == VTYPE_VMGM)
899 {
900 // In case we are jumping from FP to VMGM, we need to use a JumpSS
901 // instruction
902 write8(buf, 0x30, 0x06, 0x00, i2 & 127, 0x00, 0xc0, 0x00, 0x00); // JumpSS VMGM pgcn
903 }
904 else
905 write8(buf, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, i2 & 127); // LinkPGCN pgcn
906 buf += 8;
907 }
908 else
909 {
910 // VMGM NONE TPGC NOCH
911 // VTS NONE TPGC NOCH
912 // VTSM NONE TPGC NOCH
913 // VTS NONE TPGC CHXX
914 // VTSM NONE TPGC CHXX
915 if (ismenu < VTYPE_VMGM) /* VTS or VTSM */
916 {
917 if (i2 - 128 > ws->titles->numpgcs) /* title nr */
918 {
919 fprintf
920 (
921 stderr,
922 "ERR: Cannot jump to title #%d, only %d exist\n",
923 i2 - 128,
924 ws->titles->numpgcs
925 );
926 return 0;
927 } /*if*/
928 if ((cs->i3 & 65535) != 0 && (cs->i3 & 65535) > ws->titles->pgcs[i2 - 128 - 1]->numchapters)
929 {
930 fprintf
931 (
932 stderr,
933 "ERR: Cannot jump to chapter %d of title %d, only %d exist\n",
934 cs->i3 & 65535,
935 i2 - 128,
936 ws->titles->pgcs[i2 - 128 - 1]->numchapters
937 );
938 return 0;
939 } /*if*/
940 } /*if*/
941 write8
942 (
943 buf,
944 0x30,
945 ismenu == VTYPE_VMGM ?
946 0x02 /*JumpTT*/
947 : (cs->i3 & 65535) != 0 ?
948 0x05 /*JumpVTS_PTT*/
949 :
950 0x03 /*JumpVTS_TT*/,
951 0x00,
952 cs->i3, /* chapter if present */
953 0x00,
954 i2 - 128, /* title nr */
955 0x00,
956 0x00
957 );
958 buf += 8;
959 } /*if*/
960 }
961 break;
962
963 case VM_CALL:
964 {
965 /* cs->i1 if nonzero is 1 for VMGM, or titleset nr + 1 */
966 int i2 = cs->i2; /* menu number or menu entry ID + 120 or title number + 128 */
967 /* cs->i3 is chapter number if specified, else zero */
968 int i4 = cs->i4; /* resume cell if specified, else zero */
969
970 // CALL's from <post> MUST have a resume cell
971 if (!i4)
972 i4 = 1; /* resume from start if not specified */
973 if (ismenu != VTYPE_VTS)
974 {
975 // VTSM NONE NOPGC NOCH
976 // VMGM NONE NOPGC NOCH
977 // VTSM VMGM NOPGC NOCH
978 // VMGM VMGM NOPGC NOCH
979 // VTSM TS NOPGC NOCH
980 // VMGM TS NOPGC NOCH
981 // VTSM NONE NOPGC CHXX
982 // VMGM NONE NOPGC CHXX
983 // VTSM VMGM NOPGC CHXX
984 // VMGM VMGM NOPGC CHXX
985 // VTSM TS NOPGC CHXX
986 // VMGM TS NOPGC CHXX
987 // VTSM NONE MPGC NOCH
988 // VMGM NONE MPGC NOCH
989 // VTSM VMGM MPGC NOCH
990 // VMGM VMGM MPGC NOCH
991 // VTSM TS MPGC NOCH
992 // VMGM TS MPGC NOCH
993 // VTSM NONE MPGC CHXX
994 // VMGM NONE MPGC CHXX
995 // VTSM VMGM MPGC CHXX
996 // VMGM VMGM MPGC CHXX
997 // VTSM TS MPGC CHXX
998 // VMGM TS MPGC CHXX
999 // VTSM NONE MEPGC NOCH
1000 // VMGM NONE MEPGC NOCH
1001 // VTSM VMGM MEPGC NOCH
1002 // VMGM VMGM MEPGC NOCH
1003 // VTSM TS MEPGC NOCH
1004 // VMGM TS MEPGC NOCH
1005 // VTSM NONE MEPGC CHXX
1006 // VMGM NONE MEPGC CHXX
1007 // VTSM VMGM MEPGC CHXX
1008 // VMGM VMGM MEPGC CHXX
1009 // VTSM TS MEPGC CHXX
1010 // VMGM TS MEPGC CHXX
1011 // VTSM NONE TPGC NOCH
1012 // VMGM NONE TPGC NOCH
1013 // VTSM VMGM TPGC NOCH
1014 // VMGM VMGM TPGC NOCH
1015 // VTSM TS TPGC NOCH
1016 // VMGM TS TPGC NOCH
1017 // VTSM NONE TPGC CHXX
1018 // VMGM NONE TPGC CHXX
1019 // VTSM VMGM TPGC CHXX
1020 // VMGM VMGM TPGC CHXX
1021 // VTSM TS TPGC CHXX
1022 // VMGM TS TPGC CHXX
1023 fprintf(stderr, "ERR: Cannot 'call' a menu from another menu, use 'jump' instead\n");
1024 return 0;
1025 } /*if*/
1026 if (i2 == 0 || i2 >= 128) /* title nr or no menu/title */
1027 {
1028 // VTS NONE NOPGC NOCH
1029 // VTS VMGM NOPGC NOCH
1030 // VTS TS NOPGC NOCH
1031 // VTS NONE NOPGC CHXX
1032 // VTS VMGM NOPGC CHXX
1033 // VTS TS NOPGC CHXX
1034 // VTS NONE TPGC NOCH
1035 // VTS VMGM TPGC NOCH
1036 // VTS TS TPGC NOCH
1037 // VTS NONE TPGC CHXX
1038 // VTS VMGM TPGC CHXX
1039 // VTS TS TPGC CHXX
1040
1041 fprintf(stderr, "ERR: Cannot 'call' another title, use 'jump' instead\n");
1042 return 0;
1043 } /*if*/
1044 if (cs->i3 != 0) /* chapter/cell/program */
1045 {
1046 // VTS NONE MPGC CHXX
1047 // VTS VMGM MPGC CHXX
1048 // VTS TS MPGC CHXX
1049 // VTS NONE MEPGC CHXX
1050 // VTS VMGM MEPGC CHXX
1051 // VTS TS MEPGC CHXX
1052 fprintf(stderr, "ERR: Cannot 'call' a chapter within a menu\n");
1053 return 0;
1054 } /*if*/
1055 if
1056 (
1057 i2 == 121 /*FPC*/
1058 &&
1059 (
1060 cs->i1 >= 2 /*titleset*/
1061 ||
1062 cs->i1 == 0 /*no VMGM/titleset*/ && ismenu != VTYPE_VMGM
1063 )
1064 )
1065 {
1066 fprintf(stderr, "ERR: VMGM must be specified with FPC\n");
1067 return 0;
1068 } /*if*/
1069
1070 if (cs->i1 >= 2) /*titleset*/
1071 {
1072 // VTS TS MPGC NOCH
1073 // VTS TS MEPGC NOCH
1074 if (jumppad)
1075 {
1076 write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, cs->i1, 0x00, 0x00); buf += 8;
1077 write8(buf, 0x71, 0x00, 0x00, 0x0E, 0x00, cs->i3, 0x00, 0x00); buf += 8;
1078 write8(buf, 0x30, 0x08, 0x00, 0x00, i4, 0x42, 0x00, 0x00); buf += 8;
1079 }
1080 else
1081 {
1082 fprintf(stderr, "ERR: Cannot call to a menu in another titleset\n");
1083 return 0;
1084 } /*if*/
1085 }
1086 else if (cs->i1 == 0 && i2 < 120) /* non-entry menu in current VMGM/titleset */
1087 {
1088 // VTS NONE MPGC NOCH
1089 if (jumppad)
1090 {
1091 write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, 0x00, 0x00, 0x00); buf += 8;
1092 write8(buf, 0x30, 0x08, 0x00, 0x00, i4, 0x87, 0x00, 0x00); buf += 8;
1093 }
1094 else
1095 {
1096 fprintf(stderr, "ERR: Cannot call to a specific menu PGC, only an entry\n");
1097 return 0;
1098 } /*if*/
1099 }
1100 else if (cs->i1 == 1) /* jump to VMGM */
1101 {
1102 // VTS VMGM MPGC NOCH
1103 // VTS VMGM MEPGC NOCH
1104 // we cannot provide error checking when jumping to a VMGM
1105 if (i2 == 120) /* "default" entry means "title" */
1106 i2 = 122;
1107 if (i2 < 120)
1108 write8(buf, 0x30, 0x08, 0x00, i2, i4, 0xC0, 0x00, 0x00);
1109 else
1110 write8(buf, 0x30, 0x08, 0x00, 0x00, i4, i2 == 121 ? 0 : (0x40 + i2 - 120), 0x00, 0x00);
1111 buf += 8;
1112 }
1113 else
1114 {
1115 int i, j;
1116 // VTS NONE MEPGC NOCH
1117 if (i2 == 120)
1118 i2 = 127; /* "default" means chapter menu? */
1119 for (j = 0; j < ws->menus->numgroups; j++)
1120 {
1121 const struct pgcgroup * const pg = ws->menus->groups[j].pg;
1122 for (i = 0; i < pg->numpgcs; i++)
1123 if (pg->pgcs[i]->entries & (1 << (i2 - 120)))
1124 goto foundpgc;
1125 } /*for*/
1126 fprintf(stderr, "ERR: Cannot find PGC with entry %s\n", entries[i2 - 120]);
1127 return 0;
1128 foundpgc:
1129 write8(buf, 0x30, 0x08, 0x00, 0x00, i4 /*rsm cell*/, 0x80 + i2 - 120 /*VTSM menu*/, 0x00, 0x00);
1130 /* CallSS VTSM menu, rsm_cell */
1131 buf += 8;
1132 } /*if*/
1133 }
1134 break;
1135
1136 case VM_NOP:
1137 /* nothing to do */
1138 break;
1139
1140 default:
1141 fprintf(stderr,"ERR: Unsupported VM opcode %d\n",cs->op);
1142 return 0;
1143 } /*switch*/
1144 cs = cs->next;
1145 } /*while*/
1146 if (lastif)
1147 {
1148 /* need target for last branch */
1149 write8(buf, 0, 0, 0, 0, 0, 0, 0, 0); /* NOP */
1150 buf += 8;
1151 } /*if*/
1152 return buf;
1153 } /*compilecs*/
1154
extractif(const unsigned char * b)1155 static unsigned int extractif(const unsigned char *b)
1156 /* extracts the common bits from a conditional instruction into a format
1157 that can be reapplied to another instruction. */
1158 {
1159 switch (b[0] >> 4)
1160 {
1161 case 0:
1162 case 1:
1163 case 2:
1164 return
1165 (b[1] >> 4) << 24 /* the comparison op and direct-operand-2 flag */
1166 |
1167 b[3] << 16 /* operand 1 for the comparison */
1168 |
1169 b[4] << 8 /* operand 2, high byte */
1170 |
1171 b[5]; /* operand 2, low byte */
1172
1173 default:
1174 fprintf(stderr, "ERR: Unhandled extractif scenario (%x), file bug\n", b[0]);
1175 exit(1);
1176 } /*switch*/
1177 } /*extractif*/
1178
negateif(unsigned int ifs)1179 static unsigned int negateif(unsigned int ifs)
1180 /* negates the comparison op part of a value returned from extractif. */
1181 {
1182 return
1183 ifs & 0x8ffffff /* remove comparison op, leave direct flag and operands */
1184 |
1185 negatecompare((ifs >> 24) & 7) << 24; /* replace with opposite comparison */
1186 } /*negateif*/
1187
applyif(unsigned char * b,unsigned int ifs)1188 static void applyif(unsigned char *b,unsigned int ifs)
1189 /* inserts the bits extracted by extractif into a new instruction. */
1190 {
1191 switch (b[0] >> 4)
1192 {
1193 case 0:
1194 case 1:
1195 case 2:
1196 b[5] = ifs;
1197 b[4] = ifs >> 8;
1198 b[3] = ifs >> 16;
1199 b[1] |= (ifs >> 24) << 4;
1200 break;
1201
1202 case 3:
1203 case 4:
1204 case 5:
1205 b[7] = ifs; /* assume ifs >> 8 & 255 is zero! */
1206 b[6] = ifs >> 16;
1207 b[1] |= (ifs >> 24) << 4;
1208 break;
1209
1210 case 6:
1211 case 7:
1212 b[7] = ifs;
1213 b[6] = ifs >> 8;
1214 b[2] = ifs >> 16;
1215 b[1] = (ifs >> 24) << 4;
1216 break;
1217
1218 default:
1219 fprintf(stderr,"ERR: Unhandled applyif scenario (%x), file bug\n", b[0]);
1220 exit(1);
1221 } /*switch*/
1222 } /*applyif*/
1223
ifcombinable(unsigned char b0,unsigned char b1,unsigned char b8)1224 static bool ifcombinable(unsigned char b0 /* actually caller always passes 0 */, unsigned char b1, unsigned char b8)
1225 /* can the instruction whose first two bytes are b0 and b1 have its condition
1226 combined with the one whose first byte is b8. */
1227 {
1228 int iftype = -1;
1229 switch (b0 >> 4)
1230 {
1231 case 0:
1232 case 1:
1233 case 2:
1234 case 6:
1235 case 7:
1236 iftype = b1 >> 7; /* 1 if 2nd operand is immediate, 0 if it's a register */
1237 break;
1238 case 3:
1239 case 4:
1240 case 5:
1241 iftype = 0; /* 2nd operand always register */
1242 break;
1243 default:
1244 return false;
1245 } /*switch*/
1246 switch (b8 >> 4)
1247 {
1248 case 0:
1249 case 1:
1250 case 2:
1251 case 6:
1252 case 7:
1253 return true; /* can take both immediate and register 2nd operands */
1254 case 3:
1255 case 4:
1256 case 5:
1257 return iftype == 0; /* can only take register 2nd operand */
1258 default:
1259 return false;
1260 } /*switch*/
1261 } /*ifcombinable*/
1262
isreferenced(const unsigned char * buf,const unsigned char * end,int linenum)1263 static bool isreferenced(const unsigned char *buf, const unsigned char *end, int linenum)
1264 /* checks if there are any branches with destination linenum. */
1265 {
1266 const unsigned char *b;
1267 bool referenced;
1268 for (b = buf;;)
1269 {
1270 if (b == end)
1271 {
1272 referenced = false;
1273 break;
1274 } /*if*/
1275 if (b[0] == 0 && (b[1] & 15) == 1 && b[7] == linenum)
1276 /* check for goto -- fixme: should also check for SetTmpPML if I ever implement that */
1277 {
1278 referenced = true;
1279 break;
1280 } /*if*/
1281 b += 8;
1282 } /*for*/
1283 return
1284 referenced;
1285 } /*isreferenced*/
1286
deleteinstruction(const unsigned char * obuf,unsigned char * buf,unsigned char ** end,unsigned char * b)1287 static void deleteinstruction
1288 (
1289 const unsigned char *obuf, /* start of instruction buffer */
1290 unsigned char *buf, /* start of area where branch targets need adjusting */
1291 unsigned char **end, /* pointer to next free part of buffer, to be updated */
1292 unsigned char *b /* instruction to be deleted from buffer */
1293 )
1294 /* deletes an instruction from the buffer, and moves up the following ones,
1295 adjusting branches over the deleted instruction as appropriate. */
1296 {
1297 unsigned char *b2;
1298 const int linenum = (b - obuf) / 8 + 1;
1299 for (b2 = buf; b2 < *end; b2 += 8) /* adjust branches to following instructions */
1300 if (b2[0] == 0 && (b2[1] & 15) == 1 && b2[7] > linenum)
1301 b2[7]--;
1302 memmove(b, b + 8, *end - (b + 8));
1303 *end -= 8;
1304 memset(*end, 0, 8); // clean up tracks (so pgc structure is not polluted)
1305 } /*deleteinstruction*/
1306
dumpcode(const char * descr,const unsigned char * obuf,unsigned char * buf,const unsigned char * end)1307 static void dumpcode
1308 (
1309 const char * descr,
1310 const unsigned char *obuf, /* start of buffer for computing instruction numbers for branches */
1311 unsigned char *buf, /* where to insert new compiled code */
1312 const unsigned char *end /* points to after last instruction generated */
1313 )
1314 /* dumps out compiled code for debugging. */
1315 {
1316 #ifdef VM_DEBUG
1317 const unsigned int nrlines = (end - buf) / 8;
1318 unsigned int i, j;
1319 fprintf(stderr, "* %s:\n", descr);
1320 for (i = 0; i < nrlines; ++i)
1321 {
1322 fprintf(stderr, " %3d:", i + (buf - obuf) + 1);
1323 for (j = 0; j < 8; ++j)
1324 {
1325 fprintf(stderr, " %02X", buf[i * 8 + j]);
1326 } /*for*/
1327 fputs("\n", stderr);
1328 } /*for*/
1329 #endif
1330 } /*dumpcode*/
1331
vm_optimize(const unsigned char * obuf,unsigned char * buf,unsigned char ** end)1332 void vm_optimize(const unsigned char *obuf, unsigned char *buf, unsigned char **end)
1333 /* does various peephole optimizations on the part of obuf from buf to *end.
1334 *end will be updated if unnecessary instructions are removed. */
1335 {
1336 unsigned char *b;
1337 again:
1338 for (b = buf; b < *end; b += 8)
1339 {
1340 const int curline = (b - obuf) / 8 + 1;
1341 // if
1342 // 1. this is a jump over one statement
1343 // 2. we can combine the statement with the if
1344 // 3. there are no references to the statement
1345 // then
1346 // combine statement with if, negate if, and replace statement with nop
1347 if
1348 (
1349 b[0] == 0
1350 &&
1351 (b[1] & 0x70) != 0 /* conditional */
1352 &&
1353 (b[1] & 15) == 1 /* cmd = goto */
1354 &&
1355 b[7] == curline + 2 // step 1
1356 &&
1357 (b[9] & 0x70) == 0 /* second instr not conditional */
1358 &&
1359 (
1360 (b[8] & 15) == 0 /* not a set */
1361 ||
1362 (b[9] & 15) == 0 /* not a link */
1363 ) /* not set-and-link in one */
1364 &&
1365 ifcombinable(b[0], b[1], b[8]) // step 2
1366 &&
1367 !isreferenced(buf, *end, curline + 1) // step 3
1368 )
1369 {
1370 const unsigned int ifs = negateif(extractif(b));
1371 memcpy(b, b + 8, 8); // move statement
1372 memset(b + 8, 0, 8); // replace with nop
1373 applyif(b, ifs);
1374 dumpcode("vm_optimize: jump over one => inverse conditional", obuf, buf, *end);
1375 goto again;
1376 } /*if*/
1377 // 1. this is a NOP instruction
1378 // 2. there are more instructions after this OR there are no references here
1379 // then
1380 // delete instruction, fix goto labels
1381 if
1382 (
1383 b[0] == 0
1384 &&
1385 b[1] == 0
1386 &&
1387 b[2] == 0
1388 &&
1389 b[3] == 0
1390 &&
1391 b[4] == 0
1392 &&
1393 b[5] == 0
1394 &&
1395 b[6] == 0
1396 &&
1397 b[7] == 0 /* it's a NOP */
1398 &&
1399 (
1400 b + 8 != *end /* more instructions after this */
1401 ||
1402 !isreferenced(buf, *end, curline) /* no references here */
1403 )
1404 )
1405 {
1406 deleteinstruction(obuf, buf, end, b);
1407 dumpcode("vm_optimize: remove nop", obuf, buf, *end);
1408 goto again;
1409 } /*if*/
1410 // if
1411 // 1. the prev instruction is an UNCONDITIONAL jump/goto
1412 // 2. there are no references to the statement
1413 // then
1414 // delete instruction, fix goto labels
1415 if
1416 (
1417 b > buf
1418 &&
1419 (b[-8] >> 4) <= 3
1420 &&
1421 (b[-7] & 0x70) == 0
1422 &&
1423 (b[-7] & 15) != 0 /* previous was unconditional transfer */
1424 &&
1425 !isreferenced(buf, *end, curline) /* no references here */
1426 /* fixme: should also remove in the case where jump was to this instruction */
1427 )
1428 {
1429 /* remove dead code */
1430 deleteinstruction(obuf, buf, end, b);
1431 dumpcode("vm_optimize: remove dead code", obuf, buf, *end);
1432 goto again;
1433 } /*if*/
1434 // if
1435 // 1. this instruction sets subtitle/angle/audio
1436 // 2. the next instruction sets subtitle/angle/audio
1437 // 3. they both set them the same way (i.e. immediate/indirect)
1438 // 4. there are no references to the second instruction
1439 // then
1440 // combine
1441 if
1442 (
1443 b + 8 != *end
1444 &&
1445 (b[0] & 0xEF) == 0x41 /* SetSTN */
1446 &&
1447 b[1] == 0 // step 1
1448 &&
1449 b[0] == b[8]
1450 &&
1451 b[1] == b[9] // step 2 & 3
1452 &&
1453 !isreferenced(buf, *end, curline + 1)
1454 )
1455 {
1456 if (b[8 + 3])
1457 b[3] = b[8 + 3];
1458 if (b[8 + 4])
1459 b[4] = b[8 + 4];
1460 if (b[8 + 5])
1461 b[5] = b[8 + 5];
1462 deleteinstruction(obuf, buf, end, b + 8);
1463 dumpcode("vm_optimize: merge setting of subtitle/angle/audio", obuf, buf, *end);
1464 goto again;
1465 } /*if*/
1466 // if
1467 // 1. this instruction sets the button directly
1468 // 2. the next instruction is a link command (not NOP, not PGCN)
1469 // 3. there are no references to the second instruction
1470 // then
1471 // combine
1472 if
1473 (
1474 b + 8 != *end
1475 &&
1476 b[0] == 0x56
1477 &&
1478 b[1] == 0x00
1479 &&
1480 b[8] == 0x20
1481 &&
1482 (
1483 (b[8 + 1] & 0xf) == 5
1484 ||
1485 (b[8 + 1] & 0xf) == 6
1486 ||
1487 (b[8 + 1] & 0xf) == 7
1488 ||
1489 (b[8 + 1] & 0xf) == 1
1490 &&
1491 (b[8 + 7] & 0x1f) != 0
1492 )
1493 &&
1494 !isreferenced(buf, *end, curline + 1)
1495 )
1496 {
1497 if (b[8 + 6] == 0)
1498 b[8 + 6] = b[4];
1499 deleteinstruction(obuf, buf, end, b);
1500 dumpcode("vm_optimize: merge set button and link", obuf, buf, *end);
1501 goto again;
1502 } /*if*/
1503 // if
1504 // 1. this instruction sets a GPRM/SPRM register
1505 // 2. the next instruction is a link command (not NOP)
1506 // 3. there are no references to the second instruction
1507 // then
1508 // combine
1509 if
1510 (
1511 b + 8 != *end
1512 &&
1513 ((b[0] & 0xE0) == 0x40 || (b[0] & 0xE0) == 0x60)
1514 &&
1515 (b[1] & 0x7f) == 0x00
1516 &&
1517 b[8] == 0x20
1518 &&
1519 (
1520 (b[8 + 1] & 0x7f) == 4
1521 ||
1522 (b[8 + 1] & 0x7f) == 5
1523 ||
1524 (b[8 + 1] & 0x7f) == 6
1525 ||
1526 (b[8 + 1] & 0x7f) == 7
1527 ||
1528 (b[8 + 1] & 0x7f) == 1
1529 &&
1530 (b[8 + 7] & 0x1f) != 0
1531 )
1532 &&
1533 !isreferenced(buf, *end, curline + 1)
1534 )
1535 {
1536 b[1] = b[8 + 1];
1537 b[6] = b[8 + 6];
1538 b[7] = b[8 + 7];
1539 deleteinstruction(obuf, buf, end, b + 8);
1540 dumpcode("vm_optimize: merge set register and link", obuf, buf, *end);
1541 goto again;
1542 } /*if*/
1543 } /*for*/
1544 } /*vm_optimize*/
1545
vm_compile(const unsigned char * obuf,unsigned char * buf,const struct workset * ws,const struct pgcgroup * curgroup,const struct pgc * curpgc,const struct vm_statement * cs,vtypes ismenu)1546 unsigned char *vm_compile
1547 (
1548 const unsigned char *obuf, /* start of buffer for computing instruction numbers for branches */
1549 unsigned char *buf, /* where to insert new compiled code */
1550 const struct workset *ws,
1551 const struct pgcgroup *curgroup,
1552 const struct pgc *curpgc,
1553 const struct vm_statement *cs,
1554 vtypes ismenu
1555 )
1556 /* compiles the parse tree cs into actual VM instructions with optimization,
1557 and fixes up all the gotos. */
1558 {
1559 unsigned char *end;
1560 int i, j;
1561 numlabels = 0;
1562 numgotos = 0;
1563 end = compilecs(obuf, buf, ws, curgroup, curpgc, cs, ismenu);
1564 if (!end) /* error */
1565 return end;
1566 // fix goto references
1567 for (i = 0; i < numgotos; i++)
1568 {
1569 for (j = 0; j < numlabels; j++)
1570 if (!strcasecmp(gotos[i].lname,labels[j].lname))
1571 break;
1572 if (j == numlabels)
1573 {
1574 fprintf(stderr, "ERR: Cannot find label %s\n", gotos[i].lname);
1575 return 0;
1576 } /*if*/
1577 gotos[i].code[7] = (labels[j].code - obuf) / 8 + 1;
1578 } /*for*/
1579 dumpcode("vm_compile: before vm_optimize", obuf, buf, end);
1580 vm_optimize(obuf, buf, &end);
1581 dumpcode("vm_compile: after vm_optimize", obuf, buf, end);
1582 return end;
1583 } /*vm_compile*/
1584
dvdvmerror(const char * s)1585 void dvdvmerror(const char *s)
1586 /* reports a parse error. */
1587 {
1588 extern char *dvdvmtext;
1589 fprintf(stderr, "ERR: Parse error '%s' on token '%s'\n", s, dvdvmtext);
1590 exit(1);
1591 } /*dvdvmerror*/
1592
vm_parse(const char * b)1593 struct vm_statement *vm_parse(const char *b)
1594 /* parses a VM source string and returns the constructed parse tree. */
1595 {
1596 if (b)
1597 {
1598 const char * const cmd = strdup(b);
1599 dvdvm_buffer_state buf = dvdvm_scan_string(cmd);
1600 dvd_vm_parsed_cmd = 0;
1601 if (dvdvmparse())
1602 {
1603 fprintf(stderr, "ERR: Parser failed on code '%s'.\n", b);
1604 exit(1);
1605 } /*if*/
1606 if (!dvd_vm_parsed_cmd)
1607 {
1608 fprintf(stderr, "ERR: Nothing parsed from '%s'\n", b);
1609 exit(1);
1610 } /*if*/
1611 dvdvm_delete_buffer(buf);
1612 free((void *)cmd);
1613 return dvd_vm_parsed_cmd;
1614 }
1615 else
1616 {
1617 // pieces of code in dvdauthor rely on a non-null vm_statement
1618 // meaning something significant. so if we parse an empty string,
1619 // we should return SOMETHING not null.
1620 // also, since the xml parser strips whitespace, we treat a
1621 // NULL string as an empty string
1622 struct vm_statement * const v = statement_new();
1623 v->op = VM_NOP;
1624 return v;
1625 } /*if*/
1626 } /*vm_parse*/
1627