Bytecode patches from Benjamin Stuhl.
[p5sagit/p5-mst-13.2.git] / ext / B / B / Assembler.pm
CommitLineData
a798dbf2 1# Assembler.pm
2#
3# Copyright (c) 1996 Malcolm Beattie
4#
5# You may distribute under the terms of either the GNU General Public
6# License or the Artistic License, as specified in the README file.
7package B::Assembler;
8use Exporter;
9use B qw(ppname);
10use B::Asmdata qw(%insn_data @insn_name);
e8fcef16 11use Config qw(%Config);
a798dbf2 12
13@ISA = qw(Exporter);
14@EXPORT_OK = qw(assemble_fh assemble_insn strip_comments
e8fcef16 15 parse_statement uncstring gen_header);
a798dbf2 16
17use strict;
18my %opnumber;
19my ($i, $opname);
20for ($i = 0; defined($opname = ppname($i)); $i++) {
21 $opnumber{$opname} = $i;
22}
23
24my ($linenum, $errors);
25
26sub error {
27 my $str = shift;
28 warn "$linenum: $str\n";
29 $errors++;
30}
31
32my $debug = 0;
33sub debug { $debug = shift }
34
35#
36# First define all the data conversion subs to which Asmdata will refer
37#
38
39sub B::Asmdata::PUT_U8 {
40 my $arg = shift;
41 my $c = uncstring($arg);
42 if (defined($c)) {
43 if (length($c) != 1) {
44 error "argument for U8 is too long: $c";
45 $c = substr($c, 0, 1);
46 }
47 } else {
48 $c = chr($arg);
49 }
50 return $c;
51}
52
e8fcef16 53sub B::Asmdata::PUT_U16 { pack("S", $_[0]) }
54sub B::Asmdata::PUT_U32 { pack("L", $_[0]) }
55sub B::Asmdata::PUT_I32 { pack("L", $_[0]) }
56sub B::Asmdata::PUT_NV { sprintf("%s\0", $_[0]) } # "%lf" looses precision and pack('d',...)
57 # may not even be portable between compilers
58sub B::Asmdata::PUT_objindex { pack("L", $_[0]) } # could allow names here
cf743617 59sub B::Asmdata::PUT_svindex { &B::Asmdata::PUT_objindex }
60sub B::Asmdata::PUT_opindex { &B::Asmdata::PUT_objindex }
a798dbf2 61
62sub B::Asmdata::PUT_strconst {
63 my $arg = shift;
64 $arg = uncstring($arg);
65 if (!defined($arg)) {
66 error "bad string constant: $arg";
67 return "";
68 }
69 if ($arg =~ s/\0//g) {
70 error "string constant argument contains NUL: $arg";
71 }
72 return $arg . "\0";
73}
74
75sub B::Asmdata::PUT_pvcontents {
76 my $arg = shift;
77 error "extraneous argument: $arg" if defined $arg;
78 return "";
79}
80sub B::Asmdata::PUT_PV {
81 my $arg = shift;
82 $arg = uncstring($arg);
83 error "bad string argument: $arg" unless defined($arg);
e8fcef16 84 return pack("L", length($arg)) . $arg;
a798dbf2 85}
d53d2b93 86sub B::Asmdata::PUT_comment_t {
a798dbf2 87 my $arg = shift;
88 $arg = uncstring($arg);
89 error "bad string argument: $arg" unless defined($arg);
90 if ($arg =~ s/\n//g) {
91 error "comment argument contains linefeed: $arg";
92 }
93 return $arg . "\n";
94}
e8fcef16 95sub B::Asmdata::PUT_double { sprintf("%s\0", $_[0]) } # see PUT_NV above
a798dbf2 96sub B::Asmdata::PUT_none {
97 my $arg = shift;
98 error "extraneous argument: $arg" if defined $arg;
99 return "";
100}
101sub B::Asmdata::PUT_op_tr_array {
102 my $arg = shift;
103 my @ary = split(/\s*,\s*/, $arg);
104 if (@ary != 256) {
105 error "wrong number of arguments to op_tr_array";
106 @ary = (0) x 256;
107 }
e8fcef16 108 return pack("S256", @ary);
a798dbf2 109}
110# XXX Check this works
111sub B::Asmdata::PUT_IV64 {
112 my $arg = shift;
e8fcef16 113 return pack("LL", $arg >> 32, $arg & 0xffffffff);
a798dbf2 114}
115
116my %unesc = (n => "\n", r => "\r", t => "\t", a => "\a",
117 b => "\b", f => "\f", v => "\013");
118
119sub uncstring {
120 my $s = shift;
121 $s =~ s/^"// and $s =~ s/"$// or return undef;
122 $s =~ s/\\(\d\d\d|.)/length($1) == 3 ? chr(oct($1)) : ($unesc{$1}||$1)/eg;
123 return $s;
124}
125
126sub strip_comments {
127 my $stmt = shift;
128 # Comments only allowed in instructions which don't take string arguments
129 $stmt =~ s{
130 (?sx) # Snazzy extended regexp coming up. Also, treat
131 # string as a single line so .* eats \n characters.
132 ^\s* # Ignore leading whitespace
133 (
134 [^"]* # A double quote '"' indicates a string argument. If we
135 # find a double quote, the match fails and we strip nothing.
136 )
137 \s*\# # Any amount of whitespace plus the comment marker...
138 .*$ # ...which carries on to end-of-string.
139 }{$1}; # Keep only the instruction and optional argument.
140 return $stmt;
141}
142
e717db08 143sub gen_header { # create the ByteCode header: magic, archname, ivsize, ptrsize,
144 # byteorder
145 # nvtype irrelevant (floats are stored as strings)
e8fcef16 146 my $header = B::Asmdata::PUT_U32(0x43424c50); # 'PLBC'
e717db08 147 $header .= B::Asmdata::PUT_strconst(qq["$Config{archname}"]);
e8fcef16 148 $header .= B::Asmdata::PUT_U32($Config{ivsize});
e8fcef16 149 $header .= B::Asmdata::PUT_U32($Config{ptrsize});
e717db08 150 $header .= B::Asmdata::PUT_strconst(sprintf(qq["0x%s"], $Config{byteorder}));
151 # PV not U32 because
152 # of varying size
153
e8fcef16 154 $header;
155}
e717db08 156
a798dbf2 157sub parse_statement {
158 my $stmt = shift;
159 my ($insn, $arg) = $stmt =~ m{
160 (?sx)
161 ^\s* # allow (but ignore) leading whitespace
162 (.*?) # Instruction continues up until...
163 (?: # ...an optional whitespace+argument group
164 \s+ # first whitespace.
165 (.*) # The argument is all the rest (newlines included).
166 )?$ # anchor at end-of-line
167 };
168 if (defined($arg)) {
169 if ($arg =~ s/^0x(?=[0-9a-fA-F]+$)//) {
170 $arg = hex($arg);
171 } elsif ($arg =~ s/^0(?=[0-7]+$)//) {
172 $arg = oct($arg);
173 } elsif ($arg =~ /^pp_/) {
174 $arg =~ s/\s*$//; # strip trailing whitespace
175 my $opnum = $opnumber{$arg};
176 if (defined($opnum)) {
177 $arg = $opnum;
178 } else {
179 error qq(No such op type "$arg");
180 $arg = 0;
181 }
182 }
183 }
184 return ($insn, $arg);
185}
186
187sub assemble_insn {
188 my ($insn, $arg) = @_;
189 my $data = $insn_data{$insn};
190 if (defined($data)) {
191 my ($bytecode, $putsub) = @{$data}[0, 1];
192 my $argcode = &$putsub($arg);
193 return chr($bytecode).$argcode;
194 } else {
195 error qq(no such instruction "$insn");
196 return "";
197 }
198}
199
200sub assemble_fh {
201 my ($fh, $out) = @_;
202 my ($line, $insn, $arg);
203 $linenum = 0;
204 $errors = 0;
e8fcef16 205 &$out(gen_header());
a798dbf2 206 while ($line = <$fh>) {
207 $linenum++;
208 chomp $line;
209 if ($debug) {
210 my $quotedline = $line;
211 $quotedline =~ s/\\/\\\\/g;
212 $quotedline =~ s/"/\\"/g;
213 &$out(assemble_insn("comment", qq("$quotedline")));
214 }
215 $line = strip_comments($line) or next;
216 ($insn, $arg) = parse_statement($line);
217 &$out(assemble_insn($insn, $arg));
218 if ($debug) {
219 &$out(assemble_insn("nop", undef));
220 }
221 }
222 if ($errors) {
223 die "Assembly failed with $errors error(s)\n";
224 }
225}
226
2271;
7f20e9dd 228
229__END__
230
231=head1 NAME
232
233B::Assembler - Assemble Perl bytecode
234
235=head1 SYNOPSIS
236
237 use Assembler;
238
239=head1 DESCRIPTION
240
241See F<ext/B/B/Assembler.pm>.
242
243=head1 AUTHOR
244
245Malcolm Beattie, C<mbeattie@sable.ox.ac.uk>
246
247=cut