File Coverage

blib/lib/B/Assembler.pm
Criterion Covered Total %
statement 176 235 74.8
branch 58 140 41.4
condition 10 34 29.4
subroutine 36 46 78.2
pod 0 40 0.0
total 280 495 56.5


line stmt bran cond sub pod time code
1             # Assembler.pm
2             #
3             # Copyright (c) 1996 Malcolm Beattie
4             # Copyright (c) 2008,2009,2010,2011,2012 Reini Urban
5             # Copyright (c) 2014 cPanel Inc
6             #
7             # You may distribute under the terms of either the GNU General Public
8             # License or the Artistic License, as specified in the README file.
9              
10             package B::Assembler;
11 1     1   957 use Exporter;
  1         3  
  1         54  
12 1     1   8 use B qw(ppname);
  1         2  
  1         64  
13 1     1   8 use B::Asmdata qw(%insn_data @insn_name);
  1         2  
  1         102  
14 1     1   7 use Config qw(%Config);
  1         3  
  1         63  
15             require ByteLoader; # we just need its $VERSION
16              
17 1     1   8 no warnings; # XXX
  1         3  
  1         110  
18              
19             @ISA = qw(Exporter);
20             our @EXPORT_OK = qw(assemble_fh newasm endasm assemble asm maxopix maxsvix);
21             our $VERSION = '1.13';
22              
23 1     1   9 use strict;
  1         3  
  1         4949  
