]> git.xonotic.org Git - xonotic/xonotic.git/blob - misc/tools/progs-analyzer.pl
autobuild: include gmqcc
[xonotic/xonotic.git] / misc / tools / progs-analyzer.pl
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Digest::SHA;
6 use Carp;
7
8 sub id()
9 {
10         return sub { $_[0]; };
11 }
12
13 sub signed($)
14 {
15         my ($bits) = @_;
16         return sub { $_[0] >= (2**($bits-1)) ? $_[0]-(2**$bits) : $_[0]; };
17 }
18
19 use constant OPCODE_E => [qw[
20         DONE
21         MUL_F MUL_V MUL_FV MUL_VF
22         DIV_F
23         ADD_F ADD_V
24         SUB_F SUB_V
25         EQ_F EQ_V EQ_S EQ_E EQ_FNC
26         NE_F NE_V NE_S NE_E NE_FNC
27         LE GE LT GT
28         LOAD_F LOAD_V LOAD_S LOAD_ENT LOAD_FLD LOAD_FNC
29         ADDRESS
30         STORE_F STORE_V STORE_S STORE_ENT STORE_FLD STORE_FNC
31         STOREP_F STOREP_V STOREP_S STOREP_ENT STOREP_FLD STOREP_FNC
32         RETURN
33         NOT_F NOT_V NOT_S NOT_ENT NOT_FNC
34         IF IFNOT
35         CALL0 CALL1 CALL2 CALL3 CALL4 CALL5 CALL6 CALL7 CALL8
36         STATE
37         GOTO
38         AND OR
39         BITAND BITOR
40 ]];
41 use constant ETYPE_E => [qw[
42         void
43         string
44         float
45         vector
46         entity
47         field
48         function
49         pointer
50 ]];
51 use constant DEF_SAVEGLOBAL => 32768;
52 sub typesize($)
53 {
54         my ($type) = @_;
55         return 3 if $type eq 'vector';
56         return 1;
57 }
58
59 sub checkop($)
60 {
61         my ($op) = @_;
62         if($op =~ /^IF.*_V$/)
63         {
64                 return { a => 'inglobalvec', b => 'ipoffset', isjump => 'b', isconditional => 1 };
65         }
66         if($op =~ /^IF/)
67         {
68                 return { a => 'inglobal', b => 'ipoffset', isjump => 'b', isconditional => 1 };
69         }
70         if($op eq 'GOTO')
71         {
72                 return { a => 'ipoffset', isjump => 'a', isconditional => 0 };
73         }
74         if($op =~ /^ADD_V$|^SUB_V$/)
75         {
76                 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobalvec' };
77         }
78         if($op =~ /^MUL_V$|^EQ_V$|^NE_V$/)
79         {
80                 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobal' };
81         }
82         if($op eq 'MUL_FV')
83         {
84                 return { a => 'inglobal', b => 'inglobalvec', c => 'outglobalvec' };
85         }
86         if($op eq 'MUL_VF')
87         {
88                 return { a => 'inglobalvec', b => 'inglobal', c => 'outglobalvec' };
89         }
90         if($op eq 'LOAD_V')
91         {
92                 return { a => 'inglobal', b => 'inglobal', c => 'outglobalvec' };
93         }
94         if($op =~ /^NOT_V/)
95         {
96                 return { a => 'inglobalvec', c => 'outglobal' };
97         }
98         if($op =~ /^NOT_/)
99         {
100                 return { a => 'inglobal', c => 'outglobal' };
101         }
102         if($op eq 'STOREP_V')
103         {
104                 return { a => 'inglobalvec', b => 'inglobal' };
105         }
106         if($op eq 'STORE_V')
107         {
108                 return { a => 'inglobalvec', b => 'outglobalvec' };
109         }
110         if($op =~ /^STOREP_/)
111         {
112                 return { a => 'inglobal', b => 'inglobal' };
113         }
114         if($op =~ /^STORE_/)
115         {
116                 return { a => 'inglobal', b => 'outglobal' };
117         }
118         if($op =~ /^CALL/)
119         {
120                 return { a => 'inglobalfunc', iscall => 1 };
121         }
122         if($op =~ /^DONE$|^RETURN$/)
123         {
124                 return { a => 'inglobalvec', isreturn => 1 };
125         }
126         if($op eq 'STATE')
127         {
128                 return { a => 'inglobal', b => 'inglobalfunc' };
129         }
130         return { a => 'inglobal', b => 'inglobal', c => 'outglobal' };
131 }
132
133 use constant TYPES => {
134         int => ['V', 4, signed 32],
135         ushort => ['v', 2, id],
136         short => ['v', 2, signed 16],
137         opcode => ['v', 2, sub { OPCODE_E->[$_[0]] or do { warn "Invalid opcode: $_[0]"; "INVALID#$_[0]"; }; }],
138         float => ['f', 4, id],
139         uchar8 => ['a8', 8, sub { [unpack 'C8', $_[0]] }],
140         global => ['i', 4, sub { { int => $_[0], float => unpack "f", pack "L", $_[0] }; }],
141         deftype => ['v', 2, sub { { type => ETYPE_E->[$_[0] & ~DEF_SAVEGLOBAL], save => !!($_[0] & DEF_SAVEGLOBAL) }; }],
142 };
143
144 use constant DPROGRAMS_T => [
145         [int => 'version'],
146         [int => 'crc'],
147         [int => 'ofs_statements'],
148         [int => 'numstatements'],
149         [int => 'ofs_globaldefs'],
150         [int => 'numglobaldefs'],
151         [int => 'ofs_fielddefs'],
152         [int => 'numfielddefs'],
153         [int => 'ofs_functions'],
154         [int => 'numfunctions'],
155         [int => 'ofs_strings'],
156         [int => 'numstrings'],
157         [int => 'ofs_globals'],
158         [int => 'numglobals'],
159         [int => 'entityfields']
160 ];
161
162 use constant DSTATEMENT_T => [
163         [opcode => 'op'],
164         [short => 'a'],
165         [short => 'b'],
166         [short => 'c']
167 ];
168
169 use constant DDEF_T => [
170         [deftype => 'type'],
171         [ushort => 'ofs'],
172         [int => 's_name']
173 ];
174
175 use constant DGLOBAL_T => [
176         [global => 'v'],
177 ];
178
179 use constant DFUNCTION_T => [
180         [int => 'first_statement'],
181         [int => 'parm_start'],
182         [int => 'locals'],
183         [int => 'profile'],
184         [int => 's_name'],
185         [int => 's_file'],
186         [int => 'numparms'],
187         [uchar8 => 'parm_size'],
188 ];
189
190 sub get_section($$$)
191 {
192         my ($fh, $start, $len) = @_;
193         seek $fh, $start, 0
194                 or die "seek: $!";
195         $len == read $fh, my $buf, $len
196                 or die "short read from $start length $len (malformed progs header)";
197         return $buf;
198 }
199
200 sub parse_section($$$$$)
201 {
202         my ($fh, $struct, $start, $len, $cnt) = @_;
203
204         my $itemlen = 0;
205         $itemlen += TYPES->{$_->[0]}->[1]
206                 for @$struct;
207         my $packspec = join '', map { TYPES->{$_->[0]}->[0]; } @$struct;
208         my @packnames = map { $_->[1]; } @$struct;
209
210         $len = $cnt * $itemlen
211                 if not defined $len and defined $cnt;
212         $cnt = int($len / $itemlen)
213                 if not defined $cnt and defined $len;
214         die "Invalid length specification"
215                 unless defined $len and defined $cnt and $len == $cnt * $itemlen;
216         die "Invalid length specification in scalar context"
217                 unless wantarray or $cnt == 1;
218
219         seek $fh, $start, 0
220                 or die "seek: $!";
221         my @out = map
222         {
223                 $itemlen == read $fh, my $buf, $itemlen
224                         or die "short read from $start length $cnt * $itemlen $(malformed progs header)";
225                 my %h = ();
226                 @h{@packnames} = unpack $packspec, $buf;
227                 $h{$_->[1]} = TYPES->{$_->[0]}->[2]->($h{$_->[1]})
228                         for @$struct;
229                 \%h;
230         }
231         0..($cnt-1);
232         return @out
233                 if wantarray;
234         return $out[0];
235 }
236
237 sub nfa_default_state_checker()
238 {
239         my %seen;
240         return sub
241         {
242                 my ($ip, $state) = @_;
243                 return $seen{"$ip $state"}++;
244         };
245 }
246
247 sub run_nfa($$$$$$)
248 {
249         my ($progs, $ip, $state, $copy_handler, $state_checker, $instruction_handler) = @_;
250
251         my $statements = $progs->{statements};
252
253         my $nfa;
254         $nfa = sub
255         {
256                 no warnings 'recursion';
257
258                 my ($ip, $state) = @_;
259                 my $ret = 0;
260
261                 for(;;)
262                 {
263                         return $ret
264                                 if $state_checker->($ip, $state);
265
266                         my $s = $statements->[$ip];
267                         my $c = checkop $s->{op};
268
269                         if(($ret = $instruction_handler->($ip, $state, $s, $c)))
270                         {
271                                 # abort execution
272                                 last;
273                         }
274
275                         if($c->{isreturn})
276                         {
277                                 last;
278                         }
279                         elsif($c->{iscall})
280                         {
281                                 my $func = $s->{a};
282                                 my $funcid = $progs->{globals}[$func]{v}{int};
283                                 last
284                                         if $progs->{error_func}{$funcid};
285                                 $ip += 1;
286                         }
287                         elsif($c->{isjump})
288                         {
289                                 if($c->{isconditional})
290                                 {
291                                         if(rand 2)
292                                         {
293                                                 if(($ret = $nfa->($ip+$s->{$c->{isjump}}, $copy_handler->($state))) < 0)
294                                                 {
295                                                         last;
296                                                 }
297                                                 $ip += 1;
298                                         }
299                                         else
300                                         {
301                                                 $nfa->($ip+1, $copy_handler->($state));
302                                                 $ip += $s->{$c->{isjump}};
303                                         }
304                                 }
305                                 else
306                                 {
307                                         $ip += $s->{$c->{isjump}};
308                                 }
309                         }
310                         else
311                         {
312                                 $ip += 1;
313                         }
314                 }
315
316                 return $ret;
317         };
318
319         $nfa->($ip, $copy_handler->($state));
320 }
321
322 sub get_constant($$)
323 {
324         my ($progs, $g) = @_;
325         if($g->{int} == 0)
326         {
327                 return 0;
328         }
329         elsif($g->{int} > 0 && $g->{int} < 8388608)
330         {
331                 if($g->{int} < length $progs->{strings} && $g->{int} > 0)
332                 {
333                         return str($progs->{getstring}->($g->{int}));
334                 }
335                 else
336                 {
337                         return $g->{int} . "i";
338                 }
339         }
340         else
341         {
342                 return $g->{float};
343         }
344 }
345
346 use constant PRE_MARK_STATEMENT => "";
347 use constant POST_MARK_STATEMENT => "";
348 use constant PRE_MARK_OPERAND => "*** ";
349 use constant POST_MARK_OPERAND => " ***";
350
351 use constant INSTRUCTION_FORMAT => "%8s %3s | %-12s ";
352 use constant OPERAND_FORMAT => "%s";
353 use constant OPERAND_SEPARATOR => ", ";
354 use constant INSTRUCTION_SEPARATOR => "\n";
355
356 sub str($)
357 {
358         my ($str) = @_;
359         $str =~ s/[\000-\037\\\"\177-\377]/sprintf "\\%03o", ord $&/ge;
360         return "\"$str\"";
361 }
362
363 sub disassemble_function($$;$)
364 {
365         my ($progs, $func, $highlight) = @_;
366
367         print "$func->{debugname}:\n";
368
369         if($func->{first_statement} < 0) # builtin
370         {
371                 printf INSTRUCTION_FORMAT, '', '', '.BUILTIN';
372                 printf OPERAND_FORMAT, -$func->{first_statement};
373                 print INSTRUCTION_SEPARATOR;
374                 return;
375         }
376
377         my $initializer = sub
378         {
379                 my ($ofs) = @_;
380                 my $g = get_constant($progs, $progs->{globals}[$ofs]{v});
381                 print " = $g"
382                         if defined $g;
383         };
384
385         printf INSTRUCTION_FORMAT, '', '', '.PARM_START';
386         printf OPERAND_FORMAT, "$func->{parm_start}";
387         print INSTRUCTION_SEPARATOR;
388
389         printf INSTRUCTION_FORMAT, '', '', '.LOCALS';
390         printf OPERAND_FORMAT, "$func->{locals}";
391         print INSTRUCTION_SEPARATOR;
392
393         my %override_locals = ();
394         my $p = $func->{parm_start};
395         for(0..($func->{numparms}-1))
396         {
397                 $override_locals{$p} //= "argv_$_";
398                 for my $comp(0..($func->{parm_size}[$_]-1))
399                 {
400                         $override_locals{$p} //= "argv_$_\[$comp]";
401                         ++$p;
402                 }
403                 printf INSTRUCTION_FORMAT, '', '', '.ARG';
404                 printf OPERAND_FORMAT, "argv_$_";
405                 print OPERAND_SEPARATOR;
406                 printf OPERAND_FORMAT, $func->{parm_size}[$_];
407                 print INSTRUCTION_SEPARATOR;
408         }
409         for($func->{parm_start}..($func->{parm_start} + $func->{locals} - 1))
410         {
411                 next
412                         if exists $override_locals{$_};
413                 $override_locals{$_} = "local_$_";
414
415                 printf INSTRUCTION_FORMAT, '', '', '.LOCAL';
416                 printf OPERAND_FORMAT, "local_$_";
417                 $initializer->($_);
418                 print INSTRUCTION_SEPARATOR;
419         }
420
421         my $getname = sub
422         {
423                 my ($ofs) = @_;
424                 return $override_locals{$ofs}
425                         if exists $override_locals{$ofs};
426                 my $def = $progs->{globaldef_byoffset}->($ofs);
427                 return $def->{debugname};
428         };
429
430         my $operand = sub
431         {
432                 my ($ip, $type, $operand) = @_;
433                 if($type eq 'inglobal')
434                 {
435                         my $name = $getname->($operand);
436                         printf OPERAND_FORMAT, "$name";
437                 }
438                 elsif($type eq 'outglobal')
439                 {
440                         my $name = $getname->($operand);
441                         printf OPERAND_FORMAT, "&$name";
442                 }
443                 elsif($type eq 'inglobalvec')
444                 {
445                         my $name = $getname->($operand);
446                         printf OPERAND_FORMAT, "$name\[\]";
447                 }
448                 elsif($type eq 'outglobalvec')
449                 {
450                         my $name = $getname->($operand);
451                         printf OPERAND_FORMAT, "&$name\[\]";
452                 }
453                 elsif($type eq 'inglobalfunc')
454                 {
455                         my $name = $getname->($operand);
456                         printf OPERAND_FORMAT, "$name()";
457                 }
458                 elsif($type eq 'ipoffset')
459                 {
460                         printf OPERAND_FORMAT, "@{[$ip + $operand]}" . sprintf ' ($%+d)', $operand;
461                 }
462                 else
463                 {
464                         die "unknown type: $type";
465                 }
466         };
467
468         my $statements = $func->{statements};
469         my $come_from = $func->{come_from};
470
471         my $ipprev = undef;
472         for my $ip(sort { $a <=> $b } keys %$statements)
473         {
474                 if($ip == $func->{first_statement})
475                 {
476                         printf INSTRUCTION_FORMAT, $ip, '', '.ENTRY';
477                         print INSTRUCTION_SEPARATOR;
478                 }
479                 if(defined $ipprev && $ip != $ipprev + 1)
480                 {
481                         printf INSTRUCTION_FORMAT, $ip, '', '.SKIP';
482                         printf OPERAND_FORMAT, $ip - $ipprev - 1;
483                         print INSTRUCTION_SEPARATOR;
484                 }
485                 if(my $cf = $come_from->{$ip})
486                 {
487                         printf INSTRUCTION_FORMAT, $ip, '', '.XREF';
488                         my $cnt = 0;
489                         for(sort { $a <=> $b } keys %$cf)
490                         {
491                                 print OPERAND_SEPARATOR
492                                         if $cnt++;
493                                 printf OPERAND_FORMAT, ($cf->{$_} ? 'c' : 'j') . $_ . sprintf ' ($%+d)', $_ - $ip;
494                         }
495                         print INSTRUCTION_SEPARATOR;
496                 }
497
498                 my $op = $progs->{statements}[$ip]{op};
499                 my $ipt = $progs->{statements}[$ip];
500                 my $opprop = checkop $op;
501
502                 if($highlight and $highlight->{$ip})
503                 {
504                         for(values %{$highlight->{$ip}})
505                         {
506                                 for(sort keys %$_)
507                                 {
508                                         print PRE_MARK_STATEMENT;
509                                         printf INSTRUCTION_FORMAT, '', '<!>', '.WARN';
510                                         printf OPERAND_FORMAT, "$_ (in $func->{debugname})";
511                                         print INSTRUCTION_SEPARATOR;
512                                 }
513                         }
514                 }
515
516                 print PRE_MARK_STATEMENT
517                         if $highlight and $highlight->{$ip};
518
519                 my $showip = $opprop->{isjump};
520                 printf INSTRUCTION_FORMAT, $showip ? $ip : '', $highlight->{$ip} ? '<!>' : '', $op;
521
522                 my $cnt = 0;
523                 for my $o(qw(a b c))
524                 {
525                         next
526                                 if not defined $opprop->{$o};
527                         print OPERAND_SEPARATOR
528                                 if $cnt++;
529                         print PRE_MARK_OPERAND
530                                 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
531                         $operand->($ip, $opprop->{$o}, $ipt->{$o});
532                         print POST_MARK_OPERAND
533                                 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
534                 }
535
536                 print POST_MARK_STATEMENT
537                         if $highlight and $highlight->{$ip};
538
539                 print INSTRUCTION_SEPARATOR;
540         }
541 }
542
543 sub find_uninitialized_locals($$)
544 {
545         my ($progs, $func) = @_;
546
547         return
548                 if $func->{first_statement} < 0; # builtin
549
550         print STDERR "Checking $func->{debugname}...\n";
551
552         my $p = $func->{parm_start};
553         for(0..($func->{numparms}-1))
554         {
555                 $p += $func->{parm_size}[$_];
556         }
557
558         use constant WATCHME_R => 1;
559         use constant WATCHME_W => 2;
560         use constant WATCHME_X => 4;
561         use constant WATCHME_T => 8;
562         my %watchme = map { $_ => WATCHME_X } ($func->{parm_start} .. ($func->{parm_start} + $func->{locals} - 1));
563
564         for(keys %{$progs->{temps}})
565         {
566                 next
567                         if exists $watchme{$_};
568                 if($progs->{temps}{$_})
569                 {
570                         # shared temp
571                         $watchme{$_} = WATCHME_T | WATCHME_X
572                 }
573                 else
574                 {
575                         # unique temp
576                         $watchme{$_} = WATCHME_X
577                 }
578         }
579
580         $watchme{$_} |= WATCHME_R
581                 for keys %{$func->{globals_read}};
582         $watchme{$_} |= WATCHME_W
583                 for keys %{$func->{globals_written}};
584
585         my %write_places = ();
586         for my $ofs(keys %{$func->{globals_written}})
587         {
588                 next
589                         unless exists $watchme{$ofs} and $watchme{$ofs} & WATCHME_X;
590                 for my $ip(keys %{$func->{globals_written}{$ofs}})
591                 {
592                         for my $op(keys %{$func->{globals_written}{$ofs}{$ip}})
593                         {
594                                 push @{$write_places{$ip}{$op}}, $ofs;
595                         }
596                 }
597         }
598
599         for(keys %watchme)
600         {
601                 delete $watchme{$_}
602                         if ($watchme{$_} & (WATCHME_R | WATCHME_W | WATCHME_X)) != (WATCHME_R | WATCHME_W | WATCHME_X);
603         }
604
605         return
606                 if not keys %watchme;
607
608         for(keys %watchme)
609         {
610                 $watchme{$_} = {
611                         flags => $watchme{$_},
612                         valid => [0, undef, undef]
613                 };
614         }
615
616         # mark parameters as initialized
617         for($func->{parm_start} .. ($p-1))
618         {
619                 $watchme{$_}{valid} = [1, undef, undef]
620                         if defined $watchme{$_};
621         }
622
623         my %warned = ();
624         my %ip_seen = ();
625         run_nfa $progs, $func->{first_statement}, \%watchme,
626                 sub {
627                         my ($h) = @_;
628                         return { map { $_ => { %{$h->{$_}} } } keys %$h };
629                 },
630                 sub {
631                         my ($ip, $state) = @_;
632
633                         my $s = $ip_seen{$ip};
634                         if($s)
635                         {
636                                 # if $state is stronger or equal to $s, return 1
637
638                                 for(keys %$state)
639                                 {
640                                         if($state->{$_}{valid}[0] < $s->{$_})
641                                         {
642                                                 # The current state is LESS valid than the previously run one. We NEED to run this.
643                                                 # The saved state can safely become the intersection [citation needed].
644                                                 for(keys %$state)
645                                                 {
646                                                         $s->{$_} = $state->{$_}{valid}[0]
647                                                                 if $state->{$_}{valid}[0] < $s->{$_};
648                                                 }
649                                                 return 0;
650                                         }
651                                 }
652                                 # if we get here, $state is stronger or equal. No need to try it.
653                                 return 1;
654                         }
655                         else
656                         {
657                                 # Never seen this IP yet.
658                                 $ip_seen{$ip} = { map { ($_ => $state->{$_}{valid}[0]); } keys %$state };
659                                 return 0;
660                         }
661                 },
662                 sub {
663                         my ($ip, $state, $s, $c) = @_;
664                         my $op = $s->{op};
665
666                         # QCVM BUG: RETURN always takes vector, there is no float equivalent
667                         my $return_hack = $c->{isreturn} // 0;
668
669                         if($op eq 'STORE_V')
670                         {
671                                 # COMPILER BUG of QCC: params are always copied using STORE_V
672                                 if($s->{b} >= 4 && $s->{b} < 28) # parameter range
673                                 {
674                                         $return_hack = 1;
675                                 }
676                         }
677
678                         for(qw(a b c))
679                         {
680                                 my $type = $c->{$_};
681                                 next
682                                         unless defined $type;
683
684                                 my $ofs = $s->{$_};
685
686                                 my $read = sub
687                                 {
688                                         my ($ofs) = @_;
689                                         ++$return_hack
690                                                 if $return_hack;
691                                         return
692                                                 if not exists $state->{$ofs};
693                                         my $valid = $state->{$ofs}{valid};
694                                         if($valid->[0] == 0)
695                                         {
696                                                 # COMPILER BUG of FTEQCC: AND and OR may take uninitialized as second argument (logicops)
697                                                 if($return_hack <= 2 and ($op ne 'OR' && $op ne 'AND' || $_ ne 'b'))
698                                                 {
699                                                         ++$warned{$ip}{$_}{"Use of uninitialized value"};
700                                                 }
701                                         }
702                                         elsif($valid->[0] < 0)
703                                         {
704                                                 # COMPILER BUG of FTEQCC: AND and OR may take uninitialized as second argument (logicops)
705                                                 if($return_hack <= 2 and ($op ne 'OR' && $op ne 'AND' || $_ ne 'b'))
706                                                 {
707                                                         ++$warned{$ip}{$_}{"Use of temporary across CALL"};
708                                                 }
709                                         }
710                                         else
711                                         {
712                                                 # it's VALID
713                                                 if(defined $valid->[1])
714                                                 {
715                                                         delete $write_places{$valid->[1]}{$valid->[2]};
716                                                 }
717                                         }
718                                 };
719                                 my $write = sub
720                                 {
721                                         my ($ofs) = @_;
722                                         $state->{$ofs}{valid} = [1, $ip, $_]
723                                                 if exists $state->{$ofs};
724                                 };
725
726                                 if($type eq 'inglobal' || $type eq 'inglobalfunc')
727                                 {
728                                         $read->($ofs);
729                                 }
730                                 elsif($type eq 'inglobalvec')
731                                 {
732                                         $read->($ofs);
733                                         $read->($ofs+1);
734                                         $read->($ofs+2);
735                                 }
736                                 elsif($type eq 'outglobal')
737                                 {
738                                         $write->($ofs);
739                                 }
740                                 elsif($type eq 'outglobalvec')
741                                 {
742                                         $write->($ofs);
743                                         $write->($ofs+1);
744                                         $write->($ofs+2);
745                                 }
746                                 elsif($type eq 'ipoffset')
747                                 {
748                                         ++$warned{$ip}{$_}{"Endless loop"}
749                                                 if $ofs == 0;
750                                         ++$warned{$ip}{$_}{"No-operation jump"}
751                                                 if $ofs == 1;
752                                 }
753                         }
754                         if($c->{iscall})
755                         {
756                                 # builtin calls may clobber stuff
757                                 my $func = $s->{a};
758                                 my $funcid = $progs->{globals}[$func]{v}{int};
759                                 my $funcobj = $progs->{functions}[$funcid];
760                                 if(!$funcobj || $funcobj->{first_statement} >= 0)
761                                 {
762                                         # invalidate temps
763                                         for(values %$state)
764                                         {
765                                                 if($_->{flags} & WATCHME_T)
766                                                 {
767                                                         $_->{valid} = [-1, undef, undef];
768                                                 }
769                                         }
770                                 }
771                         }
772
773                         return 0;
774                 };
775
776         for my $ip(keys %write_places)
777         {
778                 for my $operand(keys %{$write_places{$ip}})
779                 {
780                         # TODO verify it
781                         my %left = map { $_ => 1 } @{$write_places{$ip}{$operand}};
782                         my $isread = 0;
783
784                         my %writeplace_seen = ();
785                         run_nfa $progs, $ip+1, \%left,
786                                 sub
787                                 {
788                                         return { %{$_[0]} };
789                                 },
790                                 sub
791                                 {
792                                         my ($ip, $state) = @_;
793                                         return $writeplace_seen{"$ip " . join " ", sort keys %$state}++;
794                                 },
795                                 sub
796                                 {
797                                         my ($ip, $state, $s, $c) = @_;
798                                         for(qw(a b c))
799                                         {
800                                                 my $type = $c->{$_};
801                                                 next
802                                                         unless defined $type;
803
804                                                 my $ofs = $s->{$_};
805                                                 if($type eq 'inglobal' || $type eq 'inglobalfunc')
806                                                 {
807                                                         if($state->{$ofs})
808                                                         {
809                                                                 $isread = 1;
810                                                                 return -1; # exit TOTALLY
811                                                         }
812                                                 }
813                                                 elsif($type eq 'inglobalvec')
814                                                 {
815                                                         if($state->{$ofs} || $state->{$ofs+1} || $state->{$ofs+2})
816                                                         {
817                                                                 $isread = 1;
818                                                                 return -1; # exit TOTALLY
819                                                         }
820                                                 }
821                                                 elsif($type eq 'outglobal')
822                                                 {
823                                                         delete $state->{$ofs};
824                                                         return 1
825                                                                 if !%$state;
826                                                 }
827                                                 elsif($type eq 'outglobalvec')
828                                                 {
829                                                         delete $state->{$ofs};
830                                                         delete $state->{$ofs+1};
831                                                         delete $state->{$ofs+2};
832                                                         return 1
833                                                                 if !%$state;
834                                                 }
835                                         }
836                                         return 0;
837                                 };
838
839                         if(!$isread)
840                         {
841                                 ++$warned{$ip}{$operand}{"Value is never used"};
842                         }
843                 }
844         }
845         
846         disassemble_function($progs, $func, \%warned)
847                 if keys %warned;
848 }
849
850 use constant DEFAULTGLOBALS => [
851         "OFS_NULL",
852         "OFS_RETURN",
853         "OFS_RETURN[1]",
854         "OFS_RETURN[2]",
855         "OFS_PARM0",
856         "OFS_PARM0[1]",
857         "OFS_PARM0[2]",
858         "OFS_PARM1",
859         "OFS_PARM1[1]",
860         "OFS_PARM1[2]",
861         "OFS_PARM2",
862         "OFS_PARM2[1]",
863         "OFS_PARM2[2]",
864         "OFS_PARM3",
865         "OFS_PARM3[1]",
866         "OFS_PARM3[2]",
867         "OFS_PARM4",
868         "OFS_PARM4[1]",
869         "OFS_PARM4[2]",
870         "OFS_PARM5",
871         "OFS_PARM5[1]",
872         "OFS_PARM5[2]",
873         "OFS_PARM6",
874         "OFS_PARM6[1]",
875         "OFS_PARM6[2]",
876         "OFS_PARM7",
877         "OFS_PARM7[1]",
878         "OFS_PARM7[2]"
879 ];
880
881 sub defaultglobal($)
882 {
883         my ($ofs) = @_;
884         if($ofs < @{(DEFAULTGLOBALS)})
885         {
886                 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
887         }
888         return { ofs => $ofs, s_name => undef, debugname => "<undefined>\@$ofs", type => undef };
889 }
890
891 sub detect_constants($)
892 {
893         my ($progs) = @_;
894         use constant GLOBALFLAG_R => 1; # read
895         use constant GLOBALFLAG_W => 2; # written
896         use constant GLOBALFLAG_S => 4; # saved
897         use constant GLOBALFLAG_I => 8; # initialized
898         use constant GLOBALFLAG_N => 16; # named
899         use constant GLOBALFLAG_Q => 32; # unique to function
900         use constant GLOBALFLAG_U => 64; # unused
901         use constant GLOBALFLAG_P => 128; # possibly parameter passing
902         use constant GLOBALFLAG_D => 256; # has a def
903         my @globalflags = (GLOBALFLAG_Q | GLOBALFLAG_U) x (@{$progs->{globals}} + 2);
904
905         for(@{$progs->{functions}})
906         {
907                 for(keys %{$_->{globals_used}})
908                 {
909                         if($globalflags[$_] & GLOBALFLAG_U)
910                         {
911                                 $globalflags[$_] &= ~GLOBALFLAG_U;
912                         }
913                         elsif($globalflags[$_] & GLOBALFLAG_Q)
914                         {
915                                 $globalflags[$_] &= ~GLOBALFLAG_Q;
916                         }
917                 }
918                 $globalflags[$_] |= GLOBALFLAG_R
919                         for keys %{$_->{globals_read}};
920                 $globalflags[$_] |= GLOBALFLAG_W
921                         for keys %{$_->{globals_written}};
922                 next
923                         if $_->{first_statement} < 0;
924                 for my $ip($_->{first_statement} .. (@{$progs->{statements}}-1))
925                 {
926                         my $s = $progs->{statements}[$ip];
927                         if($s->{op} eq 'STORE_V')
928                         {
929                                 $globalflags[$s->{a}] |= GLOBALFLAG_P
930                                         if $s->{b} >= $_->{parm_start} and $s->{b} < $_->{parm_start} + $_->{locals};
931                                 $globalflags[$s->{a}+1] |= GLOBALFLAG_P
932                                         if $s->{b}+1 >= $_->{parm_start} and $s->{b}+1 < $_->{parm_start} + $_->{locals};
933                                 $globalflags[$s->{a}+2] |= GLOBALFLAG_P
934                                         if $s->{b}+2 >= $_->{parm_start} and $s->{b}+2 < $_->{parm_start} + $_->{locals};
935                         }
936                         elsif($s->{op} =~ /^STORE_/)
937                         {
938                                 $globalflags[$s->{a}] |= GLOBALFLAG_P
939                                         if $s->{b} >= $_->{parm_start} and $s->{b} < $_->{parm_start} + $_->{locals};
940                         }
941                         else
942                         {
943                                 last;
944                         }
945                 }
946         }
947
948         # parameter passing globals are only ever used in STORE_ instructions
949         for my $s(@{$progs->{statements}})
950         {
951                 next
952                         if $s->{op} =~ /^STORE_/;
953
954                 my $c = checkop $s->{op};
955
956                 for(qw(a b c))
957                 {
958                         my $type = $c->{$_};
959                         next
960                                 unless defined $type;
961
962                         my $ofs = $s->{$_};
963                         if($type eq 'inglobal' || $type eq 'inglobalfunc' || $type eq 'outglobal')
964                         {
965                                 $globalflags[$ofs] &= ~GLOBALFLAG_P;
966                         }
967                         if($type eq 'inglobalvec' || $type eq 'outglobalvec')
968                         {
969                                 $globalflags[$ofs] &= ~GLOBALFLAG_P;
970                                 $globalflags[$ofs+1] &= ~GLOBALFLAG_P;
971                                 $globalflags[$ofs+2] &= ~GLOBALFLAG_P;
972                         }
973                 }
974         }
975
976         my %offsets_saved = ();
977         for(@{$progs->{globaldefs}})
978         {
979                 my $type = $_->{type};
980                 my $name = $progs->{getstring}->($_->{s_name});
981                 $name = ''
982                         if $name eq 'IMMEDIATE' or $name =~ /^\./;
983                 $_->{debugname} = $name
984                         if $name ne '';
985                 $globalflags[$_->{ofs}] |= GLOBALFLAG_D;
986                 if($type->{save})
987                 {
988                         $globalflags[$_->{ofs}] |= GLOBALFLAG_S;
989                 }
990                 if(defined $_->{debugname})
991                 {
992                         $globalflags[$_->{ofs}] |= GLOBALFLAG_N;
993                 }
994         }
995         # fix up vectors
996         my @extradefs = ();
997         for(@{$progs->{globaldefs}})
998         {
999                 my $type = $_->{type};
1000                 for my $i(1..(typesize($type->{type})-1))
1001                 {
1002                         # add missing def
1003                         if(!($globalflags[$_->{ofs}+$i] & GLOBALFLAG_D))
1004                         {
1005                                 print "Missing globaldef for a component@{[defined $_->{debugname} ? ' of ' . $_->{debugname} : '']} at $_->{ofs}+$i\n";
1006                                 push @extradefs, {
1007                                         type => {
1008                                                 saved => 0,
1009                                                 type => 'float'
1010                                         },
1011                                         ofs => $_->{ofs} + $i,
1012                                         debugname => defined $_->{debugname} ? $_->{debugname} . "[$i]" : undef
1013                                 };
1014                         }
1015                         # "saved" and "named" states hit adjacent globals too
1016                         $globalflags[$_->{ofs}+$i] |= $globalflags[$_->{ofs}] & (GLOBALFLAG_S | GLOBALFLAG_N | GLOBALFLAG_D);
1017                 }
1018         }
1019         push @{$progs->{globaldefs}}, @extradefs;
1020
1021         my %offsets_initialized = ();
1022         for(0..(@{$progs->{globals}}-1))
1023         {
1024                 if($progs->{globals}[$_]{v}{int})
1025                 {
1026                         $globalflags[$_] |= GLOBALFLAG_I;
1027                 }
1028         }
1029
1030         my @globaltypes = (undef) x @{$progs->{globals}};
1031
1032         my %istemp = ();
1033         for(0..(@{$progs->{globals}}-1))
1034         {
1035                 next
1036                         if $_ < @{(DEFAULTGLOBALS)};
1037                 if(($globalflags[$_] & (GLOBALFLAG_R | GLOBALFLAG_W)) == 0)
1038                 {
1039                         $globaltypes[$_] = "unused";
1040                 }
1041                 elsif(($globalflags[$_] & (GLOBALFLAG_R | GLOBALFLAG_W)) == GLOBALFLAG_R)
1042                 {
1043                         # so it is ro
1044                         if(($globalflags[$_] & GLOBALFLAG_N) == GLOBALFLAG_N)
1045                         {
1046                                 $globaltypes[$_] = "read_only";
1047                         }
1048                         elsif(($globalflags[$_] & GLOBALFLAG_S) == 0)
1049                         {
1050                                 $globaltypes[$_] = "const";
1051                         }
1052                         else
1053                         {
1054                                 $globaltypes[$_] = "read_only";
1055                         }
1056                 }
1057                 elsif(($globalflags[$_] & (GLOBALFLAG_R | GLOBALFLAG_W)) == GLOBALFLAG_W)
1058                 {
1059                         $globaltypes[$_] = "write_only";
1060                 }
1061                 else
1062                 {
1063                         # now we know it is rw
1064                         if(($globalflags[$_] & GLOBALFLAG_N) == GLOBALFLAG_N)
1065                         {
1066                                 $globaltypes[$_] = "global";
1067                         }
1068                         elsif(($globalflags[$_] & (GLOBALFLAG_S | GLOBALFLAG_I)) == 0)
1069                         {
1070                                 if($globalflags[$_] & GLOBALFLAG_P)
1071                                 {
1072                                         $globaltypes[$_] = "OFS_PARM";
1073                                 }
1074                                 elsif($globalflags[$_] & GLOBALFLAG_Q)
1075                                 {
1076                                         $globaltypes[$_] = "uniquetemp";
1077                                         $istemp{$_} = 0;
1078                                 }
1079                                 else
1080                                 {
1081                                         $globaltypes[$_] = "temp";
1082                                         $istemp{$_} = 1;
1083                                 }
1084                         }
1085                         elsif(($globalflags[$_] & (GLOBALFLAG_S | GLOBALFLAG_I)) == GLOBALFLAG_I)
1086                         {
1087                                 $globaltypes[$_] = "not_saved";
1088                         }
1089                         else
1090                         {
1091                                 $globaltypes[$_] = "global";
1092                         }
1093                 }
1094         }
1095         $progs->{temps} = \%istemp;
1096
1097         # globaldefs
1098         my @globaldefs = (undef) x @{$progs->{globals}};
1099         for(@{$progs->{globaldefs}})
1100         {
1101                 $globaldefs[$_->{ofs}] //= $_
1102                         if defined $_->{debugname};
1103         }
1104         for(@{$progs->{globaldefs}})
1105         {
1106                 $globaldefs[$_->{ofs}] //= $_;
1107         }
1108         for(0..(@{$progs->{globals}}-1))
1109         {
1110                 $globaldefs[$_] //= {
1111                         ofs => $_,
1112                         s_name => undef,
1113                         debugname => undef,
1114                         type => undef
1115                 };
1116         }
1117         for(0..(@{(DEFAULTGLOBALS)}-1))
1118         {
1119                 $globaldefs[$_] = { ofs => $_, s_name => undef, debugname => DEFAULTGLOBALS->[$_], type => undef };
1120                 $globaltypes[$_] = 'defglobal';
1121         }
1122         my %globaldefs_namecount = ();
1123         for(@globaldefs)
1124         {
1125                 $_->{globaltype} = $globaltypes[$_->{ofs}];
1126                 if(defined $_->{debugname})
1127                 {
1128                         # already has debugname
1129                 }
1130                 elsif($_->{globaltype} eq 'const')
1131                 {
1132                         $_->{debugname} = get_constant($progs, $progs->{globals}[$_->{ofs}]{v});
1133                 }
1134                 else
1135                 {
1136                         $_->{debugname} = "$_->{globaltype}_$_->{ofs}";
1137                 }
1138                 ++$globaldefs_namecount{$_->{debugname}};
1139         }
1140         for(@globaldefs)
1141         {
1142                 next
1143                         if $globaldefs_namecount{$_->{debugname}} <= 1 && !$ENV{FORCE_OFFSETS};
1144                 #print "Not unique: $_->{debugname} at $_->{ofs}\n";
1145                 $_->{debugname} .= "\@$_->{ofs}";
1146         }
1147         $progs->{globaldef_byoffset} = sub
1148         {
1149                 my ($ofs) = @_;
1150                 my $def = $globaldefs[$ofs];
1151                 return $def;
1152         };
1153 }
1154
1155 sub parse_progs($)
1156 {
1157         my ($fh) = @_;
1158
1159         my %p = ();
1160
1161         print STDERR "Parsing header...\n";
1162         $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
1163         
1164         print STDERR "Parsing strings...\n";
1165         $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
1166         $p{getstring} = sub
1167         {
1168                 my ($startpos) = @_;
1169                 my $endpos = index $p{strings}, "\0", $startpos;
1170                 return substr $p{strings}, $startpos, $endpos - $startpos;
1171         };
1172
1173         print STDERR "Parsing globals...\n";
1174         $p{globals} = [parse_section $fh, DGLOBAL_T, $p{header}{ofs_globals}, undef, $p{header}{numglobals}];
1175
1176         print STDERR "Parsing globaldefs...\n";
1177         $p{globaldefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_globaldefs}, undef, $p{header}{numglobaldefs}];
1178
1179         print STDERR "Range checking globaldefs...\n";
1180         for(0 .. (@{$p{globaldefs}}-1))
1181         {
1182                 my $g = $p{globaldefs}[$_];
1183                 die "Out of range name in globaldef $_"
1184                         if $g->{s_name} < 0 || $g->{s_name} >= length $p{strings};
1185                 my $name = $p{getstring}->($g->{s_name});
1186                 die "Out of range ofs $g->{ofs} in globaldef $_ (name: \"$name\")"
1187                         if $g->{ofs} >= $p{globals};
1188         }
1189
1190         print STDERR "Parsing fielddefs...\n";
1191         $p{fielddefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_fielddefs}, undef, $p{header}{numfielddefs}];
1192
1193         print STDERR "Range checking fielddefs...\n";
1194         for(0 .. (@{$p{fielddefs}}-1))
1195         {
1196                 my $g = $p{fielddefs}[$_];
1197                 die "Out of range name in fielddef $_"
1198                         if $g->{s_name} < 0 || $g->{s_name} >= length $p{strings};
1199                 my $name = $p{getstring}->($g->{s_name});
1200                 die "Out of range ofs $g->{ofs} in fielddef $_ (name: \"$name\")"
1201                         if $g->{ofs} >= $p{header}{entityfields};
1202         }
1203
1204         print STDERR "Parsing statements...\n";
1205         $p{statements} = [parse_section $fh, DSTATEMENT_T, $p{header}{ofs_statements}, undef, $p{header}{numstatements}];
1206
1207         print STDERR "Parsing functions...\n";
1208         $p{functions} = [parse_section $fh, DFUNCTION_T, $p{header}{ofs_functions}, undef, $p{header}{numfunctions}];
1209
1210         print STDERR "Range checking functions...\n";
1211         for(0 .. (@{$p{functions}} - 1))
1212         {
1213                 my $f = $p{functions}[$_];
1214                 die "Out of range name in function $_"
1215                         if $f->{s_name} < 0 || $f->{s_name} >= length $p{strings};
1216                 my $name = $p{getstring}->($f->{s_name});
1217                 die "Out of range file in function $_"
1218                         if $f->{s_file} < 0 || $f->{s_file} >= length $p{strings};
1219                 my $file = $p{getstring}->($f->{s_file});
1220                 die "Out of range first_statement in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1221                         if $f->{first_statement} >= @{$p{statements}};
1222                 die "Out of range parm_start in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1223                         if $f->{parm_start} < 0 || $f->{parm_start} >= @{$p{globals}};
1224                 die "Out of range locals in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1225                         if $f->{locals} < 0 || $f->{parm_start} + $f->{locals} >= @{$p{globals}};
1226                 if($f->{first_statement} >= 0)
1227                 {
1228                         die "Out of range numparms $f->{numparms} in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1229                                 if $f->{numparms} < 0 || $f->{numparms} > 8;
1230                         my $totalparms = 0;
1231                         for(0..($f->{numparms}-1))
1232                         {
1233                                 die "Out of range parm_size[$_] in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1234                                         unless { 0 => 1, 1 => 1, 3 => 1 }->{$f->{parm_size}[$_]};
1235                                 $totalparms += $f->{parm_size}[$_];
1236                         }
1237                         die "Out of range parms in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
1238                                 if $f->{locals} < 0 || $f->{parm_start} + $totalparms >= @{$p{globals}};
1239                 }
1240         }
1241
1242         print STDERR "Range checking statements...\n";
1243         for my $ip(0 .. (@{$p{statements}}-1))
1244         {
1245                 my $s = $p{statements}[$ip];
1246                 my $c = checkop $s->{op};
1247
1248                 for(qw(a b c))
1249                 {
1250                         my $type = $c->{$_};
1251                         next
1252                                 unless defined $type;
1253
1254                         if($type eq 'inglobal' || $type eq 'inglobalfunc')
1255                         {
1256                                 $s->{$_} &= 0xFFFF;
1257                                 die "Out of range global offset in statement $ip - cannot continue"
1258                                         if $s->{$_} >= @{$p{globals}};
1259                         }
1260                         elsif($type eq 'inglobalvec')
1261                         {
1262                                 $s->{$_} &= 0xFFFF;
1263                                 if($c->{isreturn})
1264                                 {
1265                                         die "Out of range global offset in statement $ip - cannot continue"
1266                                                 if $s->{$_} >= @{$p{globals}};
1267                                         print "Potentially out of range global offset in statement $ip - may crash engines"
1268                                                 if $s->{$_} >= @{$p{globals}}-2;
1269                                 }
1270                                 else
1271                                 {
1272                                         die "Out of range global offset in statement $ip - cannot continue"
1273                                                 if $s->{$_} >= @{$p{globals}}-2;
1274                                 }
1275                         }
1276                         elsif($type eq 'outglobal')
1277                         {
1278                                 $s->{$_} &= 0xFFFF;
1279                                 die "Out of range global offset in statement $ip - cannot continue"
1280                                         if $s->{$_} >= @{$p{globals}};
1281                         }
1282                         elsif($type eq 'outglobalvec')
1283                         {
1284                                 $s->{$_} &= 0xFFFF;
1285                                 die "Out of range global offset in statement $ip - cannot continue"
1286                                         if $s->{$_} >= @{$p{globals}}-2;
1287                         }
1288                         elsif($type eq 'ipoffset')
1289                         {
1290                                 die "Out of range GOTO/IF/IFNOT in statement $ip - cannot continue"
1291                                         if $ip + $s->{$_} < 0 || $ip + $s->{$_} >= @{$p{statements}};
1292                         }
1293                 }
1294         }
1295
1296         print STDERR "Looking for error()...\n";
1297         $p{error_func} = {};
1298         for(@{$p{globaldefs}})
1299         {
1300                 next
1301                         if $p{getstring}($_->{s_name}) ne 'error';
1302                 my $v = $p{globals}[$_->{ofs}]{v}{int};
1303                 next
1304                         if $v <= 0 || $v >= @{$p{functions}};
1305                 my $first = $p{functions}[$v]{first_statement};
1306                 next
1307                         if $first >= 0;
1308                 print STDERR "Detected error() at offset $_->{ofs} (builtin #@{[-$first]})\n";
1309                 $p{error_func}{$_->{ofs}} = 1;
1310         }
1311
1312         print STDERR "Scanning functions...\n";
1313         for(@{$p{functions}})
1314         {
1315                 my $file = $p{getstring}->($_->{s_file});
1316                 my $name = $p{getstring}->($_->{s_name});
1317                 $name = "$file:$name"
1318                         if length $file;
1319                 $_->{debugname} = $name;
1320
1321                 next
1322                         if $_->{first_statement} < 0;
1323
1324                 my %statements = ();
1325                 my %come_from = ();
1326                 my %go_to = ();
1327                 my %globals_read = ();
1328                 my %globals_written = ();
1329                 my %globals_used = ();
1330
1331                 if($_->{first_statement} >= 0)
1332                 {
1333                         run_nfa \%p, $_->{first_statement}, "", id, nfa_default_state_checker,
1334                                 sub
1335                                 {
1336                                         my ($ip, $state, $s, $c) = @_;
1337                                         ++$statements{$ip};
1338
1339                                         if(my $j = $c->{isjump})
1340                                         {
1341                                                 my $t = $ip + $s->{$j};
1342                                                 $come_from{$t}{$ip} = $c->{isconditional};
1343                                                 $go_to{$ip}{$t} = $c->{isconditional};
1344                                         }
1345
1346                                         for my $o(qw(a b c))
1347                                         {
1348                                                 my $type = $c->{$o}
1349                                                         or next;
1350                                                 my $ofs = $s->{$o};
1351
1352                                                 my $read = sub
1353                                                 {
1354                                                         my ($ofs) = @_;
1355                                                         $globals_read{$ofs}{$ip}{$o} = 1;
1356                                                         $globals_used{$ofs} = 1;
1357                                                 };
1358                                                 my $write = sub
1359                                                 {
1360                                                         my ($ofs) = @_;
1361                                                         $globals_written{$ofs}{$ip}{$o} = 1;
1362                                                         $globals_used{$ofs} = 1;
1363                                                 };
1364
1365                                                 if($type eq 'inglobal' || $type eq 'inglobalfunc')
1366                                                 {
1367                                                         $read->($ofs);
1368                                                 }
1369                                                 elsif($type eq 'inglobalvec')
1370                                                 {
1371                                                         $read->($ofs);
1372                                                         $read->($ofs+1);
1373                                                         $read->($ofs+2);
1374                                                 }
1375                                                 elsif($type eq 'outglobal')
1376                                                 {
1377                                                         $write->($ofs);
1378                                                 }
1379                                                 elsif($type eq 'outglobalvec')
1380                                                 {
1381                                                         $write->($ofs);
1382                                                         $write->($ofs+1);
1383                                                         $write->($ofs+2);
1384                                                 }
1385                                         }
1386
1387                                         return 0;
1388                                 };
1389                 }
1390
1391                 $_->{statements} = \%statements;
1392                 $_->{come_from} = \%come_from;
1393                 $_->{go_to} = \%go_to;
1394                 $_->{globals_read} = \%globals_read;
1395                 $_->{globals_written} = \%globals_written;
1396                 $_->{globals_used} = \%globals_used;
1397
1398                 # using this info, we could now identify basic blocks
1399         }
1400
1401         print STDERR "Detecting constants and temps, and naming...\n";
1402         detect_constants \%p;
1403
1404         if($ENV{DUMP})
1405         {
1406                 use Data::Dumper;
1407                 print Dumper \%p;
1408                 return;
1409         }
1410
1411         # what do we want to do?
1412         my $checkfunc = \&find_uninitialized_locals;
1413         if($ENV{DISASSEMBLE})
1414         {
1415                 $checkfunc = \&disassemble_function;
1416         }
1417         for(sort { $a->{debugname} cmp $b->{debugname} } @{$p{functions}})
1418         {
1419                 $checkfunc->(\%p, $_);
1420         }
1421 }
1422
1423 open my $fh, '<', $ARGV[0];
1424 parse_progs $fh;