File Coverage

lib/SIRTX/VM/Opcode.pm
Criterion Covered Total %
statement 23 387 5.9
branch 0 298 0.0
condition 0 307 0.0
subroutine 8 22 36.3
pod 11 11 100.0
total 42 1025 4.1


line stmt bran cond sub pod time code
1             # Copyright (c) 2025-2026 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: module for single SIRTX VM opcodes
6              
7              
8             package SIRTX::VM::Opcode;
9              
10 1     1   1388 use v5.16;
  1         4  
11 1     1   6 use strict;
  1         2  
  1         27  
12 1     1   5 use warnings;
  1         2  
  1         46  
13              
14 1     1   4 use Carp;
  1         3  
  1         72  
15 1     1   6 use Scalar::Util qw(looks_like_number);
  1         18  
  1         55  
16              
17 1     1   6 use SIRTX::VM::RegisterFile;
  1         2  
  1         36  
18 1     1   715 use SIRTX::VM::Assembler;
  1         4  
  1         57  
19              
20 1     1   10 use parent 'Data::Identifier::Interface::Userdata';
  1         2  
  1         9  
21              
22             our $VERSION = v0.12;
23              
24             my %_die_raen = (code => 0, P => 7, codeX => 0, S => 2, T => 4+1);
25              
26             my %_raes_to_raen = (
27             NONE => 0,
28             NOENT => 2,
29             NOSYS => 6,
30             NOTSUP => 7,
31             NOMEM => 12,
32             INVAL => 13,
33             FAULT => 18,
34             IO => 19,
35             NODATA => 25,
36             NOSPC => 38,
37             TYPEMM => 39,
38             RO => 45,
39             ILLSEQ => 56,
40             BADEXEC => 79,
41             BADFH => 83,
42             );
43              
44             our %_logicals_to_sni = (
45             sni => 10,
46             sid => 115,
47             raen => 116,
48             chat0w => 118,
49             uuid => 119,
50             uri => 121,
51             asciicp => 122,
52             oid => 120,
53             wd => 123,
54             logical => 129,
55             false => 189,
56             true => 190,
57             ac => 298,
58             accumulator => 298,
59             );
60             my %_sni_to_logicals = reverse %_logicals_to_sni;
61              
62             my %_logicals_to_sid = (
63             asi => 1,
64             tagname => 3,
65             SEEK_SET => 34,
66             SEEK_CUR => 35,
67             SEEK_END => 36,
68             backwards => 43,
69             forwards => 44,
70             black => 61,
71             white => 62,
72             grey => 63,
73             red => 119,
74             green => 120,
75             blue => 121,
76             cyan => 122,
77             magenta => 123,
78             yellow => 124,
79             orange => 125,
80             gtin => 160,
81             left => 192,
82             right => 193,
83             up => 194,
84             down => 195,
85             north => 208,
86             east => 209,
87             south => 210,
88             west => 211,
89             );
90              
91             my %_compare_flags = (
92             with => 0x0000, # no-op
93             icase => 0x0001,
94             asciz => 0x0008,
95             nulls_distinct => 0x0080,
96             nulls_equal => 0x0040,
97             nulls_first => 0x0010,
98             nulls_last => 0x0020,
99             prefix => 0x0002,
100             suffix => 0x0004,
101             seekback_end => 0x0400,
102             seekback_start => 0x0200,
103             subject => 0x0100,
104             );
105              
106             my %_logicals = (
107             (map {$_ => 'sni:'.$_logicals_to_sni{$_}} keys %_logicals_to_sni),
108             (map {$_ => 'sid:'.$_logicals_to_sid{$_}} keys %_logicals_to_sid),
109             );
110              
111             my @_simple_0 = ();
112             my @_simple_1 = (reg => 'P');
113             my @_simple_2 = (reg => 'P', reg => 'T');
114             my @_simple_3 = (reg => 'P', reg => 'S', reg => 'T');
115              
116             my %_simple_opcodes = (
117             noop => [\@_simple_0 => {first => 0, second => 0, T => 0}],
118             magic => [\@_simple_0 => {first => 0, codeX => 0, S => 0, T => 4+3, extra => "VM\r\n\xc0\n"}],
119             autodie => [\@_simple_0 => {code => 0, P => 7, codeX => 0, S => 2, T => 4+0}],
120             data_start_marker => [\@_simple_0 => {code => 0, P => 6, codeX => 0, S => 0, T => 0}],
121              
122             filesize => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 0, T => 0+1}],
123             section_pointer => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 1, T => 0+1}],
124             minimum_handles => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 2, T => 0+1}],
125             minimum_memory => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 3, T => 0+1}],
126             text_boundary => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 4, T => 0+1}],
127             load_boundary => [[int_half => 'extra[]'] => {code => 0, P => 1, codeX => 0, S => 5, T => 0+1}],
128              
129             rjump => [[int => 'extra[]'] => {code => 0, P => 7, codeX => 0, S => 4, T => 0+1}],
130              
131             unref => [\@_simple_1 => {code => 0, codeX => 1, S => 0, T => 0+0}],
132             rewind => [\@_simple_1 => {code => 0, codeX => 1, S => 1, T => 0+0}],
133             die => [\@_simple_1 => {code => 0, codeX => 1, S => 2, T => 4+0}],
134             exit => [\@_simple_1 => {code => 0, codeX => 1, S => 3, T => 0+0}],
135             #jump => {code => 0, codeX => 1, S => 4, T => 0+0},
136             rcall => [[int => 'extra[]'] => {code => 0, codeX => 1, S => 5, T => 0+1}],
137             trcall => [[int => 'extra[]'] => {code => 0, codeX => 1, S => 5, T => 4+1}],
138             open_context => [\@_simple_1 => {code => 1, codeX => 1, S => 7, T => 0+0}],
139              
140             replace => [\@_simple_2 => {code => 0, codeX => 2, S => 0}],
141             move => [\@_simple_2 => {code => 0, codeX => 2, S => 1}],
142             seek => [\@_simple_2 => {code => 0, codeX => 2, S => 2}],
143             tell => [\@_simple_2 => {code => 0, codeX => 2, S => 3}],
144             transfer => [\@_simple_2 => {code => 0, codeX => 2, S => 4}],
145              
146             return => [\@_simple_0 => {code => 0, P => 7, codeX => 0, S => 2, T => 0+0},
147             \@_simple_1 => {code => 0, codeX => 1, S => 2, T => 0+0}],
148              
149             contents => [\@_simple_2 => {code => 1, codeX => 2, S => 1}],
150             die => [['raen:' => 'extra[]'] => \%_die_raen],
151             substr => [[reg => 'P', reg => 'T', int => 'extra[]', int => 'extra[]'] => {code => 1, codeX => 2, S => (0+3)}],
152             open_function => [[reg => 'P', int_rel4 => 'extra[]'] => {code => 1, codeX => 1, S => 6, T => 0+1}],
153             relations => [[reg => 'P', reg => 'S', reg => 'T', undef => 'undef'] => {code => 4, codeX => 0},
154             [reg => 'P', undef => 'undef', reg => 'T', reg => 'S'] => {code => 4, codeX => 1}],
155             metadata => [\@_simple_3 => {code => 4, codeX => 2}],
156             control => [[reg => 'P', reg => 'T'] => {code => 1, codeX => 2, S => 0+0},
157             [reg => 'P', 'sni:' => 'extra[]', reg => 'T'] => {code => 1, codeX => 2, S => 4+0},
158             [reg => 'P', 'sni:' => 'extra[]'] => {code => 1, codeX => 1, S => 0, T => 0+1},
159             [reg => 'P', 'sni:' => 'extra[]', 'sni:' => 'extra[]'] => {code => 1, codeX => 1, S => 0, T => 0+2},
160             [reg => 'P', 'sni:' => 'extra[]', int => 'extra[]'] => {code => 1, codeX => 1, S => 0, T => 4+2},
161             [reg => 'P', reg => 'S', reg => 'T'] => {code => 1, codeX => 3}],
162              
163             open => [[reg => 'P', 'sni:' => 'extra[]'] => {code => 0, codeX => 1, S => 6, T => 0+1},
164             [reg => 'P', '"ns"' => 'undef', int => 'extra[]'] => {code => 0, codeX => 1, S => 6, T => 4+1},
165             [reg => 'P', '"ns"' => 'undef', int => 'extra[]', int => 'extra[]'] => {code => 0, codeX => 1, S => 6, T => 4+2}],
166             byte_transfer => [[reg => 'P', reg => 'T', int => 'extra[]', autodie => 'false'] => {code => 1, codeX => 2, S => 4+2},
167             [reg => 'P', reg => 'T', int => 'extra[]', autodie => 'true'] => {code => 1, codeX => 2, S => 4+1}],
168             call => [[reg => 'P', reg => 'T', autodie => 'false'] => {code => 0, codeX => 2, S => 5},
169             [reg => 'P', reg => 'T', autodie => 'true'] => {code => 0, codeX => 2, S => 6}],
170             jump => [[int_rel4 => 'extra[]'] => {code => 0, P => 7, codeX => 0, S => 4, T => 0+1}],
171              
172             '.not_implemented' => [\@_simple_0 => {%_die_raen, extra => [$_raes_to_raen{NOSYS}]}],
173             '.bug' => [\@_simple_0 => {%_die_raen, extra => [$_raes_to_raen{ILLSEQ}]}],
174             );
175             $_simple_opcodes{nop} = $_simple_opcodes{noop}; # alias
176              
177             my @_opcode_to_text;
178              
179             my %_synthetic = (
180             open => [
181             [reg => 1, undef => 'undef'] => ['unref', \1],
182             [reg => 1, '"false"' => 'undef'] => ['open', \1, 'sni:189'],
183             [reg => 1, '"true"' => 'undef'] => ['open', \1, 'sni:190'],
184             ],
185             add => [['"out"' => 'undef', reg => 1, reg => 2] => ['control', \1, 'sni:81', \2]],
186             sub => [['"out"' => 'undef', reg => 1, reg => 2] => ['control', \1, 'sni:82', \2]],
187             div => [['"out"' => 'undef', reg => 1, reg => 2] => ['control', \1, 'sni:83', \2]],
188             mod => [['"out"' => 'undef', reg => 1, reg => 2] => ['control', \1, 'sni:84', \2]],
189             jump => [[reg => 1] => ['seek', 'program_text', \1]],
190             return => [[undef => 'undef'] => ['return']],
191             control => [[any => 1, any => 2, any => 3, '"arg"' => 'undef'] => ['control', \1, \2, \3]],
192             push => [[reg => 1, reg => 2] => ['control', \1, 'sni:180', \2]],
193             pop => [
194             ['"out"' => 1, reg => 2] => ['control', \2, 'sni:181'],
195             ['"undef"' => 1, reg => 2] => ['control', \2, 'sni:181'], # alias
196             ],
197             setvalue => [[reg => 1, reg => 2, '"arg"' => 3] => ['control', \1, 'sni:102', \2, \3]],
198             getvalue => [['"out"' => 1, reg => 2, reg => 3] => ['control', \2, 'sni:101', \3]],
199             read_character => [['"out"' => 'undef', reg => 1] => ['control', \1, 'sni:282']],
200             write_character => [
201             [reg => 1, reg => 2] => ['control', \1, 'sni:283', \2],
202             [reg => 1, int => 2] => ['control', \1, 'sni:283', \2],
203             ],
204             read_be8 => [['"out"' => 'undef', reg => 1] => ['control', \1, 'sni:143', '8']],
205             read_be16 => [['"out"' => 'undef', reg => 1] => ['control', \1, 'sni:143', '16']],
206             );
207              
208              
209             sub new {
210 0     0 1   my ($pkg, %opts) = @_;
211 0           my $self = bless({}, $pkg);
212              
213 0           foreach my $key (qw(first second code codeX ST P S T size pos)) {
214 0   0       my $val = delete $opts{$key} // next;
215 0 0         unless (looks_like_number($val)) {
216 0           croak 'Invalid argument: '.$key.' is not a number';
217             }
218 0           $self->{$key} = int($val);
219             }
220              
221 0           $self->{extra} = delete $opts{extra};
222              
223             # TODO: Implement more checks here.
224              
225 0 0         croak 'Stray options passed' if scalar keys %opts;
226              
227 0           return $self;
228             }
229              
230              
231             sub from_template {
232 0     0 1   my ($pkg, %opts) = @_;
233 0           my $parts = delete $opts{parts};
234 0           my $asm = delete $opts{assembler};
235 0           my $size = delete $opts{size};
236 0           my $line = delete $opts{line};
237 0           my $out = delete $opts{out};
238 0           my $autodie = delete $opts{autodie};
239 0           my ($cmd, @args) = @{$parts};
  0            
240              
241 0 0         croak 'Stray options passed' if scalar keys %opts;
242              
243 0 0         if (defined(my $entry = $_synthetic{$cmd})) {
244             outer:
245 0           for (my $i = 0; $i < scalar(@{$entry}); $i += 2) {
  0            
246 0           my @argmap = @{$entry->[$i]};
  0            
247 0           my %updates;
248              
249 0 0         next unless (scalar(@args)*2) == scalar(@argmap);
250              
251 0           for (my $j = 0; ($j*2) < scalar(@argmap); $j++) {
252 0           my $type = $argmap[$j*2 + 0];
253 0           my $dst = $argmap[$j*2 + 1];
254 0           my $val = $args[$j];
255              
256 0 0         if ($val =~ /^raes:(.+)$/) {
    0          
257 0 0         if (defined(my $raen = $_raes_to_raen{uc($1)})) {
258 0           $val = 'raen:'.$raen;
259             }
260             } elsif ($val =~ /^logical:(.+)$/) {
261 0 0         if (defined(my $logical = $_logicals{$1})) {
262 0           $val = $logical;
263             }
264             }
265              
266 0 0         if ($type =~ /^".+"$/) {
    0          
267 0 0         next outer if $val ne $asm->_parse_string($type);
268             } elsif ($type eq 'any') {
269             # no-op
270             } else {
271 0 0         next outer if $asm->_get_value_type($val) ne $type;
272             }
273              
274 0 0         if ($dst eq 'undef') {
275             # ignore this value
276             } else {
277 0           $updates{$dst} = $val;
278             }
279             }
280              
281 0 0         ($cmd, @args) = map {ref ? $updates{${$_}} : $_} @{$entry->[$i+1]};
  0            
  0            
  0            
282             }
283             }
284              
285             # replace logics:
286 0           foreach my $arg (@args) {
287 0 0         if ($arg =~ /^logical:(.+)$/) {
288 0 0         if (defined(my $logical = $_logicals{$1})) {
289 0           $arg = $logical;
290             }
291             }
292             }
293              
294 0 0         if (defined(my $entry = $_simple_opcodes{$cmd})) {
295             outer:
296 0           for (my $i = 0; $i < scalar(@{$entry}); $i += 2) {
  0            
297 0           my @argmap = @{$entry->[$i]};
  0            
298 0           my %updates;
299             my $last_data;
300 0           my $reset_autodie;
301              
302 0 0 0       if (scalar(@argmap) >= 2 && $argmap[-2] eq 'autodie') {
303 0 0         next unless ((scalar(@args) + 1)*2) == scalar(@argmap);
304             } else {
305 0 0         next unless ((scalar(@args) + 0)*2) == scalar(@argmap);
306             }
307              
308 0           for (my $j = 0; ($j*2) < scalar(@argmap); $j++) {
309 0           my $type = $argmap[$j*2 + 0];
310 0           my $dst = $argmap[$j*2 + 1];
311 0           my $val = $args[$j];
312 0           my $data;
313 0           my $mods = '';
314              
315 0 0         if ($type =~ s/_(.+)$//) {
316 0           $mods = $1;
317             }
318              
319 0 0 0       if ($type ne 'autodie' && $val =~ /^raes:(.+)$/) {
320 0 0         if (defined(my $raen = $_raes_to_raen{uc($1)})) {
321 0           $val = 'raen:'.$raen;
322             }
323             }
324              
325 0 0         if ($type =~ /^".+"$/) {
    0          
326 0 0         next outer if $val ne $asm->_parse_string($type);
327             } elsif ($type eq 'autodie') {
328 0 0         next outer if !defined($autodie);
329             } else {
330 0 0         next outer if $asm->_get_value_type($val) ne $type;
331             }
332              
333 0 0         if ($type eq 'reg') {
    0          
    0          
    0          
    0          
    0          
334 0           $data = $asm->_force_mapped($val);
335             } elsif ($type eq 'int') {
336 0 0         if ($mods eq 'rel4') {
    0          
337 0           my $org = $out->tell + 4;
338 0           $data = $asm->_parse_int($val, $org) - $org;
339 0 0         if ($data & 1) {
340 0           croak sprintf('Bad offset: line %s: offset %i', $line, $data);
341             }
342 0           $data /= 2;
343             } elsif ($mods eq 'half') {
344 0           $data = $asm->_parse_int($val, $last_data);
345 0 0         if ($data & 1) {
346 0           croak sprintf('Bad value: line %s: value %i', $line, $data);
347             }
348 0           $data /= 2;
349             } else {
350 0           $data = $asm->_parse_int($val, $last_data);
351             }
352             } elsif ($type =~ /^[a-z]+:$/) {
353 0           (undef, $data) = $asm->_parse_id($val);
354             } elsif ($type eq 'undef') {
355 0           $data = undef;
356             } elsif ($type =~ /^".+"$/) {
357 0           $data = $val;
358             } elsif ($type eq 'autodie') {
359 0           $reset_autodie = $asm->_parse_bool($dst);
360 0 0 0       next outer if (${$autodie} xor $reset_autodie);
  0            
361 0           next;
362             } else {
363 0           croak 'BUG: Unsupported type: '.$type;
364 0           next outer;
365             }
366              
367 0 0         if ($dst eq 'extra[]') {
    0          
368 0   0       push(@{$updates{extra} //= []}, $data);
  0            
369             } elsif ($dst eq 'undef') {
370             # ignore this value
371             } else {
372 0           $updates{$dst} = $data;
373             }
374              
375 0           $last_data = $data;
376             }
377              
378 0 0 0       ${$autodie} = undef if defined($autodie) && $reset_autodie;
  0            
379 0           return $pkg->new(%{$entry->[$i+1]}, %updates, size => $size);
  0            
380             }
381             }
382              
383 0 0 0       if ($cmd eq 'compare' && scalar(@args) >= 3 && $args[0] eq 'out') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
384 0           my @flags = @args[3..$#args];
385 0           my $flags = 0;
386              
387 0           foreach my $flag (@flags) {
388 0 0         if (defined(my $v = $_compare_flags{$flag})) {
389 0           $flags |= $v;
390             } else {
391 0           croak sprintf('Unsupported compare flag: line %s: flag %s', $line, $flag);
392             }
393             }
394              
395 0           return $pkg->new(code => 3, P => $asm->_force_mapped($args[1]), codeX => 2, S => 0, T => $asm->_force_mapped($args[2]), extra => [$flags]);
396             } elsif ($cmd eq 'open' && scalar(@args) == 2 && $asm->_get_value_type($args[0]) eq 'reg' && $asm->_get_value_type($args[1]) eq 'int') {
397 0           my $num = $asm->_parse_int($args[1]);
398 0 0 0       if (($num >= 0 && $num <= 7) && (!defined($size) || $size != 4)) {
      0        
      0        
399 0           return $pkg->new(code => 0, P => $asm->_force_mapped($args[0]), codeX => 2, S => 7, T => $num);
400             } else {
401 0           return $pkg->new(code => 0, P => $asm->_force_mapped($args[0]), codeX => 1, S => 7, T => 0+1, extra => [$num]);
402             }
403             } elsif ($cmd eq 'open' && scalar(@args) == 2 && $asm->_get_value_type($args[0]) eq 'reg' && $asm->_get_value_type($args[1]) =~ /:$/) {
404 0           my ($type, $num) = $asm->_parse_id($args[1]);
405 0           my $sni = $asm->_type_to_sni($type);
406 0           return $pkg->new(code => 0, P => $asm->_force_mapped($args[0]), codeX => 1, S => 6, T => 0+2, extra => [$sni, $num], size => $size);
407             } elsif ($cmd eq 'jump' && scalar(@args) >= 3 && $asm->_get_value_type($args[0]) eq 'int' && ($args[1] eq 'if' || $args[1] eq 'unless')) {
408 0           my $org = $out->tell + 4;
409 0           my $extra = $asm->_parse_int($args[0], $org) - $org;
410 0           my @cond = @args[2..$#args];
411 0           my $P = 0;
412 0           my $S = 0;
413 0           my $T = 0;
414              
415 0 0         if ($extra & 1) {
416 0           croak sprintf('Bad offset: line %s: offset %i', $line, $extra);
417             }
418 0           $extra /= 2;
419              
420 0 0         if ($args[1] eq 'if') {
    0          
421             # no-op
422             } elsif ($args[1] eq 'unless') {
423 0           $P |= 0x1;
424             }
425              
426 0           while (scalar(@cond) >= 3) {
427 0           my $reg = shift(@cond);
428 0           my $op = shift(@cond);
429 0           my $val = shift(@cond);
430              
431 0 0         croak sprintf('Unsupported jump syntax: line %s: register %s', $line, $reg) unless $reg eq 'out';
432              
433 0 0 0       if ($op eq 'is') {
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
434 0 0 0       if ($val eq 'valid') {
    0 0        
    0 0        
435 0           $S |= 0x1;
436             } elsif ($val eq 'true') {
437 0           $S |= 0x2;
438             } elsif ($val eq 'notfine' || $val eq 'bad' || $val eq 'dog' || $val eq 'hotdog') {
439 0           $S |= 0x4;
440             } else {
441 0           croak sprintf('Unsupported jump syntax: line %s: is-value %s', $line, $val);
442             }
443             } elsif (($op eq '<' && $val eq '0') || ($op eq '<=' && $val eq '-1')) {
444 0           $T |= 0x1;
445             } elsif ($op eq '==' && $val eq '0') {
446 0           $T |= 0x2;
447             } elsif (($op eq '>' && $val eq '0') || ($op eq '>=' && $val =~ /^\+?1$/)) {
448 0           $T |= 0x4;
449             } else {
450 0           croak sprintf('Unsupported jump syntax: line %s: operator/value %s %s', $line, $op, $val);
451             }
452              
453 0 0 0       shift(@cond) if scalar(@cond) && $cond[0] eq 'or';
454             }
455              
456 0 0         if (scalar @cond) {
457 0           croak sprintf('Unsupported jump syntax: line %s: condition %s', $line, join(' ', @cond));
458             }
459              
460 0           return $pkg->new(code => 3, P => $P, codeX => 0, S => $S, T => $T, extra => [$extra]);
461             } elsif ($cmd eq 'noop' && scalar(@args) == 1 && $asm->_get_value_type($args[0]) eq 'string') {
462 0           my $string = $asm->_parse_string($args[0]);
463 0           my $l = length($string);
464              
465 0 0 0       if ($l > 6 || ($l & 1)) {
466 0           croak sprintf('Unsupported noop with data of invalid length: line %s: length %u', $line, $l);
467             }
468              
469 0           return $pkg->new(first => 0, codeX => 0, S => 0, T => ($l/2), extra => $string);
470             } elsif ($cmd eq 'execution_info_flags') {
471 0           my $flags = 0;
472              
473 0           foreach my $flag (@args) {
474 0 0         if ($flag eq 'resources_only') {
    0          
    0          
475 0           $flags |= 1<<15;
476             } elsif ($flag eq 'single_load') {
477 0           $flags |= 1<<14;
478             } elsif ($flag eq 'multi_session') {
479 0           $flags |= (1<<13) | (1<<14);
480             } else {
481 0           croak sprintf('Unsupported/unknown execution_info_flags flag: line %s: flag %s', $line, $flag);
482             }
483             }
484              
485 0           return $pkg->new(code => 0, P => 3, codeX => 0, S => 0, T => 0+1, extra => [$flags]);
486             }
487              
488 0           croak 'Unsupported template';
489             }
490              
491              
492             sub read {
493 0     0 1   my ($pkg, $fh, @opts) = @_;
494 0           my $data;
495             my $pos;
496              
497 0 0         croak 'Stray options passed' if scalar @opts;
498              
499 0           $pos = $fh->tell;
500              
501 0 0 0       if (defined($pos) && ($pos & 1)) {
502 0           croak 'Invalid aligned opcode read';
503             }
504              
505 0 0         croak 'Cannot read opcode' unless $fh->read($data, 2) == 2;
506              
507             {
508 0           my ($first, $second) = unpack('CC', $data);
  0            
509 0           my $code = ($first & 0370) >> 3;
510 0           my $P = ($first & 0007) >> 0;
511 0           my $codeX = ($second & 0300) >> 6;
512 0           my $S = ($second & 0070) >> 3;
513 0           my $T = ($second & 0007) >> 0;
514              
515 0           my $registers;
516             my $extra_len;
517 0           my $extra;
518              
519 0 0         if ($code <= 3) {
    0          
520 0           $registers = $codeX;
521             } elsif ($code == 4) {
522 0           $registers = 3;
523             } else {
524 0           croak sprintf('Unsupported/invalid opcode: 0x%02x%02x: Number of registers is undefined/unknown', $first, $second);
525             }
526              
527 0 0 0       if ($code <= 1 && $codeX <= 1) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
528 0           $extra_len = $T & 3;
529             } elsif ($code <= 1 && $codeX == 3) {
530 0           $extra_len = 0;
531             } elsif ($code == 1 && $codeX == 2 && ($S & 4) == 4) {
532 0           $extra_len = 1;
533             } elsif ($code == 1 && $codeX == 2 && $S == (0 + 3)) {
534 0           $extra_len = 2;
535             } elsif ($code == 1 && $codeX == 2 && $S == (0 + 1)) {
536 0           $extra_len = 0;
537             } elsif ($code == 0 && $codeX == 2) {
538 0           $extra_len = 0;
539             } elsif ($code == 3) {
540 0           $extra_len = 1;
541             } elsif ($code == 4) {
542 0           $extra_len = 0;
543             } else {
544 0           croak sprintf('Unsupported/invalid opcode: 0x%02x%02x: Size of extra is undefined/unknown', $first, $second);
545             }
546              
547             #warn sprintf('registers: %u, extra: %u', $registers, $extra_len);
548              
549 0 0         if ($extra_len) {
550 0           $extra_len *= 2;
551              
552 0 0         croak 'Cannot read extra' unless $fh->read($extra, $extra_len) == $extra_len;
553             }
554              
555 0           return $pkg->new(first => $first, second => $second, code => $code, codeX => $codeX, P => $P, S => $S, T => $T, extra => $extra, pos => $pos);
556             }
557              
558 0           croak 'Cannot parse opcode';
559             }
560              
561              
562             sub write {
563 0     0 1   my ($self, $out, @opts) = @_;
564 0           my $required;
565             my $size;
566              
567 0 0         croak 'Stray options passed' if scalar @opts;
568              
569 0           $required = $self->required_size;
570              
571 0 0         if (defined $self->{size}) {
572 0           $size = $self->{size};
573             } else {
574 0           $size = $required;
575             }
576              
577 0 0         if ($required > $size) {
    0          
578 0           croak sprintf('Opcode does not fit in allocated size: required %u, have %u', $required, $size);
579             } elsif ($required < $size) {
580 0           my $diff = $size - $required;
581 0 0         croak 'Opcode padding alignment error' if $diff & 1;
582 0           print $out chr(0) x $diff;
583             }
584              
585 0           print $out chr($self->{first}), chr($self->{second});
586              
587 0 0         if (ref $self->{extra}) {
    0          
588 0           print $out pack('n*', @{$self->{extra}});
  0            
589             } elsif (defined $self->{extra}) {
590 0 0         $self->{extra} .= chr(0) if length($self->{extra}) & 1;
591 0           print $out $self->{extra};
592             }
593             }
594              
595              
596             sub required_alignment {
597 0     0 1   my ($self, @opts) = @_;
598              
599 0 0         croak 'Stray options passed' if scalar @opts;
600              
601 0           return 2;
602             }
603              
604              
605             sub new_alignment {
606 0     0 1   my ($self, @opts) = @_;
607              
608 0 0         croak 'Stray options passed' if scalar @opts;
609              
610 0           return 2;
611             }
612              
613              
614             sub required_size {
615 0     0 1   my ($self, @opts) = @_;
616 0           my $required;
617              
618 0 0         croak 'Stray options passed' if scalar @opts;
619              
620 0   0       $self->{first} //= ($self->{code} << 3) | $self->{P};
621 0 0 0       $self->{ST} //= ($self->{S} << 3) | $self->{T} unless defined $self->{second};
622 0   0       $self->{second} //= ($self->{codeX} << 6) | $self->{ST};
623              
624 0           $required = 2;
625 0 0         if (ref $self->{extra}) {
    0          
626 0           $required += 2 * scalar(@{$self->{extra}});
  0            
627             } elsif (defined $self->{extra}) {
628 0           my $l = length($self->{extra});
629 0 0         $l++ if $l & 1;
630 0           $required += $l;
631             }
632              
633 0           return $required;
634             }
635              
636              
637             sub is_return {
638 0     0 1   my ($self, @opts) = @_;
639              
640 0 0         croak 'Stray options passed' if scalar @opts;
641              
642 0           $self->required_size;
643              
644 0 0 0       if ($self->{first} == 0x07 && $self->{codeX} == 0 && $self->{S} == 2) {
    0 0        
      0        
645 0           my $T = $self->{T};
646 0   0       return $T == 0 || $T == (4+1);
647             } elsif ($self->{code} == 0 && $self->{codeX} == 1) {
648 0           my $S = $self->{S};
649 0   0       return $S == 2 || $S == 3;
650             }
651              
652 0           return undef;
653             }
654              
655              
656             sub is_autodie {
657 0     0 1   my ($self, @opts) = @_;
658              
659 0 0         croak 'Stray options passed' if scalar @opts;
660              
661 0           $self->required_size;
662              
663 0   0       return $self->{first} == 0x07 && $self->{second} == 0x14;
664             }
665              
666              
667             sub is_end_of_text {
668 0     0 1   my ($self, @opts) = @_;
669              
670 0 0         croak 'Stray options passed' if scalar @opts;
671              
672 0           $self->required_size;
673              
674 0 0 0       if ($self->{first} == 0x06 && $self->{codeX} == 0) {
    0 0        
      0        
      0        
675 0           my $S = $self->{S};
676 0   0       return $S == 0 || $S == 7;
677             } elsif ($self->{first} == 0x00 && $self->{codeX} == 0 && $self->{S} == 3 && ($self->{T} & 4)) {
678 0           return 1;
679             }
680              
681 0           return undef;
682             }
683              
684              
685             sub as_text {
686 0     0 1   my ($self, @opts) = @_;
687 0           my $name = eval {$self->_name};
  0            
688 0           my $opcode_to_text = $self->{opcode_to_text};
689 0   0       my $extra = $self->{extra} // '';
690 0           my $command;
691              
692 0 0         croak 'Stray options passed' if scalar @opts;
693              
694 0 0         if (defined $opcode_to_text) {
695 0 0         if (scalar(@{$opcode_to_text->{argmap}}) == 0) {
  0            
696 0           $command = $name;
697             } else {
698 0           my @argmap = @{$opcode_to_text->{argmap}};
  0            
699 0           my @extra = unpack('n*', $extra);
700              
701 0           $command = $name;
702              
703 0           for (my $j = 0; ($j*2) < scalar(@argmap); $j++) {
704 0           my $type = $argmap[$j*2 + 0];
705 0           my $dst = $argmap[$j*2 + 1];
706              
707 0 0 0       if ($type eq 'reg') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
708 0           $command .= sprintf(' r%u', $self->{$dst});
709             } elsif ($type =~ /^(?:sni|raen):$/ && $dst eq 'extra[]') {
710 0           $command .= sprintf(' %s%u', $type, shift(@extra));
711             } elsif ($type eq 'int_half' && $dst eq 'extra[]') {
712 0           $command .= sprintf(' %u', shift(@extra)*2);
713             } elsif ($type eq 'int_rel4' && $dst eq 'extra[]') {
714 0           $command .= sprintf(' %u', shift(@extra)*2 + $self->{pos} + 4);
715             } elsif ($type eq 'int' && $dst eq 'extra[]') {
716 0           $command .= sprintf(' %u', shift(@extra));
717             } elsif ($type eq 'undef' && $dst eq 'undef') {
718 0           $command .= ' undef';
719             } elsif ($type =~ /^"([0-9a-zA-Z]+)"$/ && $dst eq 'undef') {
720 0           $command .= ' '.$1;
721             } elsif ($type eq 'autodie' && $dst eq 'true') {
722 0           $command =~ s/^(\S+)(\s?)/$1!$2/;
723             } elsif ($type eq 'autodie' && $dst eq 'false') {
724 0           $command =~ s/^(\S+)(\s?)/$1?$2/;
725             } else {
726 0           $command = undef;
727 0           last;
728             }
729             }
730             }
731             } else {
732 0           my $first = $self->{first};
733 0           my $second = $self->{second};
734 0           my $code = ($first & 0370) >> 3;
735 0           my $P = ($first & 0007) >> 0;
736 0           my $codeX = ($second & 0300) >> 6;
737 0           my $S = ($second & 0070) >> 3;
738 0           my $T = ($second & 0007) >> 0;
739              
740 0 0 0       if ($code == 0 && $codeX == 3 && defined(my $regname = SIRTX::VM::RegisterFile->_physical_name_by_number($second & 0077))) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
741 0           $command = sprintf('map r%u %s', $P, $regname);
742             } elsif ($first == 0 && $codeX == 0 && $S > 0 && $T == 0) {
743 0   0       $command = sprintf('.endsection ; %s', $SIRTX::VM::Assembler::_header_ids_rev{$S} // '???');
744             } elsif ($first == 0 && $codeX == 0 && $S > 0 && $T == 4) {
745 0   0       $command = sprintf('.section %s', $SIRTX::VM::Assembler::_header_ids_rev{$S} // '???');
746             } elsif ($first == 0 && $codeX == 0 && $S > 0 && ($T & 4)) {
747 0   0       $command = sprintf('.section %s %s', $SIRTX::VM::Assembler::_header_ids_rev{$S} // '???', _escape_text($extra));
748             } elsif ($first == 0 && $codeX == 0 && $S == 0 && $T > 0 && $T < 4) {
749 0           $command = sprintf('noop %s', _escape_text($extra));
750             } elsif ($code == 0 && $codeX == 2 && $S == 7) {
751 0           $command = sprintf('open r%u %u', $P, $T);
752             } elsif ($code == 0 && $codeX == 1 && $S == 6 && $T == (0+2)) {
753 0           my ($sni, $id) = unpack('nn', $extra);
754 0   0       $sni = $_sni_to_logicals{$sni} // $sni;
755 0           $command = sprintf('open r%u %s:%u', $P, $sni, $id);
756             } elsif ($code == 0 && $codeX == 1 && $S == 7 && $T == (0+1)) {
757 0           $command = sprintf('open r%u %u', $P, unpack('n', $extra));
758             } elsif ($code == 3 && $codeX == 2 && $S == 0) {
759 0           my $flags = unpack('n', $extra);
760 0           $command = sprintf('compare out, r%u r%i', $P, $T);
761 0 0         $command .= ' with' if $flags;
762 0           foreach my $key (sort keys %_compare_flags) {
763 0 0         $command .= ' '.$key if $flags & $_compare_flags{$key};
764             }
765             } elsif ($code == 3 && $codeX == 0) {
766 0           $command = sprintf('jump %u', unpack('n', $extra)*2 + $self->{pos} + 4);
767 0 0         if ($P & 0x01) {
768 0           $command .= ' unless';
769             } else {
770 0           $command .= ' if';
771             }
772              
773 0 0         $command .= ' out is valid or' if $S & 0x01;
774 0 0         $command .= ' out is true or' if $S & 0x02;
775 0 0         $command .= ' out is notfine or' if $S & 0x04;
776              
777 0 0         $command .= ' out < 0 or' if $T & 0x01;
778 0 0         $command .= ' out == 0 or' if $T & 0x02;
779 0 0         $command .= ' out > 0 or' if $T & 0x04;
780              
781 0           $command =~ s/ or$//;
782             }
783             }
784              
785 0 0         unless (defined $command) {
786 0           $command = sprintf('.byte 0x%02x 0x%02x', $self->{first}, $self->{second});
787 0 0         $command .= ', '.join(' ', map {sprintf('0x%02x', ord)} split(//, $self->{extra})) if defined $self->{extra};
  0            
788             }
789              
790             {
791 0 0         my $pos = defined($self->{pos}) ? sprintf('%04x', $self->{pos}) : '????';
  0            
792             $command = sprintf('%-48s ; at 0x%s: code=%2u, P=%u, codeX=%u, S=%u, T=%u, extra=[%s]',
793             $command, $pos,
794             $self->{code}, $self->{P},
795             $self->{codeX}, $self->{S}, $self->{T},
796 0           join(', ', map {sprintf('0x%04x', $_)} unpack('n*', $extra)),
  0            
797             );
798             }
799              
800 0           return $command;
801             }
802              
803             # ---- Private helpers ----
804             sub _name { # TODO: Private for now, might be exposed later on
805 0     0     my ($self, @opts) = @_;
806              
807 0 0         croak 'Stray options passed' if scalar @opts;
808              
809 0 0         return $self->{opcode_to_text}{name} if defined $self->{opcode_to_text};
810              
811             {
812 0           my $code;
  0            
813              
814 0           $self->required_size;
815              
816 0           $code = ($self->{first} << 8) | $self->{second};
817              
818 0           foreach my $entry (@_opcode_to_text) {
819 0 0         if ($entry->{masked_code} == ($code & $entry->{mask})) {
820 0           $self->{opcode_to_text} = $entry;
821 0           return $entry->{name};
822             }
823             }
824             }
825              
826 0           croak 'Unknown/no name for opcode';
827             }
828              
829             sub _extra {
830 0     0     my ($self, @opts) = @_;
831 0           my %res = (type => 'opcode');
832              
833 0 0         croak 'Stray options passed' if scalar @opts;
834              
835 0           $self->required_size;
836              
837 0 0 0       if ($self->{first} == 0x01 && $self->{codeX} == 0 && $self->{S} == 1) {
    0 0        
      0        
      0        
      0        
838 0           my $T = $self->{T};
839              
840 0 0         if ($T == 1) {
841 0           $res{start_offsets} = [unpack('n', $self->{extra})*2];
842             }
843             } elsif ($self->{first} == 0x06 && $self->{codeX} == 0 && $self->{S} == 7 && defined($self->{pos})) {
844 0           my $T = $self->{T};
845 0           my $length;
846              
847 0 0         if ($T == 1) {
    0          
848 0           $length = unpack('n', $self->{extra})*2;
849             } elsif ($T == 2) {
850 0           $length = unpack('N', $self->{extra})*2;
851             }
852              
853 0           $res{start_offsets} = [$length + $self->{pos} + 4];
854 0           $res{length} = $length;
855 0           $res{type} = 'chunk';
856             }
857              
858 0           return %res;
859             }
860              
861             foreach my $key (keys %_simple_opcodes) {
862             my $entry = $_simple_opcodes{$key};
863              
864             next if $key eq 'nop'; # skip alias.
865              
866             for (my $i = 0; $i < scalar(@{$entry}); $i += 2) {
867             my @argmap = @{$entry->[$i]};
868             my $opcode = $entry->[$i + 1];
869             my $first = $opcode->{first};
870             my $second = $opcode->{second};
871             my $code = $opcode->{code};
872             my $codeX = $opcode->{codeX};
873             my $ST = $opcode->{ST};
874             my $P = $opcode->{P};
875             my $S = $opcode->{S};
876             my $T = $opcode->{T};
877             my $mask = 0;
878              
879             for (my $j = 0; ($j*2) < scalar(@argmap); $j++) {
880             my $type = $argmap[$j*2 + 0];
881             my $dst = $argmap[$j*2 + 1];
882              
883             if ($dst eq 'P') {
884             $P = 0;
885             $mask |= 0x0700;
886             } elsif ($dst eq 'S') {
887             $S = 0;
888             $mask |= 0070;
889             } elsif ($dst eq 'T') {
890             $T = 0;
891             $mask |= 0007;
892             }
893             }
894              
895             $first //= ($code << 3) | $P if defined($code) && defined($P);
896             $ST //= ($S << 3) | $T if defined($S) && defined($T);
897             $second //= ($codeX << 6) | $ST if defined($codeX) && defined($ST);
898              
899             $mask ^= 0xFFFF;
900              
901             if (defined($first) && defined($second)) {
902             my $code = ($first << 8) | $second;
903              
904             push(@_opcode_to_text, {
905             masked_code => $code,
906             masked_first => $first,
907             masked_second => $second,
908             mask => $mask,
909             name => $key,
910             argmap => $entry->[$i+0],
911             opcode => $entry->[$i+1],
912             });
913             }
914             }
915             }
916              
917             sub _escape_text {
918 0     0     my ($text) = @_;
919             return '"'.join('',
920             map {
921 0           my $x = ord;
  0            
922 0 0 0       $x >= 0x20 && $x <= 0x7E && $x != 0x5C ? $_ : sprintf('\\x%02x', $x)
923             } split //, $text
924             ).'"';
925             }
926              
927             #warn sprintf('# %04x / %04x => %s', $_->{masked_code}, $_->{mask}, $_->{name}) foreach @_opcode_to_text;
928              
929             1;
930              
931             __END__