24             my %opnumber;
25             my ( $i, $opname );
26             for ( $i = 0 ; defined( $opname = ppname($i) ) ; $i++ ) {
27             $opnumber{$opname} = $i;
28             }
29              
30             my ( $linenum, $errors, $out ); # global state, set up by newasm
31              
32             sub error {
33 23     23 0 30 my $str = shift;
34 23         183 warn "$linenum: $str\n";
35 23         123 $errors++;
36             }
37              
38             my $debug = 0;
39 0     0 0 0 sub debug { $debug = shift }
40             my $quiet = 0;
41 0     0 0 0 sub quiet { $quiet = shift }
42             my ( $maxopix, $maxsvix ) = ( 0xffffffff, 0xffffffff );
43 0     0 0 0 sub maxopix { $maxopix = shift }
44 0     0 0 0 sub maxsvix { $maxsvix = shift }
45              
46             sub limcheck($$$$) {
47 161     161 0 373 my ( $val, $lo, $hi, $loc ) = @_;
48 161 100 100     667 if ( $val < $lo || $hi < $val ) {
49 16         63 error "argument for $loc outside [$lo, $hi]: $val";
50 16         22 $val = $hi;
51             }
52 161         396 return $val;
53             }
54              
55             #
56             # First define all the data conversion subs to which Asmdata will refer
57             #
58              
59             sub B::Asmdata::PUT_U8 {
60 30 50   30 0 69 error "Missing argument to PUT_U8" if @_ < 1;
61 30         51 my $arg = shift;
62 30         64 my $c = uncstring($arg);
63 30 50       77 if ( defined($c) ) {
64 0 0       0 if ( length($c) != 1 ) {
65 0         0 error "argument for U8 is too long: $c";
66 0         0 $c = substr( $c, 0, 1 );
67             }
68             }
69             else {
70 30         64 $arg = limcheck( $arg, 0, 0xff, 'U8' );
71 30         62 $c = chr($arg);
72             }
73 30         64 return $c;
74             }
75              
76             sub B::Asmdata::PUT_U16 {
77 9 50   9 0 24 error "Missing argument to PUT_U16" if @_ < 1;
78 9         20 my $arg = limcheck( $_[0], 0, 0xffff, 'U16' );
79 9         35 pack( "S", $arg );
80             }
81              
82             sub B::Asmdata::PUT_U32 {
83 34 50   34 0 83 error "Missing argument to PUT_U32" if @_ < 1;
84 34         77 my $arg = limcheck( $_[0], 0, 0xffffffff, 'U32' );
85 34         125 pack( "L", $arg );
86             }
87              
88             sub B::Asmdata::PUT_I32 {
89 7 50   7 0 25 error "Missing argument to PUT_I32" if @_ < 1;
90 7         21 my $arg = limcheck( $_[0], -0x80000000, 0x7fffffff, 'I32' );
91 7         24 pack( "l", $arg );
92             }
93              
94             sub B::Asmdata::PUT_NV {
95 2 50   2 0 9 error "Missing argument to PUT_NV" if @_ < 1;
96 2         10 sprintf( "%s\0", $_[0] );
97             } # "%lf" looses precision and pack('d',...)
98             # may not even be portable between compilers
99              
100             sub B::Asmdata::PUT_objindex { # could allow names here
101 81 50   81 0 191 error "Missing argument to PUT_objindex" if @_ < 1;
102 81   50     449 my $maxidx = $_[1] || 0xffffffff;
103 81   50     197 my $what = $_[2] || 'ix';
104 81         175 my $arg = limcheck( $_[0], 0, $maxidx, $what );
105 81         287 pack( "L", $arg );
106             }
107 50     50 0 111 sub B::Asmdata::PUT_svindex { B::Asmdata::PUT_objindex( @_, $maxsvix, 'svix' ) }
108 19     19 0 44 sub B::Asmdata::PUT_opindex { B::Asmdata::PUT_objindex( @_, $maxopix, 'opix' ) }
109 12     12 0 27 sub B::Asmdata::PUT_pvindex { B::Asmdata::PUT_objindex( @_, $maxsvix, 'pvix' ) }
110 0     0 0 0 sub B::Asmdata::PUT_hekindex { B::Asmdata::PUT_objindex( @_ ) }
111              
112             sub B::Asmdata::PUT_strconst {
113 17 50   17 0 45 error "Missing argument to PUT_strconst" if @_ < 1;
114 17         33 my $arg = shift;
115 17         46 my $str = uncstring($arg);
116 17 100       42 if ( !defined($str) ) {
117 1         8 my @callstack = caller(3);
118 1 50       9 error "bad string constant: '$arg', called from ".$callstack[3]
119             ." line:".$callstack[2] unless $callstack[3] eq 'B::PADNAME::ix'; # empty newpadnx
120 1         4 $str = '';
121             }
122 17 100       49 if ( $str =~ s/\0//g ) {
123 1         6 error "string constant argument contains NUL: $arg";
124 1         3 $str = '';
125             }
126 17         45 return $str . "\0";
127             }
128              
129             # expects the string argument already on the "stack" (with depth 1, one sv)
130             sub B::Asmdata::PUT_pvcontents {
131 5     5 0 9 my $arg = shift;
132 5 100       18 error "extraneous argument to pvcontents: $arg" if defined $arg;
133 5         13 return "";
134             }
135              
136             sub B::Asmdata::PUT_PV {
137 4 50   4 0 13 error "Missing argument to PUT_PV" if @_ < 1;
138 4         9 my $arg = shift;
139 4         11 my $str = uncstring($arg);
140 4 100       14 if ( !defined($str) ) {
141 1         4 error "bad string argument: $arg";
142 1         2 $str = '';
143             }
144 4         18 return pack( "L", length($str) ) . $str;
145             }
146              
147             sub B::Asmdata::PUT_comment_t {
148 3     3 0 7 my $arg = shift;
149 3         8 $arg = uncstring($arg);
150 3 50       12 error "bad string argument: $arg" unless defined($arg);
151 3 100       12 if ( $arg =~ s/\n//g ) {
152 1         4 error "comment argument contains linefeed: $arg";
153             }
154 3         12 return $arg . "\n";
155             }
156             sub B::Asmdata::PUT_double {
157 0 0   0 0 0 error "Missing argument to PUT_double" if @_ < 1;
158 0         0 sprintf( "%s\0", $_[0] )
159             } # see PUT_NV above
160              
161             sub B::Asmdata::PUT_none {
162 7     7 0 14 my $arg = shift;
163 7 100       22 error "extraneous argument: $arg" if defined $arg;
164 7         14 return "";
165             }
166              
167             sub B::Asmdata::PUT_op_tr_array {
168 2 50   2 0 158 error "Missing argument to PUT_tr_array" if @_ < 1;
169 2         396 my @ary = split /\s*,\s*/, shift;
170 2         134 return pack "S*", @ary;
171             }
172              
173             sub B::Asmdata::PUT_IV64 {
174 9 50   9 0 25 error "Missing argument to PUT_IV64" if @_ < 1;
175 9         31 return pack "Q", shift;
176             }
177              
178             sub B::Asmdata::PUT_IV {
179 9 50   9 0 74 $Config{ivsize} == 4 ? &B::Asmdata::PUT_I32 : &B::Asmdata::PUT_IV64;
180             }
181              
182             sub B::Asmdata::PUT_PADOFFSET {
183 0 0   0 0 0 $Config{ptrsize} == 8 ? &B::Asmdata::PUT_IV64 : &B::Asmdata::PUT_U32;
184             }
185              
186             sub B::Asmdata::PUT_long {
187 0 0   0 0 0 $Config{longsize} == 8 ? &B::Asmdata::PUT_IV64 : &B::Asmdata::PUT_U32;
188             }
189              
190             sub B::Asmdata::PUT_svtype {
191 0 0   0 0 0 $Config{longsize} == 8 ? &B::Asmdata::PUT_IV64 : &B::Asmdata::PUT_U32;
192             }
193              
194             sub B::Asmdata::PUT_pmflags {
195 5 50   5 0 22 return ($] < 5.013) ? B::Asmdata::PUT_U16(@_) : B::Asmdata::PUT_U32(@_);
196             }
197              
198             my %unesc = (
199             n => "\n",
200             r => "\r",
201             t => "\t",
202             a => "\a",
203             b => "\b",
204             f => "\f",
205             v => "\013"
206             );
207              
208             sub uncstring {
209 54     54 0 94 my $s = shift;
210 54 100 66     310 $s =~ s/^"// and $s =~ s/"$// or return undef;
211 22 50 33     69 $s =~ s/\\(\d\d\d|.)/length($1) == 3 ? chr(oct($1)) : ($unesc{$1}||$1)/eg;
  1         11  
212 22         53 return $s;
213             }
214              
215             sub strip_comments {
216 202     202 0 333 my $stmt = shift;
217              
218             # Comments only allowed in instructions which don't take string arguments
219             # Treat string as a single line so .* eats \n characters.
220 202         323 my $line = $stmt;
221 202         349 $stmt =~ s{
222             ^\s* # Ignore leading whitespace
223             (
224             [^"]* # A double quote '"' indicates a string argument. If we
225             # find a double quote, the match fails and we strip nothing.
226             )
227             \s*\# # Any amount of whitespace plus the comment marker...
228             \s*(.*)$ # ...which carries on to end-of-string.
229             }{$1}sx; # Keep only the instruction and optional argument.
230 202 50       612 return ($stmt) if $line eq $stmt;
231              
232 0         0 $stmt =~ m{
233             ^\s*
234             (
235             [^"]*
236             )
237             \s*\#
238             \s*(.*)$
239             }sx; # Keep only the instruction and optional argument.
240 0         0 my ( $line, $comment ) = ( $1, $2 );
241              
242             # $line =~ s/\t$// if $comment;
243 0         0 return ( $line, $comment );
244             }
245              
246             # create the ByteCode header:
247             # magic, archname, ByteLoader $VERSION, ivsize, ptrsize, longsize, byteorder,
248             # archflag, perlversion
249             # byteorder is strconst, not U32 because of varying size issues (?)
250             # archflag: bit 1: useithreads, bit 2: multiplicity
251             # perlversion for the bytecode translation.
252              
253             sub gen_header {
254 1     1 0 6 my $header = gen_header_hash();
255 1         5 my $string = "";
256 1         6 $string .= B::Asmdata::PUT_U32( $header->{magic} );
257 1         10 $string .= B::Asmdata::PUT_strconst( '"' . $header->{archname} . '"' );
258 1         7 $string .= B::Asmdata::PUT_strconst( '"' . $header->{blversion} . '"' );
259 1         5 $string .= B::Asmdata::PUT_U32( $header->{ivsize} );
260 1         5 $string .= B::Asmdata::PUT_U32( $header->{ptrsize} );
261 1 50       10 if ( exists $header->{longsize} ) {
262 1         6 $string .= B::Asmdata::PUT_U32( $header->{longsize} );
263             }
264 1         7 $string .= B::Asmdata::PUT_strconst( sprintf(qq["0x%s"], $header->{byteorder} ));
265 1 50       5 if ( exists $header->{archflag} ) {
266 1         6 $string .= B::Asmdata::PUT_U16( $header->{archflag} );
267             }
268 1 50       6 if ( exists $header->{perlversion} ) {
269 1         5 $string .= B::Asmdata::PUT_strconst( '"' . $header->{perlversion} . '"');
270             }
271 1         14 $string;
272             }
273              
274             # Calculate the ByteCode header values:
275             # magic, archname, ByteLoader $VERSION, ivsize, ptrsize, longsize, byteorder
276             # archflag, perlversion
277             # nvtype is irrelevant (floats are stored as strings)
278             # byteorder is strconst, not U32 because of varying size issues (?)
279             # archflag: bit 1: useithreads, bit 2: multiplicity
280             # perlversion for the bytecode translation.
281              
282             sub gen_header_hash {
283 1     1 0 3 my $header = {};
284 1         5 my $blversion = "$ByteLoader::VERSION";
285             #if ($] < 5.009 and $blversion eq '0.06_01') {
286             # $blversion = '0.06';# fake the old backwards compatible version
287             #}
288 1         5 $header->{magic} = 0x43424c50;
289 1         20 $header->{archname} = $Config{archname};
290 1         7 $header->{blversion} = $blversion;
291 1         10 $header->{ivsize} = $Config{ivsize};
292 1         89 $header->{ptrsize} = $Config{ptrsize};
293 1 50       10 if ( $blversion ge "0.06_03" ) {
294 1         67 $header->{longsize} = $Config{longsize};
295             }
296 1         34 my $byteorder = $Config{byteorder};
297 1 50       10 if ($] < 5.007) {
298             # until 5.6 the $Config{byteorder} was dependent on ivsize, which was wrong. we need longsize.
299 0         0 my $t = $Config{ivtype};
300 0         0 my $s = $Config{longsize};
301 0 0       0 my $f = $t eq 'long' ? 'L!' : $s == 8 ? 'Q': 'I';
    0          
302 0 0 0     0 if ($s == 4 || $s == 8) {
303 0         0 my $i = 0;
304 0         0 foreach my $c (reverse(2..$s)) { $i |= ord($c); $i <<= 8 }
  0         0  
  0         0  
305 0         0 $i |= ord(1);
306 0         0 $byteorder = join('', unpack('a'x$s, pack($f, $i)));
307             } else {
308 0         0 $byteorder = '?'x$s;
309             }
310             }
311 1         4 $header->{byteorder} = $byteorder;
312 1 50       5 if ( $blversion ge "0.06_05" ) {
313 1         4 my $archflag = 0;
314 1 50       14 $archflag += 1 if $Config{useithreads};
315 1 50       57 $archflag += 2 if $Config{usemultiplicity};
316 1         7 $header->{archflag} = $archflag;
317             }
318 1 50       6 if ( $blversion ge "0.06_06" ) {
319 1         4 $header->{perlversion} = $];
320             }
321 1         4 $header;
322             }
323              
324             sub parse_statement {
325 202     202 0 356 my $stmt = shift;
326 202         1430 my ( $insn, $arg ) = $stmt =~ m{
327             ^\s* # allow (but ignore) leading whitespace
328             (.*?) # Ignore -S op groups. Instruction continues up until...
329             (?: # ...an optional whitespace+argument group
330             \s+ # first whitespace.
331             (.*) # The argument is all the rest (newlines included).
332             )?$ # anchor at end-of-line
333             }sx;
334 202 100       596 if ( defined($arg) ) {
335 191 100       692 if ( $arg =~ s/^0x(?=[0-9a-fA-F]+$)// ) {
    50          
    50          
336 9         26 $arg = hex($arg);
337             }
338             elsif ( $arg =~ s/^0(?=[0-7]+$)// ) {
339 0         0 $arg = oct($arg);
340             }
341             elsif ( $arg =~ /^pp_/ ) {
342 0         0 $arg =~ s/\s*$//; # strip trailing whitespace
343 0         0 my $opnum = $opnumber{$arg};
344 0 0       0 if ( defined($opnum) ) {
345 0         0 $arg = $opnum;
346             }
347             else {
348             # TODO: ignore [op] from O=Bytecode,-S
349 0         0 error qq(No such op type "$arg");
350 0         0 $arg = 0;
351             }
352             }
353             }
354 202         565 return ( $insn, $arg );
355             }
356              
357             sub assemble_insn {
358 202     202 0 406 my ( $insn, $arg ) = @_;
359 202         486 my $data = $insn_data{$insn};
360 202 100       395 if ( defined($data) ) {
361 201         308 my ( $bytecode, $putsub ) = @{$data}[ 0, 1 ];
  201         502  
362 201 50       448 error qq(unsupported instruction "$insn") unless $putsub;
363 201 50       429 return "" unless $putsub;
364 201         422 my $argcode = &$putsub($arg);
365 201         957 return chr($bytecode) . $argcode;
366             }
367             else {
368 1         55 error qq(no such instruction "$insn");
369 1         3 return "";
370             }
371             }
372              
373             sub assemble_fh {
374 1     1 0 7134 my ( $fh, $out ) = @_;
375 1         3 my $line;
376 1         7 my $asm = newasm($out);
377 1         40 while ( $line = <$fh> ) {
378 202         2833 assemble($line);
379             }
380 1         42 endasm();
381             }
382              
383             sub newasm {
384 1     1 0 4 my ($outsub) = @_;
385              
386 1 50       8 die "Invalid printing routine for B::Assembler\n"
387             unless ref $outsub eq 'CODE';
388 1 50       7 die <
389             Can't have multiple byteassembly sessions at once!
390             (perhaps you forgot an endasm()?)
391             EOD
392              
393 1         3 $linenum = $errors = 0;
394 1         3 $out = $outsub;
395              
396 1         5 $out->( gen_header() );
397             }
398              
399             sub endasm {
400 1 50   1 0 6 if ($errors) {
401 1         9 die "There were $errors assembly errors\n";
402             }
403 0         0 $linenum = $errors = $out = 0;
404             }
405              
406             ### interface via whole line, and optional comments
407             sub assemble {
408 202     202 0 463 my ($line) = @_;
409 202         598 my ( $insn, $arg, $comment );
410 202         337 $linenum++;
411 202         371 chomp $line;
412 202         424 $line =~ s/\cM$//;
413 202 50       476 if ($debug) {
414 0         0 my $quotedline = $line;
415 0         0 $quotedline =~ s/\\/\\\\/g;
416 0         0 $quotedline =~ s/"/\\"/g;
417 0         0 $out->( assemble_insn( "comment", qq("$quotedline") ) );
418             }
419 202         394 ( $line, $comment ) = strip_comments($line);
420 202 50 0     471 if ($line) {
    0          
421 202         405 ( $insn, $arg ) = parse_statement($line);
422 202 0 33     486 if ($debug and !$comment and $insn =~ /_flags/) {
      33        
423 0         0 $comment = sprintf("0x%x", $arg);
424             }
425 202         421 $out->( assemble_insn( $insn, $arg, $comment ) );
426 202 50       11544 if ($debug) {
427 0           $out->( assemble_insn( "nop", undef ) );
428             }
429             }
430             elsif ( $debug and $comment ) {
431 0           $out->( assemble_insn( "nop", undef, $comment ) );
432             }
433             }
434              
435             ### temporary workaround
436             ### interface via 2-3 args
437              
438             sub asm ($;$$) {
439 0 0   0 0   return if $_[0] =~ /\s*\W/;
440 0 0         if ( defined $_[1] ) {
441             return
442 0 0 0       if $_[1] eq "0"
443             and $_[0] !~ /^(?:ldsv|stsv|newsvx?|newpad.*|av_pushx?|av_extend|xav_flags)$/;
444 0 0 0       return if $_[1] eq "1" and $]>5.007 and $_[0] =~ /^(?:sv_refcnt)$/;
      0        
445             }
446 0           my ( $insn, $arg, $comment ) = @_;
447 0 0         if ($] < 5.007) {
448 0 0         if ($insn eq "newsvx") {
    0          
    0          
    0          
    0          
    0          
    0          
449 0           $arg = $arg & 0xff; # sv not SVt_NULL
450 0           $insn = "newsv";
451             # XXX but this needs stsv tix-1 also
452             } elsif ($insn eq "newopx") {
453 0           $insn = "newop";
454             } elsif ($insn eq "av_pushx") {
455 0           $insn = "av_push";
456             } elsif ($insn eq "ldspecsvx") {
457 0           $insn = "ldspecsv";
458             } elsif ($insn eq "gv_stashpvx") {
459 0           $insn = "gv_stashpv";
460             } elsif ($insn eq "gv_fetchpvx") {
461 0           $insn = "gv_fetchpv";
462             } elsif ($insn eq "main_cv") {
463 0           return;
464             }
465             }
466 0           $out->( assemble_insn( $insn, $arg, $comment ) );
467 0           $linenum++;
468              
469             # assemble "@_";
470             }
471              
472             1;
473              
474             __END__