File Coverage

lib/SIRTX/VM/Assembler.pm
Criterion Covered Total %
statement 32 701 4.5
branch 0 376 0.0
condition 0 334 0.0
subroutine 11 42 26.1
pod 3 3 100.0
total 46 1456 3.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 assembling SIRTX VM code
6              
7              
8             package SIRTX::VM::Assembler;
9              
10 1     1   13 use v5.16;
  1         4  
11 1     1   7 use strict;
  1         2  
  1         49  
12 1     1   6 use warnings;
  1         2  
  1         64  
13              
14 1     1   6 use Carp;
  1         2  
  1         78  
15 1     1   7 use Encode ();
  1         2  
  1         28  
16 1     1   5 use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);
  1         2  
  1         74  
17 1     1   6 use List::Util qw(uniq);
  1         2  
  1         78  
18              
19 1     1   728 use String::Super;
  1         1492  
  1         48  
20              
21 1     1   7 use SIRTX::VM::RegisterFile;
  1         2  
  1         26  
22 1     1   5 use SIRTX::VM::Opcode;
  1         1  
  1         30  
23              
24 1     1   5 use parent 'Data::Identifier::Interface::Userdata';
  1         2  
  1         7  
25              
26             our $VERSION = v0.12;
27              
28             my %_escapes = (
29             '\\' => '\\',
30             '0' => chr(0x00),
31             'n' => chr(0x0A),
32             'r' => chr(0x0D),
33             't' => chr(0x09),
34             'e' => chr(0x1B),
35             );
36             my %_type_to_sni = %SIRTX::VM::Opcode::_logicals_to_sni; # copy
37             my %_sni_to_type = map {$_type_to_sni{$_} => $_} keys %_type_to_sni;
38              
39             my %_header_ids = (
40             init => 1,
41             header => 2,
42             rodata => 3,
43             text => 4,
44             trailer => 5,
45             resources => 6,
46             );
47              
48             our %_header_ids_rev = reverse %_header_ids;
49              
50             my @_section_order = qw(header init text rodata resources trailer);
51             my @_section_text = qw(header init text);
52             my @_section_load = (@_section_text, qw(rodata resources));
53              
54             my %_disabled_sections = (
55             'resources_only' => {map {$_ => 1} qw(init text)},
56             );
57              
58             my %_info = map {$_ => 1} (
59             qw(.author .license .copyright_years .copyright_holder),
60             qw(.description .comment .displayname .displaycolour .icon .icontext),
61             qw(.subject_webpage .vendor_webpage .author_webpage .webpage),
62             qw(.repo_uri),
63             );
64              
65             my %_profiles = (
66             default => 0,
67             resources_only => 1,
68             minimal => 2,
69             );
70              
71             my %_synthetic = (
72             default => {
73             mul => [['"out"' => 'undef', reg => 1, '"arg"' => 'undef'] => ['user*' => 2] => [
74             ['open', \2, 0],
75             ['control', \2, 'sni:81', \1],
76             ],
77             ['"out"' => 'undef', reg => 1, reg => 2] => ['arg' => 3] => [
78             ['replace', \3, \2],
79             ['mul', 'out', \1, \3],
80             ],
81             ['"out"' => 'undef', reg => 1, int => 2] => ['arg' => 3] => [
82             ['open', \3, \2],
83             ['mul', 'out', \1, \3],
84             ]],
85             contents => [[reg => 1, int => 2] => ['user*' => 3] => [
86             ['open_function*', \3, \2],
87             ['contents*', \1, \3],
88             ],
89             [reg => 1, id => 2] => ['user*' => 3] => [
90             ['open*', \3, \2],
91             ['contents*', \1, \3],
92             ]],
93             call => [[reg => 1, int => 2] => ['user*' => 3] => [
94             ['open_function*', \3, \2],
95             ['call*', \1, \3]
96             ],
97             [reg => 1, id => 2] => ['user*' => 3] => [
98             ['open*', \3, \2],
99             ['call*', \1, \3],
100             ],
101             [[qw(reg int id)] => 1] => ['user*' => 2] => [
102             ['open_context*', \2],
103             ['call*', \2, \1],
104             ]],
105             transfer => [[reg => 1, string => 2] => ['user*' => 3] => [
106             ['open', \3, \2],
107             ['transfer', \1, \3],
108             ]],
109             control => [[reg => 1, [qw(reg sni:)] => 2, [qw(string bool)] => 3] => ['user*' => 4] => [
110             ['open', \4, \3],
111             ['control', \1, \2, \4],
112             ],
113             [reg => 1, [qw(reg sni:)] => 2, [qw(string bool)] => 3, reg => 4] => ['user*' => 5, 'arg' => 6] => [
114             ['open', \5, \3],
115             ['replace', \6, \4],
116             ['control', \1, \2, \5],
117             ],
118             [reg => 1, [qw(reg sni:)] => 2, [qw(string bool)] => 3, any => 4] => ['user*' => 5, 'arg' => 6] => [
119             ['open', \5, \3],
120             ['open', \6, \4],
121             ['control', \1, \2, \5],
122             ],
123             [any => 1, any => 2, any => 3, reg => 4] => ['arg' => 5] => [
124             ['replace', \5, \4],
125             ['control', \1, \2, \3, \5],
126             ],
127             [any => 1, any => 2, any => 3, any => 4] => ['arg' => 5] => [
128             ['open', \5, \4],
129             ['control', \1, \2, \3, \5],
130             ]],
131             push => [[any => 1, string => 2] => ['user*' => 3] => [
132             ['open', \3, \2],
133             ['push', \1, \3],
134             ],
135             [any => 1, any => 2] => ['user*' => 3] => [
136             ['open', \3, \2],
137             ['push', \1, \3],
138             ],
139             [any => 1, [qw(int id string bool)] => 2, '"arg"' => 3] => ['user*' => 4] => [
140             ['open', \4, \2],
141             ['control', \1, 'sni:180', \4, \3],
142             ],
143             [any => 1, reg => 2, '"arg"' => 3] => [] => [
144             ['control', \1, 'sni:180', \2, \3],
145             ],
146             [any => 1, [qw(int id string bool)] => 2, reg => 3] => ['user*' => 4, 'arg' => 5] => [
147             ['open', \4, \2],
148             ['replace', \5, \3],
149             ['control', \1, 'sni:180', \4, \3],
150             ],
151             [any => 1, reg => 2, reg => 3] => ['arg' => 4] => [
152             ['replace', \4, \3],
153             ['control', \1, 'sni:180', \2, \4],
154             ],
155             [any => 1, [qw(int id string bool)] => 2, any => 3] => ['user*' => 4, 'arg' => 5] => [
156             ['open', \4, \2],
157             ['open', \5, \3],
158             ['control', \1, 'sni:180', \4, \3],
159             ],
160             [any => 1, reg => 2, any => 3] => ['arg' => 4] => [
161             ['open', \4, \3],
162             ['control', \1, 'sni:180', \2, \4],
163             ]],
164             pop => [[reg => 1, reg => 2] => [] => [
165             ['control', \2, 'sni:181'],
166             ['replace', \1, 'out'],
167             ]],
168             setvalue => [
169             [reg => 1, reg => 2, '"arg"' => 3] => [] => [
170             ['control', \1, 'sni:102', \2, \3],
171             ],
172             [reg => 1, any => 2, '"arg"' => 3] => ['user*' => 4] => [
173             ['open', \4, \2],
174             ['control', \1, 'sni:102', \4, \3],
175             ],
176             [reg => 1, reg => 2, reg => 3] => ['arg' => 4] => [
177             ['replace', \4, \3],
178             ['control', \1, 'sni:102', \2, \4],
179             ],
180             [reg => 1, any => 2, reg => 3] => ['arg' => 4, 'user*' => 5] => [
181             ['replace', \4, \3],
182             ['open', \5, \2],
183             ['control', \1, 'sni:102', \5, \4],
184             ],
185             [reg => 1, reg => 2, any => 3] => ['arg' => 4] => [
186             ['open', \4, \3],
187             ['control', \1, 'sni:102', \2, \4],
188             ],
189             [reg => 1, any => 2, any => 3] => ['arg' => 4, 'user*' => 5] => [
190             ['open', \4, \3],
191             ['open', \5, \2],
192             ['control', \1, 'sni:102', \5, \4],
193             ]],
194             getvalue => [
195             ['"out"' => 1, reg => 2, any => 3] => ['user*' => 4] => [
196             ['open', \4, \3],
197             ['getvalue', \1, \2, \4],
198             ],
199             [reg => 1, reg => 2, reg => 3] => [] => [
200             ['getvalue', 'out', \2, \3],
201             ['replace', \1, 'out'],
202             ],
203             [reg => 1, reg => 2, any => 3] => ['user*' => 4] => [
204             ['open', \4, \3],
205             ['getvalue', 'out', \2, \4],
206             ['replace', \1, 'out'],
207             ]],
208             relations => [[alias => 1, reg => 2, id => 3, any => 4] => ['user*' => 5, 'user*' => 6] => [
209             ['.force_mapped', \5],
210             ['open_function', \5, \1],
211             ['open', \6, \3],
212             ['relations', \5, \2, \6, \4],
213             ]],
214             write_be8 => [
215             [reg => 1, reg => 2] => [arg => 3] => [
216             ['replace', \3, \2],
217             ['control', \1, 'sni:144', '8'],
218             ],
219             [reg => 1, any => 2] => [arg => 3] => [
220             ['open', \3, \2],
221             ['control', \1, 'sni:144', '8'],
222             ]],
223             write_be16 => [
224             [reg => 1, reg => 2] => [arg => 3] => [
225             ['replace', \3, \2],
226             ['control', \1, 'sni:144', '16'],
227             ],
228             [reg => 1, any => 2] => [arg => 3] => [
229             ['open', \3, \2],
230             ['control', \1, 'sni:144', '16'],
231             ]],
232             '.autosectionstart' => [['"header"' => 1] => [] => [
233             ['.section', \1, '"VM\\r\\n\\xc0\\n"'],
234             ['filesize', 'size$out$'],
235             ['text_boundary', 'end$boundary$text'],
236             ['load_boundary', 'end$boundary$load'],
237             (map {['section_pointer', 'section$'.$_.'//section$header']} @_section_order),
238             ],
239             ['"rodata"' => 1] => [] => [
240             ['.section', \1],
241             ['.rodata'], # INTERNAL COMMAND, NOT FOR DOCS!
242             ['.align', 2],
243             ],
244             [any => 1] => [] => [
245             ['.section', \1]
246             ]],
247             '.autosection' => [[any => 1] => [] => [
248             ['.autosectionstart', \1],
249             ['.endsection']
250             ]],
251             '.filechunk' => [[string => 1, 'any...' => 2] => [] => [
252             ['.chunk', \2],
253             ['.cat', \1],
254             ['.endchunk'],
255             ]],
256             '.paddingchunk' => [['any...' => 1] => [] => [
257             ['.chunk', 'of', 'sni:237'],
258             ['.org', \1],
259             ['.endchunk'],
260             ]],
261             },
262             minimal => {
263             '.autosection' => [
264             ['"rodata"' => 1] => [] => [
265             ['data_start_marker'],
266             ['.rodata'], # INTERNAL COMMAND, NOT FOR DOCS!
267             ],
268             [any => 1] => [] => [
269             ]],
270             },
271             );
272              
273             my %_section_order_bad;
274              
275             {
276             my @got;
277             foreach my $section (reverse @_section_order) {
278             $_section_order_bad{$section} = {map {$_ => 1} @got};
279             push(@got, $section);
280             }
281             }
282              
283              
284             sub new {
285 0     0 1   my ($pkg, %opts) = @_;
286 0           my $self = bless({
287             alive => 1,
288             alignment => 1024,
289             aliases => {},
290             current => {},
291             rf => SIRTX::VM::RegisterFile->new,
292             regmap_last_used_c => 0,
293             regmap_last_used => {},
294             regmap_mapped => {},
295             sections => {},
296             pushback => [],
297             settings => {
298             synthetic_auto_unref => 1,
299             regmap_auto => undef,
300             },
301             rodata => String::Super->new,
302             alias_rodata_idx => {},
303             auto_host_defined => undef,
304             profiles => [],
305             profiles_hash => undef,
306             }, $pkg);
307              
308             {
309 0           my $fh = delete $opts{in};
310 0 0         croak 'No input given' unless defined $fh;
311              
312 0 0         unless (ref $fh) {
313 0 0         open(my $x, '<', $fh) or die $!;
314 0           $fh = $x;
315             }
316              
317 0           $fh->binmode;
318 0           $fh->binmode(':utf8');
319 0           $self->{in} = $fh;
320             }
321              
322             {
323 0           my $fh = delete $opts{out};
  0            
  0            
324 0 0         croak 'No output given' unless defined $fh;
325              
326 0 0         unless (ref $fh) {
327 0 0         open(my $x, '>', $fh) or die $!;
328 0           $fh = $x;
329             }
330              
331 0           $fh->binmode;
332 0           $self->{out} = $fh;
333             }
334              
335 0           $self->_join_profile('default');
336              
337 0 0         if (defined(my $profile = delete $opts{profile})) {
338 0 0         $profile = [$profile] unless ref $profile;
339              
340 0           foreach my $p (@{$profile}) {
  0            
341 0           $self->_join_profile(split(/(?:\s*,\s*|\s+)/, $p));
342             }
343             }
344              
345 0 0         croak 'Stray options passed' if scalar keys %opts;
346              
347 0           return $self;
348             }
349              
350              
351             sub run {
352 0     0 1   my ($self, @opts) = @_;
353              
354 0 0         croak 'Stray options passed' if scalar @opts;
355              
356 0           $self->_save_position('out$');
357 0           $self->_proc_input($self->{in});
358              
359 0           eval {
360 0           my $size;
361 0           $self->{out}->seek(0, SEEK_END);
362 0           $size = $self->{out}->tell;
363 0 0         if ($size & 1) {
364 0           carp sprintf('WARNING: Final file size is %u bytes, this is a odd number', $size);
365             }
366 0           $self->_save_endposition('out$');
367             };
368              
369             {
370 0           my $boundary = 0;
371              
372 0           foreach my $section (@_section_text) {
373 0   0       my $s = $self->{aliases}{'end$inner$section$'.$section} // next;
374 0 0         $boundary = $s->[-1] if $boundary < $s->[-1];
375             }
376              
377 0 0         if ($boundary & 1) {
378 0 0 0       if (defined($self->{aliases}{'size$out$'}) && $self->{aliases}{'size$out$'}[-1] > $boundary) {
379 0           $boundary++;
380             } else {
381 0           croak sprintf('Error: Text boundary has odd size and output size is invalid/too low');
382             }
383             }
384              
385 0   0       push(@{$self->{aliases}{'boundary$text'} //= []}, 0);
  0            
386 0   0       push(@{$self->{aliases}{'end$boundary$text'} //= []}, $boundary);
  0            
387             }
388              
389             {
390 0           my $boundary = 0;
  0            
  0            
391              
392 0           foreach my $section (@_section_load) {
393 0   0       my $s = $self->{aliases}{'end$inner$section$'.$section} // next;
394 0 0         $boundary = $s->[-1] if $boundary < $s->[-1];
395             }
396              
397 0 0         if ($boundary & 1) {
398 0 0 0       if (defined($self->{aliases}{'size$out$'}) && $self->{aliases}{'size$out$'}[-1] > $boundary) {
399 0           $boundary++;
400             } else {
401 0           croak sprintf('Error: Load boundary has odd size and output size is invalid/too low');
402             }
403             }
404              
405 0   0       push(@{$self->{aliases}{'boundary$load'} //= []}, 0);
  0            
406 0   0       push(@{$self->{aliases}{'end$boundary$load'} //= []}, $boundary);
  0            
407             }
408              
409             # We are past the first pass.
410             # We disable automapping here. If there is still mapping needed there is a bug somewhere as this all should be resolved by now.
411             # So turning it off to let any requests fail is the safest option.
412 0           $self->{settings}{regmap_auto} = undef;
413              
414             {
415 0           my $pushback = $self->{pushback};
  0            
416              
417 0           $self->{pushback} = []; # reset
418              
419 0           foreach my $entry (@{$pushback}) {
  0            
420 0           local $self->{rf} = $entry->{rf};
421 0           $self->{out}->seek($entry->{pos}, SEEK_SET);
422 0 0         $entry->{update}->($self, $entry) if defined $entry->{update};
423 0           $self->_proc_parts($entry->{parts}, $entry->{opts}, undef, 1);
424             }
425             }
426              
427 0 0         if (scalar(@{$self->{pushback}})) {
  0            
428 0           foreach my $entry (@{$self->{pushback}}) {
  0            
429 0           carp sprintf('Warning: Still active pushback from line %u', $entry->{opts}{line});
430             }
431              
432 0           croak sprintf('Error: There are still %u open pushbacks', scalar(@{$self->{pushback}}));
  0            
433             }
434             }
435              
436              
437             sub dump {
438 0     0 1   my ($self, $dumpfilename, @opts) = @_;
439 0           my $aliases = $self->{aliases};
440 0           my $rf = $self->{rf};
441 0           my $dumpfh;
442              
443 0 0         croak 'Stray options passed' if scalar @opts;
444              
445 0 0         if (ref($dumpfilename)) {
446 0           $dumpfh = $dumpfilename;
447             } else {
448 0           $dumpfh = $self->_open_file($dumpfilename, '>');
449             }
450 0           $dumpfh->binmode;
451 0           $dumpfh->binmode(':utf8');
452              
453 0           say $dumpfh '; Profiles:';
454 0           foreach my $key (@{$self->{profiles}}) {
  0            
455 0           printf $dumpfh "; %s\n", $key;
456             }
457              
458 0           say $dumpfh '';
459 0           say $dumpfh '; Settings:';
460 0           foreach my $key (sort keys %{$self->{settings}}) {
  0            
461 0   0       printf $dumpfh "; %-32s -> %s\n", $key, $self->{settings}{$key} // '';
462             }
463              
464 0           say $dumpfh '';
465 0           say $dumpfh '; Register map:';
466 0           foreach my $reg ($rf->expand('r*')) {
467 0   0       printf $dumpfh "; %-32s -> %s\n", $reg, scalar(eval {$rf->get_physical_by_name($reg)->name}) // '';
  0            
468             }
469              
470 0           say $dumpfh '';
471 0           say $dumpfh '; Register attributes:';
472 0           foreach my $reg ($rf->expand('r*', 'user*', 'system*')) {
473 0           my $physical = $rf->get_physical_by_name($reg)->physical;
474 0           my $temperature = $rf->register_temperature($reg);
475 0           my $owner = $rf->register_owner($reg);
476 0   0       printf $dumpfh "; %-32s -> %2u: %8s %8s %8u\n", $reg, $physical, $temperature, $owner, $self->{regmap_last_used}{$physical} // 0;
477             }
478              
479 0           say $dumpfh '';
480 0           say $dumpfh '; Aliases:';
481 0           foreach my $key (sort keys %{$aliases}) {
  0            
482 0           printf $dumpfh "; %-32s = %s\n", $key, join(', ', @{$aliases->{$key}});
  0            
483             }
484             }
485             # ---- Private helpers ----
486             sub _open_file {
487 0     0     my ($self, $filename, $mode) = @_;
488 0   0       $mode //= '<';
489 0 0         open(my $fh, $mode, $filename) or die $!;
490 0           return $fh;
491             }
492              
493             sub _alive {
494 0     0     my ($self) = @_;
495 0           return $self->{alive};
496             }
497              
498             sub _quit {
499 0     0     my ($self) = @_;
500 0           delete $self->{alive};
501             }
502              
503             sub _join_profile {
504 0     0     my ($self, @profiles) = @_;
505 0           push(@profiles, @{$self->{profiles}});
  0            
506              
507 0   0       $self->{profiles} = [uniq sort {($_profiles{$b} // croak 'Bad profile: '.$b) <=> ($_profiles{$a} // croak 'Bad profile: '.$a)} @profiles];
  0   0        
508 0           $self->{profiles_hash} = {map {$_ => 1} @{$self->{profiles}}};
  0            
  0            
509             }
510              
511             sub _using_profile {
512 0     0     my ($self, @profile) = @_;
513 0           my $hash = $self->{profiles_hash};
514              
515 0           foreach my $profile (@profile) {
516 0 0         return 1 if $hash->{$profile};
517             }
518              
519 0           return undef;
520             }
521              
522             sub _get_synthetic {
523 0     0     my ($self, $cmd) = @_;
524              
525 0           foreach my $profile (@{$self->{profiles}}) {
  0            
526 0 0         if (defined(my $entry = $_synthetic{$profile}{$cmd})) {
527 0           return $entry;
528             }
529             }
530              
531 0           return undef;
532             }
533              
534             sub _align {
535 0     0     my ($self, $req, $warn) = @_;
536 0 0         if ($self->{alignment} % $req) {
537 0           my $pos = $self->{out}->tell;
538 0           my $error = $pos % $req;
539 0 0         if ($error) {
540 0   0       $warn //= 0;
541 0 0         if ($warn > 1) {
    0          
542 0           croak sprintf('Fatal alignment missmatch would need to skip %u bytes', $req - $error);
543             } elsif ($warn) {
544 0           carp sprintf('Alignment missmatch, auto skipping %u bytes', $req - $error);
545             }
546 0           $self->{out}->seek($req - $error, SEEK_CUR);
547 0           $self->{alignment} = $req;
548             }
549             }
550             }
551              
552             sub _set_alignment {
553 0     0     my ($self, $value) = @_;
554 0           $self->{alignment} = $value;
555             }
556              
557             sub _save_position {
558 0     0     my ($self, $name) = @_;
559 0   0       push(@{$self->{aliases}{$name} //= []}, $self->{out}->tell);
  0            
560             }
561             sub _save_endposition {
562 0     0     my ($self, $name) = @_;
563 0   0       push(@{$self->{aliases}{'end$' .$name} //= []}, $self->{out}->tell);
  0            
564 0 0 0       push(@{$self->{aliases}{'size$'.$name} //= []}, $self->{aliases}{'end$'.$name}->[-1] - $self->{aliases}{$name}->[-1]) if defined $self->{aliases}{$name};
  0            
565             }
566              
567             sub _write_opcode {
568 0     0     my ($self, $opcode) = @_;
569 0           $self->_align($opcode->required_alignment, 1);
570 0           $opcode->write($self->{out});
571 0           $self->_set_alignment($opcode->new_alignment);
572             }
573              
574             sub _reg_map {
575 0     0     my ($self, $loc, $phy) = @_;
576 0           my $rf = $self->{rf};
577 0           $loc = $rf->get_logical_by_name($loc);
578 0           $phy = $rf->get_physical_by_name($phy);
579 0           $rf->map($loc, $phy);
580 0           return ($loc, $phy);
581             }
582             sub _reg_map_and_write {
583 0     0     my ($self, @args) = @_;
584 0           my ($loc, $phy) = $self->_reg_map(@args);
585 0           $self->_write_opcode(SIRTX::VM::Opcode->new(code => 0, codeX => 3, P => $loc, ST => $phy->physical));
586             }
587              
588             sub _force_mapped {
589 0     0     my ($self, $register) = @_;
590 0           my $regmap_last_used = $self->{regmap_last_used};
591 0           my $rf = $self->{rf};
592 0           my $loc = eval {$rf->get_logical_by_name($register)};
  0            
593 0           my $inc = 5;
594              
595 0 0         unless (defined $loc) {
596 0 0         if ($self->{settings}{regmap_auto}) {
597             # Try to auto-map a register.
598 0           my $regmap_mapped = $self->{regmap_mapped};
599 0   0       my ($reg) = sort {($regmap_last_used->{$a} // 0) <=> ($regmap_last_used->{$b} // 0)} map {$_->physical} map {$rf->get_physical_by_name($_)} grep {$rf->register_owner($_) eq SIRTX::VM::Register::OWNER_YOURS()} grep {!$regmap_mapped->{$_}} $rf->expand('r*');
  0   0        
  0            
  0            
  0            
  0            
600              
601 0 0         croak 'No suitable register found for auto mapping, did you set enough registers with .yours?' unless defined $reg;
602              
603 0           $loc = $rf->get_logical_by_physical($reg);
604 0           $regmap_mapped->{'r'.$loc} = 1;
605 0           $self->_reg_map_and_write('r'.$loc, $register);
606             }
607              
608 0 0         croak 'Cannot map register: '.$register unless defined $loc;
609             }
610              
611             {
612 0           my $physical = $rf->get_logical($loc)->physical;
  0            
613 0           $regmap_last_used->{$physical} = $self->{regmap_last_used_c} + $inc;
614 0           $self->{regmap_last_used_c} += 5;
615             }
616 0           return $loc;
617             }
618              
619             sub _reg_alloc_phy {
620 0     0     my ($self, @names) = @_;
621 0           my $regmap_mapped = $self->{regmap_mapped};
622 0           my $rf = $self->{rf};
623 0   0       my ($reg) = sort {(eval {$rf->get_logical_by_name($a)} // 999) <=> (eval {$rf->get_logical_by_name($b)} // 999)}
  0   0        
  0            
624 0           grep {$rf->register_owner($_) eq SIRTX::VM::Register::OWNER_YOURS()}
625 0           grep {!$regmap_mapped->{$_}}
  0            
626             $rf->expand(@names);
627              
628 0 0         croak 'No suitable physical register found for auto mapping, did you set enough registers with .yours?' unless defined $reg;
629              
630 0           $regmap_mapped->{$reg} = 1;
631              
632 0           return $reg;
633             }
634              
635             sub _autostring_allocate {
636 0     0     my ($self, $str) = @_;
637 0           state $autostring = 0;
638 0           my $key = sprintf('autostring$%u', $autostring++);
639              
640 0           $self->{alias_rodata_idx}{$key} = $self->{rodata}->add_blob($str);
641 0   0       push(@{$self->{aliases}{'size$'.$key} //= []}, length($str));
  0            
642              
643 0           return $key;
644             }
645              
646             sub _auto_host_defined {
647 0     0     my ($self) = @_;
648 0           my $auto_host_defined = $self->{auto_host_defined};
649 0           my $res;
650              
651 0 0 0       croak 'No auto host defined IDs available' unless defined($auto_host_defined) && defined($auto_host_defined->[2]);
652              
653 0           $res = $auto_host_defined->[2]++;
654              
655 0 0         if ($auto_host_defined->[2] > $auto_host_defined->[1]) {
656 0           $auto_host_defined->[2] = undef;
657             }
658              
659 0           return '~'.$res;
660             }
661              
662             sub _pushback {
663 0     0     my ($self, %opts) = @_;
664 0           $opts{rf} = $self->{rf}->clone;
665 0           push(@{$self->{pushback}}, \%opts);
  0            
666             }
667              
668             sub _proc_input {
669 0     0     my ($self, $in) = @_;
670              
671 0   0       while ($self->_alive && defined(my $line = <$in>)) {
672 0           my @parts;
673             my %opts;
674 0           my $autodie;
675              
676 0           $line =~ s/\r?\n$//;
677 0           $line =~ s/^\s+//;
678 0           $line =~ s/\s+$//;
679 0           $line =~ s/^(?:;|\/\/|#).*$//;
680 0           $line =~ s/\s+/ /g;
681              
682 0 0         next if $line eq '';
683              
684 0           while (length($line)) {
685 0 0         if ($line =~ s/^("[^"]*")//) {
    0          
    0          
686 0           push(@parts, $1);
687             } elsif ($line =~ /^(?:;|\/\/|#)/) {
688             # this is a comment, so just leave the rest alone
689 0           last;
690             } elsif ($line =~ s/^([^\s,=]+)//) {
691 0           push(@parts, $1);
692             } else {
693 0           croak 'Bad line: '.$in->input_line_number;
694             }
695              
696 0           $line =~ s/^\s*[,=]\s*//;
697 0           $line =~ s/^\s+//;
698             }
699              
700 0 0         next unless scalar @parts;
701              
702 0           $autodie = undef;
703 0 0         if ($parts[0] =~ s/([\?\!])$//) {
704 0 0         if ($1 eq '!') {
705 0           $autodie = 1;
706             }
707             }
708              
709 0           %opts = (line => $in->input_line_number);
710              
711 0           $self->{regmap_mapped} = {};
712 0           $self->_proc_parts(\@parts, \%opts, \$autodie);
713             }
714             }
715              
716             sub _proc_parts {
717 0     0     my ($self, $parts, $opts, $autodie, $allow_alts) = @_;
718 0           my $out = $self->{out};
719 0           my ($cmd, @args) = @{$parts};
  0            
720 0           my $was_return;
721             my $opcode;
722              
723 0   0       $autodie //= do { \my $x };
  0            
724              
725 0 0 0       if ($cmd ne '.pushname' && $cmd ne '.popname') {
726 0           foreach my $part (@{$parts}) {
  0            
727 0           my @alts;
728              
729 0 0         next unless $part =~ /^[a-z0-9]*\$/;
730              
731 0           @alts = split(m#//#, $part);
732              
733 0 0         if ($allow_alts) {
734 0           foreach my $alt (@alts) {
735 0 0         if (defined $self->{aliases}{$alt}) {
736 0           $part = $self->{aliases}{$alt}[-1];
737 0           last;
738             }
739             }
740             } else {
741 0 0         $part = $self->{aliases}{$alts[0]}[-1] if defined $self->{aliases}{$alts[0]};
742             }
743             }
744 0           ($cmd, @args) = @{$parts};
  0            
745             }
746              
747 0           foreach my $arg (@args) {
748 0 0         if ($arg eq '~auto') {
749 0           $arg = $self->_auto_host_defined;
750             }
751             }
752              
753 0 0 0       if ($cmd eq '.quit' && scalar(@args) == 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 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 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        
      0        
      0        
      0        
754 0           $self->_quit;
755             } elsif ($cmd eq '.profile' && scalar(@args) > 0) {
756 0           $self->_join_profile(@args);
757             } elsif ($cmd eq '.pushname' && !(scalar(@args) & 1)) {
758 0           for (my $i = 0; $i < scalar(@args); $i += 2) {
759 0           my $key = $args[$i + 0];
760 0           my $value = $args[$i + 1];
761              
762 0           $key =~ s/^\$?/\$/; # ensure it starts with a '$'.
763 0 0         if ($key !~ /^\$[0-9a-zA-Z_]+/) {
764 0           croak sprintf('Bad key name: line %s: key %s', $opts->{line}, $key);
765             }
766 0   0       push(@{$self->{aliases}{$key} //= []}, $value);
  0            
767             }
768             } elsif ($cmd eq '.popname') {
769 0           foreach my $key (@args) {
770 0           $key =~ s/^\$?/\$/; # ensure it starts with a '$'.
771 0 0         unless (defined $self->{aliases}{$key}) {
772 0           croak sprintf('Bad/unknown key name: line %s: key %s', $opts->{line}, $key);
773             }
774              
775 0           pop(@{$self->{aliases}{$key}});
  0            
776 0 0         delete $self->{aliases}{$key} unless scalar @{$self->{aliases}{$key}};
  0            
777             }
778             } elsif ($cmd eq '.tag' && scalar(@args) == 2 && $args[0] =~ /^[0-9a-zA-Z_]+$/ && defined($_type_to_sni{$args[1]})) {
779 0   0       push(@{$self->{aliases}{'tag$'.$args[0]} //= []}, 'sni:'.$_type_to_sni{$args[1]});
  0            
780             } elsif ($cmd eq '.tag' && scalar(@args) == 2 && $args[0] =~ /^[0-9a-zA-Z_]+$/ && $self->_get_value_type($args[1]) =~ /:$/) {
781 0   0       push(@{$self->{aliases}{'tag$'.$args[0]} //= []}, $args[1]);
  0            
782             } elsif ($cmd eq '.tag' && scalar(@args) == 2 && $args[0] =~ /^[0-9a-zA-Z_]+$/ && $self->_get_value_type($args[1]) eq 'int') {
783 0   0       push(@{$self->{aliases}{'tag$'.$args[0]} //= []}, 'sni:'.$self->_parse_int($args[1]));
  0            
784             } elsif ($cmd eq '.tag' && scalar(@args) == 3 && $args[0] =~ /^[0-9a-zA-Z_]+$/ && $args[1] =~ /^[0-9a-zA-Z_]+$/ && $self->_get_value_type($args[2]) eq 'int') {
785 0   0       push(@{$self->{aliases}{'tag$'.$args[0]} //= []}, $args[1].':'.$self->_parse_int($args[2]));
  0            
786             } elsif ($cmd eq '.utf8') {
787 0           foreach my $str (@args) {
788 0           print $out $self->_parse_string($str);
789             }
790 0           $self->_set_alignment(1);
791             } elsif ($cmd eq '.byte') {
792 0           my $last;
793 0           foreach my $str (@args) {
794 0           my $c = $self->_parse_int($str, $last);
795 0           print $out pack('C', $c);
796 0           $last = $c;
797             }
798 0           $self->_set_alignment(1);
799             } elsif ($cmd eq '.uint16') { # INTERNAL COMMAND! NOT FOR DOCS!
800 0           my $last;
801 0           foreach my $str (@args) {
802 0           my $c = $self->_parse_int($str, $last);
803 0           print $out pack('n', $c);
804 0           $last = $c;
805             }
806 0           $self->_set_alignment(1);
807             } elsif ($cmd eq '.uint16_half_up') { # INTERNAL COMMAND! NOT FOR DOCS!
808 0           my $last;
809 0           foreach my $str (@args) {
810 0           my $c = $self->_parse_int($str, $last);
811 0           print $out pack('n', ($c / 2) + ($c & 1));
812 0           $last = $c;
813             }
814 0           $self->_set_alignment(1);
815             } elsif ($cmd eq '.string' && scalar(@args) >= 2) {
816 0           my $key = 'string$'.$args[0];
817 0           my $catted = '';
818 0           foreach my $str (@args[1..$#args]) {
819 0           $catted .= $self->_parse_string($str);
820             }
821 0           $self->{alias_rodata_idx}{$key} = $self->{rodata}->add_blob($catted);
822 0   0       push(@{$self->{aliases}{'size$'.$key} //= []}, length($catted));
  0            
823             } elsif ($cmd eq 'open' && scalar(@args) == 2 && $self->_get_value_type($args[0]) eq 'reg' && $self->_get_value_type($args[1]) eq 'string') {
824 0           my $key = $self->_autostring_allocate($self->_parse_string($args[1]));
825 0           $self->_proc_parts(['substr', $args[0], 'program_text', $key, 'end$'.$key], $opts);
826             } elsif ($cmd eq 'open' && scalar(@args) == 2 && $self->_get_value_type($args[0]) eq 'reg' && $self->_get_value_type($args[1]) eq 'port') {
827 0           my $reg = $self->_reg_alloc_phy('user*');
828              
829 0 0         $self->_reg_alloc_phy('out') if $args[0] ne 'out';
830              
831 0           $self->_proc_parts(['open', $reg, $args[1] =~ s/^%//r], $opts);
832 0           $self->_proc_parts(['getvalue', 'out', 'context', $reg], $opts);
833              
834 0 0         if ($args[0] ne 'out') {
835 0           $self->_proc_parts(['replace', $args[0], 'out'], $opts);
836             }
837              
838 0 0         $self->_proc_parts(['unref', $reg], $opts) if $self->{settings}{synthetic_auto_unref};
839             } elsif ($cmd eq 'byte_transfer' && scalar(@args) == 2 && $self->_get_value_type($args[0]) eq 'reg' && $self->_get_value_type($args[1]) eq 'string') {
840 0           my $key = $self->_autostring_allocate($self->_parse_string($args[1]));
841 0           my $reg = $self->_reg_alloc_phy('user*');
842              
843 0           $self->_proc_parts(['substr', $reg, 'program_text', $key, 'end$'.$key], $opts);
844 0           $self->_proc_parts(['byte_transfer', $args[0], $reg, 'size$'.$key], $opts);
845 0 0         $self->_proc_parts(['unref', $reg], $opts) if $self->{settings}{synthetic_auto_unref};
846             } elsif ($cmd eq '.rodata' && scalar(@args) == 0) { # INTERNAL COMMAND! NOT FOR DOCS!
847 0           my $aliases = $self->{aliases};
848 0           my $rodata = $self->{rodata};
849 0           my $base = $out->tell;
850 0           print $out $rodata->result;
851 0           $self->_set_alignment(1);
852 0           foreach my $key (keys %{$self->{alias_rodata_idx}}) {
  0            
853 0           my $idx = $self->{alias_rodata_idx}{$key};
854 0           my $offset = $rodata->offset(index => $idx) + $base;
855 0           my $size = $aliases->{'size$'.$key}[-1];
856 0   0       push(@{$aliases->{$key} //= []}, $offset);
  0            
857 0 0 0       push(@{$aliases->{'end$'.$key} //= []}, $offset + $size) if defined $size;
  0            
858             }
859             } elsif ($cmd eq '.include') {
860 0           foreach my $arg (@args) {
861 0           my $fh = $self->_open_file($self->_parse_string($arg));
862 0           $fh->binmode;
863 0           $fh->binmode(':utf8');
864              
865 0           $self->_proc_input($fh);
866             }
867             } elsif ($cmd eq '.cat') {
868 0           local $/ = \4096;
869 0           foreach my $arg (@args) {
870 0           my $fh = $self->_open_file($self->_parse_string($arg));
871 0           $fh->binmode;
872              
873 0           print $out $_ while <$fh>;
874             }
875 0           $self->_set_alignment(1);
876             } elsif ($cmd eq '.noops' && scalar(@args) == 1) {
877 0           my $num = $self->_parse_int($args[0]);
878 0           my $opcode = SIRTX::VM::Opcode->from_template(parts => [qw(noop)], assembler => $self);
879              
880 0           $self->_align($opcode->required_alignment, 1);
881 0           for (my $i = 0; $i < $num; $i++) {
882 0           $opcode->write($out);
883             }
884 0           $self->_set_alignment($opcode->new_alignment);
885             } elsif ($cmd eq '.org' && scalar(@args) == 1) {
886 0           my $p = $self->_parse_int($args[0], $out->tell);
887 0 0         carp 'New address in .org is not a multiple of the word size: line '.$opts->{line} if $p & 1;
888 0           $out->seek($p, SEEK_SET);
889 0           $self->_set_alignment(1);
890             } elsif ($cmd eq '.align' && scalar(@args) == 1) {
891 0           my $p = $self->_parse_int($args[0]);
892 0           $self->_align($p);
893             } elsif ($cmd eq '.host_defined_auto_range' && scalar(@args) == 2 && $self->_get_value_type($args[0]) eq 'int' && $self->_get_value_type($args[1]) eq 'int') {
894 0           my $start = $self->_parse_int($args[0]);
895 0           my $end = $self->_parse_int($args[1], $start);
896 0 0 0       croak 'Invalid range for host defined identifiers: '.$start.' to '.$end if $end < $start || $start < 1;
897 0 0         croak 'Host defined range already set, trying to set it again in line '.$opts->{line} if defined $self->{auto_host_defined};
898 0           $self->{auto_host_defined} = [$start, $end, $start];
899             } elsif ($cmd eq '.label' && (scalar(@args) == 1 || scalar(@args) == 3) && $args[0] =~ /^[a-z0-9A-Z_]+$/ && (scalar(@args) == 1 || ($args[1] eq 'as' && $args[2] =~ /^~[0-9]+$/))) {
900 0           $self->{current}{label} = 'label$'.$args[0];
901 0           $self->_save_position($self->{current}{label});
902 0 0 0       if (scalar(@args) == 3 && $args[1] eq 'as' && $args[2] =~ /^~[0-9]+$/) {
      0        
903 0   0       push(@{$self->{aliases}{'hostdefined$label$'.$self->{current}{label}} //= []}, $args[2]);
  0            
904             }
905             } elsif ($cmd eq '.endlabel' && scalar(@args) == 0 && defined($self->{current}{label})) {
906 0           $self->_save_endposition($self->{current}{label});
907 0           $self->{current}{label} = undef;
908             } elsif ($cmd eq '.function' && (scalar(@args) == 1 || scalar(@args) == 3) && $args[0] =~ /^[a-z0-9A-Z_]+$/ && (scalar(@args) == 1 || ($args[1] eq 'as' && $args[2] =~ /^~[0-9]+$/))) {
909 0           $self->_align(2, 1);
910 0           $self->{current}{function} = $args[0];
911 0           $self->_save_position('function$'.$self->{current}{function});
912 0           $self->{rf}->map_reset;
913 0 0 0       if (scalar(@args) == 3 && $args[1] eq 'as' && $args[2] =~ /^~[0-9]+$/) {
      0        
914 0   0       push(@{$self->{aliases}{'hostdefined$function$'.$self->{current}{function}} //= []}, $args[2]);
  0            
915             }
916             } elsif ($cmd eq '.endfunction' && scalar(@args) == 0 && defined($self->{current}{function})) {
917 0 0         unless ($self->{was_return}) {
918 0           $self->_save_position('return$function$'.$self->{current}{function});
919 0           $self->_write_opcode(SIRTX::VM::Opcode->from_template(parts => ['return'], assembler => $self));
920             }
921 0           $self->_save_endposition('function$'.$self->{current}{function});
922 0           $self->{current}{function} = undef;
923             } elsif ($cmd eq '.section' && (scalar(@args) == 1 || scalar(@args) == 2) && !defined($self->{current}{section})) {
924 0           my $S = $_header_ids{$args[0]};
925 0           my $T = 4;
926 0           my $extra;
927             my %tpl;
928              
929 0 0         unless (defined $S) {
930 0           croak sprintf('Invalid section: line %s: section %s', $opts->{line}, $args[0]);
931             }
932              
933 0           foreach my $profile (@{$self->{profiles}}) {
  0            
934 0 0         if (defined(my $disabled = $_disabled_sections{$profile})) {
935 0 0         if ($disabled->{$args[0]}) {
936 0           croak sprintf('Invalid section for profile: line %s: section %s not allowed in profile %s', $opts->{line}, $args[0], $profile);
937             }
938             }
939             }
940              
941 0 0         if (defined(my $bad = $_section_order_bad{$args[0]})) {
942 0           foreach my $key (@_section_order) {
943 0 0         next unless defined $self->{sections}{$key};
944 0 0         croak sprintf('Invalid section: line %s: section %s must not follow section %s', $opts->{line}, $args[0], $key) if defined $bad->{$key};
945             }
946             }
947              
948 0 0         if (scalar(@args) == 2) {
949 0           $extra = $self->_parse_string($args[1]);
950 0           my $l = length($extra);
951 0 0 0       croak sprintf('Invalid section magic: line %s: section %s: magic length %u', $opts->{line}, $args[0], $l) if $l != 6 && $l != 4 && $l != 2 && $l != 0;
      0        
      0        
952 0           $T += $l/2;
953             }
954              
955 0           $self->_align(2, 1);
956 0           $self->_save_position('section$'.$args[0]);
957              
958 0           %tpl = (code => 0, P => 0, codeX => 0, S => $S, T => 0);
959              
960             $self->{current}{section} = {
961 0           close_opcode => SIRTX::VM::Opcode->new(%tpl),
962             name => $args[0],
963             };
964              
965 0 0         unless ($self->_using_profile('minimal')) {
966 0           $self->_write_opcode(SIRTX::VM::Opcode->new(%tpl, T => $T, extra => $extra));
967             }
968 0           $self->_save_position('inner$section$'.$args[0]);
969              
970 0           $self->{sections}{$args[0]} = {};
971             } elsif ($cmd eq '.endsection' && scalar(@args) == 0 && defined(my $section = $self->{current}{section})) {
972 0           my $section_suffix = 'section$'.$section->{name};
973 0           $self->_save_endposition('inner$'.$section_suffix);
974 0 0         unless ($self->_using_profile('minimal')) {
975 0 0         $self->_write_opcode($section->{close_opcode}) if defined $section->{close_opcode};
976             }
977 0           $self->_save_endposition($section_suffix);
978 0           $self->{current}{section} = undef;
979             } elsif ($cmd eq '.chunk' && scalar(@args) >= 2) {
980 0           my @in = @args;
981 0           my $flags = 0;
982 0           my $type;
983 0           my $identifier = 0;
984 0           my %info;
985 0           $self->_align(2, 1);
986              
987 0           while (scalar(@in)) {
988 0           my $c = shift(@in);
989              
990 0 0 0       if (($c eq 'of' || $c eq 'as' || $c eq 'name') && scalar(@in)) {
    0 0        
991 0           $info{$c} = shift(@in);
992             } elsif ($c eq 'standalone') {
993 0           $info{$c} = 1;
994             } else {
995 0           croak sprintf('Invalid chunk option: line %s: %s', $opts->{line}, $c);
996             }
997             }
998              
999 0 0         if (defined(my $as = $info{as})) {
1000 0 0         if ($as !~ /^~([0-9]+)$/) {
1001 0           croak sprintf('Invalid chunk option: line %s: as %s', $opts->{line}, $as);
1002             }
1003              
1004 0           $identifier = int($1);
1005             }
1006              
1007 0 0 0       if (defined($info{name}) && length($info{name})) {
    0          
1008 0 0         unless ($info{name} =~ /^[0-9a-z]+$/) {
1009 0           croak sprintf('Invalid chunk name: line %s: %s', $opts->{line}, $info{name});
1010             }
1011             } elsif ($identifier) {
1012 0           $info{name} = sprintf('idchunk$%u', $identifier);
1013             } else {
1014 0           state $autochunk = 0;
1015 0           $info{name} = sprintf('autochunk$%u', $autochunk++);
1016             }
1017              
1018             # Allow for chunks' ID to be accessed via it's name
1019 0 0 0       push(@{$self->{aliases}{'hostdefined$chunk$'.$info{name}} //= []}, $info{as}) if defined $info{as};
  0            
1020              
1021 0 0         if (defined(my $of = $info{of})) {
1022 0 0         if ($of =~ /^~([0-9]+)$/) {
1023 0           $type = int($1);
1024 0           $flags |= 1<<15;
1025             } else {
1026 0           my $t;
1027 0           ($t, $type) = $self->_parse_id($of);
1028              
1029 0 0         if ($t eq 'sid') {
    0          
1030 0           $flags |= 1<<14;
1031             } elsif ($t eq 'sni') {
1032             # no-op
1033             } else {
1034 0           croak sprintf('Invalid chunk type: line %s: of type %s not supported', $opts->{line}, $t);
1035             }
1036             }
1037             } else {
1038 0           croak sprintf('Invalid chunk: line %s: no of (type) given', $opts->{line});
1039             }
1040              
1041 0 0         $flags |= 1<<7 if $info{standalone};
1042 0 0         $flags |= 1<<1 if $identifier > 0;
1043              
1044 0           print $out chr(0x06), chr(0x38+1);
1045 0           $self->_pushback(pos => $out->tell, parts => ['.uint16_half_up', 'size$chunk$'.$info{name}], opts => {%{$opts}, size => 2});
  0            
1046 0           print $out chr(0) x 2;
1047              
1048 0           $self->{current}{chunk} = $info{name};
1049 0           $self->_save_position('chunk$'.$info{name});
1050              
1051 0           $self->_pushback(pos => $out->tell, parts => ['.uint16', $flags], opts => {%{$opts}, size => 2}, update => sub {
1052 0     0     my (undef, $entry) = @_;
1053 0           $entry->{parts}[1] |= $self->{aliases}{'size$chunk$'.$info{name}}[-1] & 1; # update padding flag
1054 0           });
1055 0           print $out chr(0) x 2;
1056              
1057 0           print $out pack('n', $type);
1058 0 0         print $out pack('n', $identifier) if $identifier > 0;
1059 0           $self->_save_position('inner$chunk$'.$info{name});
1060             } elsif ($cmd eq '.endchunk' && scalar(@args) == 0 && defined($self->{current}{chunk})) {
1061 0           $self->_save_endposition('inner$chunk$'.$self->{current}{chunk});
1062 0           $self->_save_endposition('chunk$'.$self->{current}{chunk});
1063 0           $self->{current}{chunk} = undef;
1064 0           $self->_align(2);
1065             } elsif ($cmd =~ /^\.(regmap_auto|synthetic_auto_unref)$/ && scalar(@args) == 1) {
1066 0           $self->{settings}{$1} = $self->_parse_bool($args[0]);
1067             } elsif ($cmd eq '.map' && scalar(@args) == 2) {
1068 0           $self->_reg_map(@args);
1069             } elsif ($cmd eq '.force_mapped') {
1070 0           $self->_force_mapped($_) foreach $self->{rf}->expand(@args);
1071             } elsif ($cmd eq '.mine' || $cmd eq '.yours' || $cmd eq '.theirs') {
1072 0 0         my $mode = $cmd eq '.mine' ? SIRTX::VM::Register::OWNER_MINE() : $cmd eq '.yours' ? SIRTX::VM::Register::OWNER_YOURS() : SIRTX::VM::Register::OWNER_THEIRS();
    0          
1073 0           my $rf = $self->{rf};
1074 0           foreach my $reg ($rf->expand(@args)) {
1075 0           $rf->register_owner($reg, $mode);
1076             }
1077             } elsif ($cmd eq '.hot' || $cmd eq '.cold' || $cmd eq '.lukewarm') {
1078 0 0         my $mode = $cmd eq '.hot' ? SIRTX::VM::Register::TEMPERATURE_HOT() : $cmd eq '.cold' ? SIRTX::VM::Register::TEMPERATURE_COLD() : SIRTX::VM::Register::TEMPERATURE_LUKEWARM();
    0          
1079 0           my $rf = $self->{rf};
1080 0           foreach my $reg ($rf->expand(@args)) {
1081 0           $rf->register_temperature($reg, $mode);
1082             }
1083             } elsif ($cmd eq '.regattr' && scalar(@args) >= 1) {
1084 0           my ($reg, @attrs) = @args;
1085 0           my $rf = $self->{rf};
1086              
1087 0           foreach my $attr (@attrs) {
1088 0 0         if ($attr eq 'mine') {
    0          
    0          
    0          
    0          
    0          
    0          
1089 0           $rf->register_owner($reg, SIRTX::VM::Register::OWNER_MINE());
1090             } elsif ($attr eq 'yours') {
1091 0           $rf->register_owner($reg, SIRTX::VM::Register::OWNER_YOURS());
1092             } elsif ($attr eq 'theirs') {
1093 0           $rf->register_owner($reg, SIRTX::VM::Register::OWNER_THEIRS());
1094             } elsif ($attr eq 'hot') {
1095 0           $rf->register_temperature($reg, SIRTX::VM::Register::TEMPERATURE_HOT());
1096             } elsif ($attr eq 'cold') {
1097 0           $rf->register_temperature($reg, SIRTX::VM::Register::TEMPERATURE_COLD());
1098             } elsif ($attr eq 'lukewarm') {
1099 0           $rf->register_temperature($reg, SIRTX::VM::Register::TEMPERATURE_LUKEWARM());
1100             } elsif ($attr eq 'volatile') {
1101             # No-op
1102             } else {
1103 0           croak sprintf('Invalid register attribute: line %s: register %s: attribute: %s', $opts->{line}, $reg, $attr);
1104             }
1105             }
1106              
1107             } elsif ($cmd eq 'map' && scalar(@args) == 2) {
1108 0           $self->_reg_map_and_write(@args);
1109              
1110 0           } elsif (defined($opcode = eval {SIRTX::VM::Opcode->from_template(parts => $parts, assembler => $self, size => $opts->{size}, line => $opts->{line}, out => $out, autodie => $autodie)})) {
1111 0           $self->_write_opcode($opcode);
1112 0           $was_return = $opcode->is_return;
1113 0 0         ${$autodie} = undef if $opcode->is_autodie;
  0            
1114             } elsif (defined($opcode = eval {SIRTX::VM::Opcode->from_template(parts => [
1115             $parts->[0],
1116 0 0         map {scalar(eval {$self->_get_value_type($_) eq 'alias'}) ? 0xFFF0 : $_} $parts->@[1 .. (scalar(@{$parts}) - 1)]
  0            
  0            
1117 0           ], assembler => $self, size => $opts->{size}, line => $opts->{line}, out => $out, autodie => $autodie)})) {
1118 0           my $pos;
1119              
1120             # first align, then look where we are.
1121 0           $self->_align($opcode->required_alignment, 1);
1122 0           $pos = $self->{out}->tell;
1123              
1124 0           $self->_write_opcode($opcode);
1125 0           $was_return = $opcode->is_return;
1126 0 0         ${$autodie} = undef if $opcode->is_autodie;
  0            
1127 0           $self->_pushback(pos => $pos, parts => $parts, opts => {%{$opts}, size => $opcode->required_size});
  0            
1128             } elsif (defined($_info{$cmd}) && scalar(@args) > 1 && $args[0] =~ /^~0?$/) {
1129             # no-op for now.
1130             } else {
1131 0           my $done;
1132              
1133 0 0         if (defined(my $entry = $self->_get_synthetic($cmd))) {
1134             outer:
1135 0           for (my $i = 0; $i < scalar(@{$entry}); $i += 3) {
  0            
1136 0           my @argmap = @{$entry->[$i]};
  0            
1137 0           my @requests = @{$entry->[$i+1]};
  0            
1138 0           my %updates;
1139             my @allocations;
1140 0           my $reset_autodie;
1141              
1142 0 0 0       if (scalar(@argmap) >= 2 && $argmap[-2] eq 'any...') {
1143 0 0         next unless (scalar(@args)*2) >= scalar(@argmap);
1144             } else {
1145 0 0         next unless (scalar(@args)*2) == scalar(@argmap);
1146             }
1147              
1148             # Process argument map:
1149 0           for (my $j = 0; ($j*2) < scalar(@argmap); $j++) {
1150 0           my $type = $argmap[$j*2 + 0];
1151 0           my $dst = $argmap[$j*2 + 1];
1152 0           my $val = $args[$j];
1153              
1154 0 0         if ($type =~ /^".+"$/) {
    0          
    0          
1155 0 0         next outer if $val ne $self->_parse_string($type);
1156             } elsif ($type eq 'any') {
1157             # no-op.
1158             } elsif ($type eq 'any...') {
1159 0           $val = [@args[$j..$#args]];
1160             } else {
1161 0           my $t = $self->_get_value_type($val);
1162 0 0         if (ref $type) {
1163 0           my $found;
1164             inner:
1165 0           foreach my $tw (@{$type}) {
  0            
1166 0 0 0       next inner if $t ne $tw && !($t =~ /:$/ && $tw eq 'id');
      0        
1167 0           $found = 1;
1168 0           last;
1169             }
1170 0 0         next outer unless $found;
1171             } else {
1172 0 0 0       next outer if $t ne $type && !($t =~ /:$/ && $type eq 'id');
      0        
1173             }
1174             }
1175              
1176 0 0         if ($dst eq 'undef') {
1177             # ignore this value
1178             } else {
1179 0           $updates{$dst} = $val;
1180             }
1181             }
1182              
1183             # Find suitable temp registers:
1184 0           for (my $j = 0; ($j*2) < scalar(@requests); $j++) {
1185 0           my $req = $requests[$j*2 + 0];
1186 0           my $dst = $requests[$j*2 + 1];
1187 0           my $found = $self->_reg_alloc_phy($req);
1188              
1189 0           $updates{$dst} = $found;
1190 0           push(@allocations, $found);
1191             }
1192              
1193             # Actually run the parts:
1194 0           foreach my $parts (@{$entry->[$i+2]}) {
  0            
1195 0 0         my @parts = map {ref ? ref($updates{${$_}}) ? @{$updates{${$_}}} : $updates{${$_}} : $_} @{$parts};
  0 0          
  0            
  0            
  0            
  0            
  0            
1196 0           my $ad;
1197              
1198 0 0         if ($parts[0] =~ s/\*$//) {
1199 0           $ad = ${$autodie};
  0            
1200 0           $reset_autodie = 1;
1201             }
1202              
1203 0           $self->_proc_parts(\@parts, $opts, \$ad);
1204             }
1205              
1206 0 0         if ($self->{settings}{synthetic_auto_unref}) {
1207 0           $self->_proc_parts(['unref', $_], $opts) foreach @allocations;
1208             }
1209              
1210 0           $done = 1;
1211 0 0         ${$autodie} = undef if $reset_autodie;
  0            
1212 0           last outer;
1213             }
1214             }
1215              
1216 0 0         croak sprintf('Invalid input: line %s: command %s: arguments: %s', $opts->{line}, $cmd, join(', ', @args)) unless $done;
1217             }
1218              
1219 0 0         if (${$autodie}) {
  0            
1220 0           $opcode = SIRTX::VM::Opcode->from_template(parts => ['autodie'], assembler => $self, line => $opts->{line});
1221 0           $self->_write_opcode($opcode);
1222             }
1223              
1224 0           $self->{was_return} = $was_return;
1225             }
1226              
1227             sub _type_to_sni {
1228 0     0     my ($self, $type) = @_;
1229 0   0       return $_type_to_sni{$type} // croak 'Unknown type: '.$type;
1230             }
1231              
1232             sub _get_value_type {
1233 0     0     my ($self, $value) = @_;
1234 0 0         return 'reg' if defined(scalar(eval {$self->{rf}->get_physical_by_name($value)}));
  0            
1235 0 0 0       return 'bool' if $value eq 'true' || $value eq 'false';
1236 0 0         return 'undef' if $value eq 'undef';
1237 0 0         return 'string' if $value =~ /^(?:"|U\+)/;
1238 0 0         return 'int' if $value =~ /^'?[\+\-]?(?:0|[1-9][0-9]*|0x[0-9a-fA-F]+|0[0-7]+|0b[01]+)$/;
1239 0 0         return 'port' if $value =~ /^%[a-zA-Z][:a-zA-Z0-9]*$/;
1240              
1241 0 0         if ($value =~ /^([a-z]+):(?:0|[1-9][0-9]*)$/) {
1242 0           my $type = $1;
1243 0 0         return $type.':' if defined $_type_to_sni{$type};
1244             }
1245              
1246 0           $value =~ s/^\/(0|[1-9][0-9]*)$/127:$1/;
1247 0 0         if ($value =~ /^([1-9][0-9]*):(?:0|[1-9][0-9]*)$/) {
1248 0           my $type = $1;
1249 0 0         if (defined $_sni_to_type{$type}) {
1250 0           $type = $_sni_to_type{$type};
1251             } else {
1252 0           $_type_to_sni{$type} = $type;
1253             }
1254 0           return $type.':';
1255             }
1256 0 0         return 'logical:' if $value =~ /^logical:/;
1257 0 0         return 'alias' if $value =~ /^[a-z0-9]*\$/;
1258 0           die 'Bad value: '.$value;
1259             }
1260              
1261             sub _parse_bool {
1262 0     0     my ($self, $bool) = @_;
1263 0           return $bool eq 'true';
1264             }
1265              
1266             sub _parse_int {
1267 0     0     my ($self, $val, $rel) = @_;
1268 0           my $neg;
1269              
1270 0           $val =~ s/^'//;
1271              
1272 0   0       $rel //= 0;
1273              
1274 0 0         if ($val =~ s/^-//) {
    0          
1275 0           $neg = 1;
1276             } elsif ($val =~ s/^\+//) {
1277             # no-op
1278             } else {
1279 0           $rel = 0;
1280             }
1281              
1282 0 0         if ($val =~ /^[1-9]/) {
    0          
1283 0           $val = int($val);
1284             } elsif ($val =~ /^(?:0[0-7]*|0x[0-9a-fA-F]+|0b[01]+)$/) {
1285 0           $val = oct($val);
1286             } else {
1287 0           die 'Bad integer';
1288             }
1289              
1290 0 0         $val *= -1 if $neg;
1291              
1292 0           return $val + $rel;
1293             }
1294              
1295             sub _parse_escape {
1296 0     0     my ($esc) = @_;
1297              
1298 0 0         return $_escapes{$esc} if defined $_escapes{$esc};
1299 0 0         return chr(hex($1)) if $esc =~ /^x([0-9a-f]{2})$/;
1300             }
1301              
1302             sub _parse_string {
1303 0     0     my ($self, $str) = @_;
1304              
1305 0 0         if ($str =~ s/^"(.*)"$/$1/) {
    0          
1306             # no-op
1307             } elsif ($str =~ /^U\+([0-9a-fA-F]{4,6})$/) {
1308 0           my $char = chr(hex($1));
1309 0           state $UTF_8 = Encode::find_encoding('UTF-8');
1310 0           return $UTF_8->encode($char);
1311             } else {
1312 0           die 'Bad string';
1313             }
1314              
1315 0           $str =~ s/\\(\\|[0nrte]|x[0-9a-f]{2})/_parse_escape($1)/ge;
  0            
1316              
1317 0           return $str;
1318             }
1319              
1320             sub _parse_id {
1321 0     0     my ($self, $str) = @_;
1322              
1323 0           $str =~ s/^\/(0|[1-9][0-9]*)$/127:$1/;
1324              
1325 0 0         if ($str =~ /^([a-z]+):(0|[1-9][0-9]*)$/) {
    0          
1326 0           my ($type, $num) = ($1, $2);
1327 0           return ($type, $self->_parse_int($num));
1328             } elsif ($str =~ /^([1-9][0-9]*):(0|[1-9][0-9]*)$/) {
1329 0           my ($type, $num) = ($1, $2);
1330 0 0         if (defined $_sni_to_type{$type}) {
1331 0           $type = $_sni_to_type{$type};
1332             } else {
1333 0           $_type_to_sni{$type} = $type;
1334             }
1335 0           return ($type, $self->_parse_int($num));
1336             } else {
1337 0           die 'Bad ID';
1338             }
1339             }
1340              
1341             1;
1342              
1343             __END__