insns.pl: better error messages, handle no-operand instructions better
[platform/upstream/nasm.git] / insns.pl
1 #!/usr/bin/perl
2 #
3 # insns.pl   produce insnsa.c, insnsd.c, insnsi.h, insnsn.c from insns.dat
4 #
5 # The Netwide Assembler is copyright (C) 1996 Simon Tatham and
6 # Julian Hall. All rights reserved. The software is
7 # redistributable under the license given in the file "LICENSE"
8 # distributed in the NASM archive.
9
10 # Opcode prefixes which need their own opcode tables
11 # LONGER PREFIXES FIRST!
12 @disasm_prefixes = qw(0F24 0F25 0F38 0F3A 0F7A 0FA6 0FA7 0F);
13
14 # This should match MAX_OPERANDS from nasm.h
15 $MAX_OPERANDS = 5;
16
17 # Add VEX prefixes
18 @vexlist = ();
19 for ($m = 0; $m < 32; $m++) {
20     for ($lp = 0; $lp < 8; $lp++) {
21         push(@vexlist, sprintf("VEX%02X%01X", $m, $lp));
22     }
23 }
24 @disasm_prefixes = (@vexlist, @disasm_prefixes);
25
26 print STDERR "Reading insns.dat...\n";
27
28 @args   = ();
29 undef $output;
30 foreach $arg ( @ARGV ) {
31     if ( $arg =~ /^\-/ ) {
32         if  ( $arg =~ /^\-([abdin])$/ ) {
33             $output = $1;
34         } else {
35             die "$0: Unknown option: ${arg}\n";
36         }
37     } else {
38         push (@args, $arg);
39     }
40 }
41
42 $fname = "insns.dat" unless $fname = $args[0];
43 open (F, $fname) || die "unable to open $fname";
44
45 %dinstables = ();
46 @bytecode_list = ();
47
48 $line = 0;
49 $insns = 0;
50 while (<F>) {
51   $line++;
52   chomp;
53   next if ( /^\s*(\;.*|)$/ );   # comments or blank lines
54
55   unless (/^\s*(\S+)\s+(\S+)\s+(\S+|\[.*\])\s+(\S+)\s*$/) {
56       warn "line $line does not contain four fields\n";
57       next;
58   }
59   @fields = ($1, $2, $3, $4);
60   ($formatted, $nd) = format_insn(@fields);
61   if ($formatted) {
62     $insns++;
63     $aname = "aa_$fields[0]";
64     push @$aname, $formatted;
65   }
66   if ( $fields[0] =~ /cc$/ ) {
67       # Conditional instruction
68       $k_opcodes_cc{$fields[0]}++;
69   } else {
70       # Unconditional instruction
71       $k_opcodes{$fields[0]}++;
72   }
73   if ($formatted && !$nd) {
74     push @big, $formatted;
75     my @sseq = startseq($fields[2]);
76     foreach $i (@sseq) {
77         if (!defined($dinstables{$i})) {
78             $dinstables{$i} = [];
79         }
80         push(@{$dinstables{$i}}, $#big);
81     }
82   }
83 }
84
85 close F;
86
87 #
88 # Generate the bytecode array.  At this point, @bytecode_list contains
89 # the full set of bytecodes.
90 #
91
92 # Sort by descending length
93 @bytecode_list = sort { scalar(@$b) <=> scalar(@$a) } @bytecode_list;
94 @bytecode_array = ();
95 %bytecode_pos = ();
96 $bytecode_next = 0;
97 foreach $bl (@bytecode_list) {
98     my $h = hexstr(@$bl);
99     next if (defined($bytecode_pos{$h}));
100
101     push(@bytecode_array, $bl);
102     while ($h ne '') {
103         $bytecode_pos{$h} = $bytecode_next;
104         $h = substr($h, 2);
105         $bytecode_next++;
106     }
107 }
108 undef @bytecode_list;
109
110 @opcodes    = sort keys(%k_opcodes);
111 @opcodes_cc = sort keys(%k_opcodes_cc);
112
113 if ( !defined($output) || $output eq 'b') {
114     print STDERR "Writing insnsb.c...\n";
115
116     open B, ">insnsb.c";
117
118     print B "/* This file auto-generated from insns.dat by insns.pl" .
119         " - don't edit it */\n\n";
120
121     print B "#include \"nasm.h\"\n";
122     print B "#include \"insns.h\"\n\n";
123
124     print B "const uint8_t nasm_bytecodes[$bytecode_next] = {\n";
125
126     $p = 0;
127     foreach $bl (@bytecode_array) {
128         printf B "    /* %5d */ ", $p;
129         foreach $d (@$bl) {
130             printf B "%#o,", $d;
131             $p++;
132         }
133         printf B "\n";
134     }
135     print B "};\n";
136
137     close B;
138 }
139
140 if ( !defined($output) || $output eq 'a' ) {
141     print STDERR "Writing insnsa.c...\n";
142
143     open A, ">insnsa.c";
144
145     print A "/* This file auto-generated from insns.dat by insns.pl" .
146         " - don't edit it */\n\n";
147
148     print A "#include \"nasm.h\"\n";
149     print A "#include \"insns.h\"\n\n";
150
151     foreach $i (@opcodes, @opcodes_cc) {
152         print A "static const struct itemplate instrux_${i}[] = {\n";
153         $aname = "aa_$i";
154         foreach $j (@$aname) {
155             print A "    ", codesubst($j), "\n";
156         }
157         print A "    ITEMPLATE_END\n};\n\n";
158     }
159     print A "const struct itemplate * const nasm_instructions[] = {\n";
160     foreach $i (@opcodes, @opcodes_cc) {
161         print A "    instrux_${i},\n";
162     }
163     print A "};\n";
164
165     close A;
166 }
167
168 if ( !defined($output) || $output eq 'd' ) {
169     print STDERR "Writing insnsd.c...\n";
170
171     open D, ">insnsd.c";
172
173     print D "/* This file auto-generated from insns.dat by insns.pl" .
174         " - don't edit it */\n\n";
175
176     print D "#include \"nasm.h\"\n";
177     print D "#include \"insns.h\"\n\n";
178
179     print D "static const struct itemplate instrux[] = {\n";
180     $n = 0;
181     foreach $j (@big) {
182         printf D "    /* %4d */ %s\n", $n++, codesubst($j);
183     }
184     print D "};\n";
185
186     foreach $h (sort(keys(%dinstables))) {
187         print D "\nstatic const struct itemplate * const itable_${h}[] = {\n";
188         foreach $j (@{$dinstables{$h}}) {
189             print D "    instrux + $j,\n";
190         }
191         print D "};\n";
192     }
193
194     @prefix_list = ();
195     foreach $h (@disasm_prefixes, '') {
196         for ($c = 0; $c < 256; $c++) {
197             $nn = sprintf("%s%02X", $h, $c);
198             if ($is_prefix{$nn} || defined($dinstables{$nn})) {
199                 # At least one entry in this prefix table
200                 push(@prefix_list, $h);
201                 $is_prefix{$h} = 1;
202                 last;
203             }
204         }
205     }
206
207     foreach $h (@prefix_list) {
208         print D "\n";
209         print D "static " unless ($h eq '');
210         print D "const struct disasm_index ";
211         print D ($h eq '') ? 'itable' : "itable_$h";
212         print D "[256] = {\n";
213         for ($c = 0; $c < 256; $c++) {
214             $nn = sprintf("%s%02X", $h, $c);
215             if ($is_prefix{$nn}) {
216                 die "$fname: ambiguous decoding of $nn\n"
217                     if (defined($dinstables{$nn}));
218                 printf D "    { itable_%s, -1 },\n", $nn;
219             } elsif (defined($dinstables{$nn})) {
220                 printf D "    { itable_%s, %u },\n",
221                 $nn, scalar(@{$dinstables{$nn}});
222             } else {
223                 printf D "    { NULL, 0 },\n";
224             }
225         }
226         print D "};\n";
227     }
228
229     print D "\nconst struct disasm_index * const itable_VEX[32][8] = {\n   ";
230     for ($m = 0; $m < 32; $m++) {
231         print D " {\n";
232         for ($lp = 0; $lp < 8; $lp++) {
233             $vp = sprintf("VEX%02X%01X", $m, $lp);
234             if ($is_prefix{$vp}) {
235                 printf D "        itable_%s,\n", $vp;
236             } else {
237                 print  D "        NULL,\n";
238             }
239         }
240         print D "    },";
241     }
242     print D "\n};\n";
243
244     close D;
245 }
246
247 if ( !defined($output) || $output eq 'i' ) {
248     print STDERR "Writing insnsi.h...\n";
249
250     open I, ">insnsi.h";
251
252     print I "/* This file is auto-generated from insns.dat by insns.pl" .
253         " - don't edit it */\n\n";
254     print I "/* This file in included by nasm.h */\n\n";
255
256     print I "/* Instruction names */\n\n";
257     print I "#ifndef NASM_INSNSI_H\n";
258     print I "#define NASM_INSNSI_H 1\n\n";
259     print I "enum opcode {\n";
260     $maxlen = 0;
261     foreach $i (@opcodes, @opcodes_cc) {
262         print I "\tI_${i},\n";
263         $len = length($i);
264         $len++ if ( $i =~ /cc$/ );      # Condition codes can be 3 characters long
265         $maxlen = $len if ( $len > $maxlen );
266     }
267     print I "\tI_none = -1\n";
268     print I "\n};\n\n";
269     print I "#define MAX_INSLEN ", $maxlen, "\n";
270     print I "#define FIRST_COND_OPCODE I_", $opcodes_cc[0], "\n\n";
271     print I "#endif /* NASM_INSNSI_H */\n";
272
273     close I;
274 }
275
276 if ( !defined($output) || $output eq 'n' ) {
277     print STDERR "Writing insnsn.c...\n";
278
279     open N, ">insnsn.c";
280
281     print N "/* This file is auto-generated from insns.dat by insns.pl" .
282         " - don't edit it */\n\n";
283     print N "#include \"tables.h\"\n\n";
284
285     print N "const char * const nasm_insn_names[] = {";
286     $first = 1;
287     foreach $i (@opcodes, @opcodes_cc) {
288         print N "," if ( !$first );
289         $first = 0;
290         $ilower = $i;
291         $ilower =~ s/cc$//;     # Remove conditional cc suffix
292         $ilower =~ tr/A-Z/a-z/; # Change to lower case (Perl 4 compatible)
293         print N "\n\t\"${ilower}\"";
294     }
295     print N "\n};\n";
296     close N;
297 }
298
299 printf STDERR "Done: %d instructions\n", $insns;
300
301 sub format_insn(@) {
302     my ($opcode, $operands, $codes, $flags) = @_;
303     my $num, $nd = 0;
304     my @bytecode;
305
306     return (undef, undef) if $operands eq "ignore";
307
308     # format the operands
309     $operands =~ s/:/|colon,/g;
310     $operands =~ s/mem(\d+)/mem|bits$1/g;
311     $operands =~ s/mem/memory/g;
312     $operands =~ s/memory_offs/mem_offs/g;
313     $operands =~ s/imm(\d+)/imm|bits$1/g;
314     $operands =~ s/imm/immediate/g;
315     $operands =~ s/rm(\d+)/rm_gpr|bits$1/g;
316     $operands =~ s/(mmx|xmm|ymm)rm/rm_$1/g;
317     $operands =~ s/\=([0-9]+)/same_as|$1/g;
318     if ($operands eq 'void') {
319         @ops = ();
320     } else {
321         @ops = split(/\,/, $operands);
322     }
323     $num = scalar(@ops);
324     while (scalar(@ops) < $MAX_OPERANDS) {
325         push(@ops, '0');
326     }
327     $operands = join(',', @ops);
328     $operands =~ tr/a-z/A-Z/;
329
330     # format the flags
331     $flags =~ s/,/|IF_/g;
332     $flags =~ s/(\|IF_ND|IF_ND\|)//, $nd = 1 if $flags =~ /IF_ND/;
333     $flags = "IF_" . $flags;
334
335     @bytecode = (decodify($codes), 0);
336     push(@bytecode_list, [@bytecode]);
337     $codes = hexstr(@bytecode);
338
339     ("{I_$opcode, $num, {$operands}, \@\@CODES-$codes\@\@, $flags},", $nd);
340 }
341
342 #
343 # Look for @@CODES-xxx@@ sequences and replace them with the appropriate
344 # offset into nasm_bytecodes
345 #
346 sub codesubst($) {
347     my($s) = @_;
348     my $n;
349
350     while ($s =~ /\@\@CODES-([0-9A-F]+)\@\@/) {
351         my $pos = $bytecode_pos{$1};
352         if (!defined($pos)) {
353             die "$fname: no position assigned to byte code $1\n";
354         }
355         $s = $` . "nasm_bytecodes+${pos}" . "$'";
356     }
357     return $s;
358 }
359
360 sub addprefix ($@) {
361     my ($prefix, @list) = @_;
362     my $x;
363     my @l = ();
364
365     foreach $x (@list) {
366         push(@l, sprintf("%s%02X", $prefix, $x));
367     }
368
369     return @l;
370 }
371
372 #
373 # Turn a code string into a sequence of bytes
374 #
375 sub decodify($) {
376   # Although these are C-syntax strings, by convention they should have
377   # only octal escapes (for directives) and hexadecimal escapes
378   # (for verbatim bytes)
379     my($codestr) = @_;
380
381     if ($codestr =~ /^\s*\[([^\]]*)\]\s*$/) {
382         return byte_code_compile($1);
383     }
384
385     my $c = $codestr;
386     my @codes = ();
387
388     while ($c ne '') {
389         if ($c =~ /^\\x([0-9a-f]+)(.*)$/i) {
390             push(@codes, hex $1);
391             $c = $2;
392             next;
393         } elsif ($c =~ /^\\([0-7]{1,3})(.*)$/) {
394             push(@codes, oct $1);
395             $c = $2;
396             next;
397         } else {
398             die "$fname: unknown code format in \"$codestr\"\n";
399         }
400     }
401
402     return @codes;
403 }
404
405 # Turn a numeric list into a hex string
406 sub hexstr(@) {
407     my $s = '';
408     my $c;
409
410     foreach $c (@_) {
411         $s .= sprintf("%02X", $c);
412     }
413     return $s;
414 }
415
416 # Here we determine the range of possible starting bytes for a given
417 # instruction. We need only consider the codes:
418 # \1 \2 \3     mean literal bytes, of course
419 # \4 \5 \6 \7  mean PUSH/POP of segment registers: special case
420 # \1[0123]     mean byte plus register value
421 # \330         means byte plus condition code
422 # \0 or \340   mean give up and return empty set
423 # \17[234]     skip is4 control byte
424 # \26x \270    skip VEX control bytes
425 sub startseq($) {
426   my ($codestr) = @_;
427   my $word, @range;
428   my @codes = ();
429   my $c = $codestr;
430   my $c0, $c1, $i;
431   my $prefix = '';
432
433   @codes = decodify($codestr);
434
435   while ($c0 = shift(@codes)) {
436       $c1 = $codes[0];
437       if ($c0 == 01 || $c0 == 02 || $c0 == 03) {
438           # Fixed byte string
439           my $fbs = $prefix;
440           while (1) {
441               if ($c0 == 01 || $c0 == 02 || $c0 == 03) {
442                   while ($c0--) {
443                       $fbs .= sprintf("%02X", shift(@codes));
444                   }
445               } else {
446                   last;
447               }
448               $c0 = shift(@codes);
449           }
450
451           foreach $pfx (@disasm_prefixes) {
452               if (substr($fbs, 0, length($pfx)) eq $pfx) {
453                   $prefix = $pfx;
454                   $fbs = substr($fbs, length($pfx));
455                   last;
456               }
457           }
458
459           if ($fbs ne '') {
460               return ($prefix.substr($fbs,0,2));
461           }
462
463           unshift(@codes, $c0);
464       } elsif ($c0 == 04) {
465           return addprefix($prefix, 0x07, 0x17, 0x1F);
466       } elsif ($c0 == 05) {
467           return addprefix($prefix, 0xA1, 0xA9);
468       } elsif ($c0 == 06) {
469           return addprefix($prefix, 0x06, 0x0E, 0x16, 0x1E);
470       } elsif ($c0 == 07) {
471           return addprefix($prefix, 0xA0, 0xA8);
472       } elsif ($c0 >= 010 && $c0 <= 013) {
473           return addprefix($prefix, $c1..($c1+7));
474       } elsif (($c0 & ~013) == 0144) {
475           return addprefix($prefix, $c1, $c1|2);
476       } elsif ($c0 == 0330) {
477           return addprefix($prefix, $c1..($c1+15));
478       } elsif ($c0 == 0 || $c0 == 0340) {
479           return $prefix;
480       } elsif (($c0 & ~3) == 0260 || $c0 == 0270) {
481           my $m,$wlp,$vxp;
482           $m   = shift(@codes);
483           $wlp = shift(@codes);
484           $prefix .= sprintf('VEX%02X%01X', $m, $wlp & 7);
485       } elsif ($c0 >= 0172 && $c0 <= 174) {
486           shift(@codes);        # Skip is4 control byte
487       } else {
488           # We really need to be able to distinguish "forbidden"
489           # and "ignorable" codes here
490       }
491   }
492   return $prefix;
493 }
494
495 #
496 # This function takes a series of byte codes in a format which is more
497 # typical of the Intel documentation, and encode it.
498 #
499 # The format looks like:
500 #
501 # [operands: opcodes]
502 #
503 # The operands word lists the order of the operands:
504 #
505 # r = register field in the modr/m
506 # m = modr/m
507 # v = VEX "v" field
508 # d = DREX "dst" field
509 # i = immediate
510 # s = register field of is4/imz2 field
511 # - = implicit (unencoded) operand
512 #
513 # For an operand that should be filled into more than one field,
514 # enter it as e.g. "r+v".
515 #
516 sub byte_code_compile($) {
517     my($str) = @_;
518     my $opr;
519     my $opc;
520     my @codes = ();
521     my $litix = undef;
522     my %oppos = ();
523     my $i;
524     my $op, $oq;
525
526     unless ($str =~ /^(([^\s:]*)\:|)\s*(.*\S)\s*$/) {
527         die "$fname: $line: cannot parse: [$str]\n";
528     }
529     $opr = "\L$2";
530     $opc = "\L$3";
531
532     my $op = 0;
533     for ($i = 0; $i < length($opr); $i++) {
534         my $c = substr($opr,$i,1);
535         if ($c eq '+') {
536             $op--;
537         } else {
538             $oppos{$c} = $op++;
539         }
540     }
541
542     $prefix_ok = 1;
543     foreach $op (split(/\s*(?:\s|(?=[\/\\]))/, $opc)) {
544         if ($op eq 'o16') {
545             push(@codes, 0320);
546         } elsif ($op eq 'o32') {
547             push(@codes, 0321);
548         } elsif ($op eq 'o64') {  # 64-bit operand size requiring REX.W
549             push(@codes, 0324);
550         } elsif ($op eq 'o64nw') { # Implied 64-bit operand size (no REX.W)
551             push(@codes, 0323);
552         } elsif ($op eq 'a16') {
553             push(@codes, 0310);
554         } elsif ($op eq 'a32') {
555             push(@codes, 0311);
556         } elsif ($op eq 'a64') {
557             push(@codes, 0313);
558         } elsif ($op eq '!osp') {
559             push(@codes, 0364);
560         } elsif ($op eq '!asp') {
561             push(@codes, 0365);
562         } elsif ($op eq 'rex.l') {
563             push(@codes, 0334);
564         } elsif ($op eq 'repe') {
565             push(@codes, 0335);
566         } elsif ($prefix_ok && $op =~ /^(66|f2|f3|np)$/) {
567             # 66/F2/F3 prefix used as an opcode extension, or np = no prefix
568             if ($op eq '66') {
569                 push(@codes, 0361);
570             } elsif ($op eq 'f2') {
571                 push(@codes, 0362);
572             } elsif ($op eq 'f3') {
573                 push(@codes, 0363);
574             } else {
575                 push(@codes, 0360);
576             }
577         } elsif ($op =~ /^[0-9a-f]{2}$/) {
578             if (defined($litix) && $litix+$codes[$litix]+1 == scalar @codes) {
579                 $codes[$litix]++;
580                 push(@codes, hex $op);
581             } else {
582                 $litix = scalar(@codes);
583                 push(@codes, 01, hex $op);
584             }
585             $prefix_ok = 0;
586         } elsif ($op eq '/r') {
587             if (!defined($oppos{'r'}) || !defined($oppos{'m'})) {
588                 die "$fname: $line: $op requires r and m operands\n";
589             }
590             push(@codes, 0100 + ($oppos{'m'} << 3) + $oppos{'r'});
591             $prefix_ok = 0;
592         } elsif ($op =~ m:^/([0-7])$:) {
593             if (!defined($oppos{'m'})) {
594                 die "$fname: $line: $op requires m operand\n";
595             }
596             push(@codes, 0200 + ($oppos{'m'} << 3) + $1);
597             $prefix_ok = 0;
598         } elsif ($op =~ /^vex(|\..*)$/) {
599             my ($m,$w,$l,$p) = (undef,2,undef,0);
600             my $has_nds = 0;
601             foreach $oq (split(/\./, $op)) {
602                 if ($oq eq 'vex') {
603                     # prefix
604                 } elsif ($oq eq '128' || $oq eq 'l0') {
605                     $l = 0;
606                 } elsif ($oq eq '256' || $oq eq 'l1') {
607                     $l = 1;
608                 } elsif ($oq eq 'w0') {
609                     $w = 0;
610                 } elsif ($oq eq 'w1') {
611                     $w = 1;
612                 } elsif ($oq eq 'wx') {
613                     $w = 2;
614                 } elsif ($oq eq 'ww') {
615                     $w = 3;
616                 } elsif ($oq eq '66') {
617                     $p = 1;
618                 } elsif ($oq eq 'f3') {
619                     $p = 2;
620                 } elsif ($oq eq 'f2') {
621                     $p = 3;
622                 } elsif ($oq eq '0f') {
623                     $m = 1;
624                 } elsif ($oq eq '0f38') {
625                     $m = 2;
626                 } elsif ($oq eq '0f3a') {
627                     $m = 3;
628                 } elsif ($oq =~ /^m([0-9]+)$/) {
629                     $m = $1+0;
630                 } elsif ($oq eq 'nds' || $oq eq 'ndd') {
631                     if (!defined($oppos{'v'})) {
632                         die "$fname: $line: vex.$oq without 'v' operand\n";
633                     }
634                     $has_nds = 1;
635                 } else {
636                     die "$fname: $line: undefined VEX subcode: $oq\n";
637                 }
638             }
639             if (!defined($m) || !defined($w) || !defined($l) || !defined($p)) {
640                 die "$fname: $line: missing fields in VEX specification\n";
641             }
642             if (defined($oppos{'v'}) && !$has_nds) {
643                 die "$fname: $line: 'v' operand without vex.nds or vex.ndd\n";
644             }
645             push(@codes, defined($oppos{'v'}) ? 0260+$oppos{'v'} : 0270,
646                  $m, ($w << 3)+($l << 2)+$p);
647             $prefix_ok = 0;
648         } elsif ($op =~ /^\/drex([01])$/) {
649             my $oc0 = $1;
650             if (!defined($oppos{'d'})) {
651                 die "$fname: $line: DREX without a 'd' operand\n";
652             }
653             # Note the use of *unshift* here, as opposed to *push*.
654             # This is because NASM want this byte code at the start of
655             # the instruction sequence, but the AMD documentation puts
656             # this at (roughly) the position of the drex byte itself.
657             # This allows us to match the AMD documentation and still
658             # do the right thing.
659             unshift(@codes, 0160+$oppos{'d'}+($oc0 ? 4 : 0));
660         } elsif ($op =~ /^(ib\,s|ib|ib\,w|iw|iwd|id|iwdq|rel|rel8|rel16|rel32|iq|seg|ibw|ibd|ibd,s)$/) {
661             if (!defined($oppos{'i'})) {
662                 die "$fname: $line: $op without 'i' operand\n";
663             }
664             if ($op eq 'ib,s') { # Signed imm8
665                 push(@codes, 014+$oppos{'i'});
666             } elsif ($op eq 'ib') { # imm8
667                 push(@codes, 020+$oppos{'i'});
668             } elsif ($op eq 'ib,u') { # Unsigned imm8
669                 push(@codes, 024+$oppos{'i'});
670             } elsif ($op eq 'iw') { # imm16
671                 push(@codes, 030+$oppos{'i'});
672             } elsif ($op eq 'iwd') { # imm16 or imm32, depending on opsize
673                 push(@codes, 034+$oppos{'i'});
674             } elsif ($op eq 'id') { # imm32
675                 push(@codes, 040+$oppos{'i'});
676             } elsif ($op eq 'iwdq') { # imm16/32/64, depending on opsize
677                 push(@codes, 044+$oppos{'i'});
678             } elsif ($op eq 'rel8') {
679                 push(@codes, 050+$oppos{'i'});
680             } elsif ($op eq 'iq') {
681                 push(@codes, 054+$oppos{'i'});
682             } elsif ($op eq 'rel16') {
683                 push(@codes, 060+$oppos{'i'});
684             } elsif ($op eq 'rel') { # 16 or 32 bit relative operand
685                 push(@codes, 064+$oppos{'i'});
686             } elsif ($op eq 'rel32') {
687                 push(@codes, 070+$oppos{'i'});
688             } elsif ($op eq 'seg') {
689                 push(@codes, 074+$oppos{'i'});
690             } elsif ($op eq 'ibw') { # imm16 that can be bytified
691                 if (!defined($s_pos)) {
692                     die "$fname: $line: $op without a +s byte\n";
693                 }
694                 $codes[$s_pos] += 0144;
695                 push(@codes, 0140+$oppos{'i'});
696             } elsif ($op eq 'ibd') { # imm32 that can be bytified
697                 if (!defined($s_pos)) {
698                     die "$fname: $line: $op without a +s byte\n";
699                 }
700                 $codes[$s_pos] += 0154;
701                 push(@codes, 0150+$oppos{'i'});
702             } elsif ($op eq 'ibd,s') {
703                 # imm32 that can be bytified, sign extended to 64 bits
704                 if (!defined($s_pos)) {
705                     die "$fname: $line: $op without a +s byte\n";
706                 }
707                 $codes[$s_pos] += 0154;
708                 push(@codes, 0250+$oppos{'i'});
709             }
710             $prefix_ok = 0;
711         } elsif ($op eq '/is4') {
712             if (!defined($oppos{'s'})) {
713                 die "$fname: $line: $op without 's' operand\n";
714             }
715             if (defined($oppos{'i'})) {
716                 push(@codes, 0172, ($oppos{'s'} << 3)+$oppos{'i'});
717             } else {
718                 push(@codes, 0174, $oppos{'s'});
719             }
720             $prefix_ok = 0;
721         } elsif ($op =~ /^\/is4\=([0-9]+)$/) {
722             my $imm = $1;
723             if (!defined($oppos{'s'})) {
724                 die "$fname: $line: $op without 's' operand\n";
725             }
726             if ($imm < 0 || $imm > 15) {
727                 die "$fname: $line: invalid imm4 value for $op: $imm\n";
728             }
729             push(@codes, 0173, ($oppos{'s'} << 4) + $imm);
730             $prefix_ok = 0;
731         } elsif ($op =~ /^([0-9a-f]{2})\+s$/) {
732             if (!defined($oppos{'i'})) {
733                 die "$fname: $line: $op without 'i' operand\n";
734             }
735             $s_pos = scalar @codes;
736             push(@codes, $oppos{'i'}, hex $1);
737             $prefix_ok = 0;
738         } elsif ($op =~ /^([0-9a-f]{2})\+c$/) {
739             push(@codes, 0330, hex $1);
740             $prefix_ok = 0;
741         } elsif ($op =~ /^\\([0-7]+|x[0-9a-f]{2})$/) {
742             # Escape to enter literal bytecodes
743             push(@codes, oct $1);
744         } else {
745             die "$fname: $line: unknown operation: $op\n";
746         }
747     }
748
749     return @codes;
750 }