xref: /onnv-gate/usr/src/cmd/perl/5.8.4/distrib/ext/B/t/assembler.t (revision 0:68f95e015346)
1#!./perl -w
2
3=pod
4
5=head1 TEST FOR B::Assembler.pm AND B::Disassembler.pm
6
7=head2 Description
8
9The general idea is to test by assembling a choice set of assembler
10instructions, then disassemble them, and check that we've completed the
11round trip. Also, error checking of Assembler.pm is tested by feeding
12it assorted errors.
13
14Since Assembler.pm likes to assemble a file, we comply by writing a
15text file. This file contains three sections:
16
17  testing operand categories
18  use each opcode
19  erronous assembler instructions
20
21An "operand category" is identified by the suffix of the PUT_/GET_
22subroutines as shown in the C<%Asmdata::insn_data> initialization, e.g.
23opcode C<ldsv> has operand category C<svindex>:
24
25   insn_data{ldsv} = [1, \&PUT_svindex, "GET_svindex"];
26
27Because Disassembler.pm also assumes input from a file, we write the
28resulting object code to a file. And disassembled output is written to
29yet another text file which is then compared to the original input.
30(Erronous assembler instructions still generate code, but this is not
31written to the object file; therefore disassembly bails out at the first
32instruction in error.)
33
34All files are kept in memory by using TIEHASH.
35
36
37=head2 Caveats
38
39An error where Assembler.pm and Disassembler.pm agree but Assembler.pm
40generates invalid object code will not be detected.
41
42Due to the way this test has been set up, failure of a single test
43could cause all subsequent tests to fail as well: After an unexpected
44assembler error no output is written, and disassembled lines will be
45out of sync for all lines thereafter.
46
47Not all possibilities for writing a valid operand value can be tested
48because disassembly results in a uniform representation.
49
50
51=head2 Maintenance
52
53New opcodes are added automatically.
54
55A new operand category will cause this program to die ("no operand list
56for XXX"). The cure is to add suitable entries to C<%goodlist> and
57C<%badlist>. (Since the data in Asmdata.pm is autogenerated, it may also
58happen that the corresponding assembly or disassembly subroutine is
59missing.) Note that an empty array as a C<%goodlist> entry means that
60opcodes of the operand category do not take an operand (and therefore the
61corresponding entry in C<%badlist> should have one). An C<undef> entry
62in C<%badlist> means that any value is acceptable (and thus there is no
63way to cause an error).
64
65Set C<$dbg> to debug this test.
66
67=cut
68
69package VirtFile;
70use strict;
71
72# Note: This is NOT a general purpose package. It implements
73# sequential text and binary file i/o in a rather simple form.
74
75sub TIEHANDLE($;$){
76    my( $class, $data ) = @_;
77    my $obj = { data => defined( $data ) ? $data : '',
78                pos => 0 };
79    return bless( $obj, $class );
80}
81
82sub PRINT($@){
83    my( $self ) = shift;
84    $self->{data} .= join( '', @_ );
85}
86
87sub WRITE($$;$$){
88    my( $self, $buf, $len, $offset ) = @_;
89    unless( defined( $len ) ){
90	$len = length( $buf );
91        $offset = 0;
92    }
93    unless( defined( $offset ) ){
94        $offset = 0;
95    }
96    $self->{data} .= substr( $buf, $offset, $len );
97    return $len;
98}
99
100
101sub GETC($){
102    my( $self ) = @_;
103    return undef() if $self->{pos} >= length( $self->{data} );
104    return substr( $self->{data}, $self->{pos}++, 1 );
105}
106
107sub READLINE($){
108    my( $self ) = @_;
109    return undef() if $self->{pos} >= length( $self->{data} );
110    my $lfpos = index( $self->{data}, "\n", $self->{pos} );
111    if( $lfpos < 0 ){
112        $lfpos = length( $self->{data} );
113    }
114    my $pos = $self->{pos};
115    $self->{pos} = $lfpos + 1;
116    return substr( $self->{data}, $pos, $self->{pos} - $pos );
117}
118
119sub READ($@){
120    my $self = shift();
121    my $bufref = \$_[0];
122    my( undef, $len, $offset ) = @_;
123    if( $offset ){
124        die( "offset beyond end of buffer\n" )
125          if ! defined( $$bufref ) || $offset > length( $$bufref );
126    } else {
127        $$bufref = '';
128        $offset = 0;
129    }
130    my $remlen = length( $self->{data} ) - $self->{pos};
131    $len = $remlen if $remlen < $len;
132    return 0 unless $len;
133    substr( $$bufref, $offset, $len ) =
134      substr( $self->{data}, $self->{pos}, $len );
135    $self->{pos} += $len;
136    return $len;
137}
138
139sub TELL($){
140    my $self = shift();
141    return $self->{pos};
142}
143
144sub CLOSE($){
145    my( $self ) = @_;
146    $self->{pos} = 0;
147}
148
1491;
150
151package main;
152
153use strict;
154use Test::More;
155use Config qw(%Config);
156
157BEGIN {
158  if (($Config{'extensions'} !~ /\bByteLoader\b/) ){
159    print "1..0 # Skip -- Perl configured without ByteLoader module\n";
160    exit 0;
161  }
162}
163
164use B::Asmdata      qw( %insn_data );
165use B::Assembler    qw( &assemble_fh );
166use B::Disassembler qw( &disassemble_fh &get_header );
167
168my( %opsByType, @code2name );
169my( $lineno, $dbg, $firstbadline, @descr );
170$dbg = 0; # debug switch
171
172# $SIG{__WARN__} handler to catch Assembler error messages
173#
174my $warnmsg;
175sub catchwarn($){
176    $warnmsg = $_[0];
177    print "error: $warnmsg\n" if $dbg;
178}
179
180# Callback for writing assembled bytes. This is where we check
181# that we do get an error.
182#
183sub putobj($){
184    if( ++$lineno >= $firstbadline ){
185        ok( $warnmsg && $warnmsg =~ /^\d+:\s/, $descr[$lineno] );
186        undef( $warnmsg );
187    } else {
188        my $l = syswrite( OBJ, $_[0] );
189    }
190}
191
192# Callback for writing a disassembled statement.
193#
194sub putdis(@){
195    my $line = join( ' ', @_ );
196    ++$lineno;
197    print DIS "$line\n";
198    printf "%5d %s\n", $lineno, $line if $dbg;
199}
200
201# Generate assembler instructions from a hash of operand types: each
202# existing entry contains a list of good or bad operand values. The
203# corresponding opcodes can be found in %opsByType.
204#
205sub gen_type($$$){
206    my( $href, $descref, $text ) = @_;
207    for my $odt ( sort( keys( %opsByType ) ) ){
208        my $opcode = $opsByType{$odt}->[0];
209	my $sel = $odt;
210	$sel =~ s/^GET_//;
211	die( "no operand list for $sel\n" ) unless exists( $href->{$sel} );
212        if( defined( $href->{$sel} ) ){
213            if( @{$href->{$sel}} ){
214		for my $od ( @{$href->{$sel}} ){
215		    ++$lineno;
216                    $descref->[$lineno] = "$text: $code2name[$opcode] $od";
217		    print ASM "$code2name[$opcode] $od\n";
218		    printf "%5d %s %s\n", $lineno, $code2name[$opcode], $od if $dbg;
219		}
220	    } else {
221		++$lineno;
222                $descref->[$lineno] = "$text: $code2name[$opcode]";
223		print ASM "$code2name[$opcode]\n";
224		printf "%5d %s\n", $lineno, $code2name[$opcode] if $dbg;
225	    }
226	}
227    }
228}
229
230# Interesting operand values
231#
232my %goodlist = (
233comment_t   => [ '"a comment"' ],  # no \n
234none        => [],
235svindex     => [ 0x7fffffff, 0 ],
236opindex     => [ 0x7fffffff, 0 ],
237pvindex     => [ 0x7fffffff, 0 ],
238U32         => [ 0xffffffff, 0 ],
239U8          => [ 0xff, 0 ],
240PV          => [ '""', '"a string"', ],
241I32         => [ -0x80000000, 0x7fffffff ],
242IV64        => [ '0x000000000', '0x0ffffffff', '0x000000001' ], # disass formats  0x%09x
243IV          => $Config{ivsize} == 4 ?
244               [ -0x80000000, 0x7fffffff ] :
245               [ '0x000000000', '0x0ffffffff', '0x000000001' ],
246NV          => [ 1.23456789E3 ],
247U16         => [ 0xffff, 0 ],
248pvcontents  => [],
249strconst    => [ '""', '"another string"' ], # no NUL
250op_tr_array => [ join( ',', 256, 0..255 ) ],
251PADOFFSET   => undef,
252long        => undef,
253	      );
254
255# Erronous operand values
256#
257my %badlist = (
258comment_t   => [ '"multi-line\ncomment"' ],  # no \n
259none        => [ '"spurious arg"'  ],
260svindex     => [ 0xffffffff * 2, -1 ],
261opindex     => [ 0xffffffff * 2, -2 ],
262pvindex     => [ 0xffffffff * 2, -3 ],
263U32         => [ 0xffffffff * 2, -4 ],
264U16         => [ 0x5ffff, -5 ],
265U8          => [ 0x6ff, -6 ],
266PV          => [ 'no quote"' ],
267I32         => [ -0x80000001, 0x80000000 ],
268IV64        => undef, # PUT_IV64 doesn't check - no integrity there
269IV          => $Config{ivsize} == 4 ?
270               [ -0x80000001, 0x80000000 ] : undef,
271NV          => undef, # PUT_NV accepts anything - it shouldn't, real-ly
272pvcontents  => [ '"spurious arg"' ],
273strconst    => [  'no quote"',  '"with NUL '."\0".' char"' ], # no NUL
274op_tr_array => undef, # op_pv_tr is no longer exactly 256 shorts
275PADOFFSET   => undef,
276long	     => undef,
277	      );
278
279
280# Determine all operand types from %Asmdata::insn_data
281#
282for my $opname ( keys( %insn_data ) ){
283    my ( $opcode, $put, $getname ) = @{$insn_data{$opname}};
284    push( @{$opsByType{$getname}}, $opcode );
285    $code2name[$opcode] = $opname;
286}
287
288
289# Write instruction(s) for correct operand values each operand type class
290#
291$lineno = 0;
292tie( *ASM, 'VirtFile' );
293gen_type( \%goodlist, \@descr, 'round trip' );
294
295# Write one instruction for each opcode.
296#
297for my $opcode ( 0..$#code2name ){
298    next unless defined( $code2name[$opcode] );
299    my $sel = $insn_data{$code2name[$opcode]}->[2];
300    $sel =~ s/^GET_//;
301    die( "no operand list for $sel\n" ) unless exists( $goodlist{$sel} );
302    if( defined( $goodlist{$sel} ) ){
303        ++$lineno;
304        if( @{$goodlist{$sel}} ){
305            my $od = $goodlist{$sel}[0];
306            $descr[$lineno] = "round trip: $code2name[$opcode] $od";
307            print ASM "$code2name[$opcode] $od\n";
308            printf "%5d %s %s\n", $lineno, $code2name[$opcode], $od if $dbg;
309        } else {
310            $descr[$lineno] = "round trip: $code2name[$opcode]";
311            print ASM "$code2name[$opcode]\n";
312            printf "%5d %s\n", $lineno, $code2name[$opcode] if $dbg;
313	}
314    }
315}
316
317# Write instruction(s) for incorrect operand values each operand type class
318#
319$firstbadline = $lineno + 1;
320gen_type( \%badlist, \@descr, 'asm error' );
321
322# invalid opcode is an odd-man-out ;-)
323#
324++$lineno;
325$descr[$lineno] = "asm error: Gollum";
326print ASM "Gollum\n";
327printf "%5d %s\n", $lineno, 'Gollum' if $dbg;
328
329close( ASM );
330
331# Now that we have defined all of our tests: plan
332#
333plan( tests => $lineno );
334print "firstbadline=$firstbadline\n" if $dbg;
335
336# assemble (guard against warnings and death from assembly errors)
337#
338$SIG{'__WARN__'} = \&catchwarn;
339
340$lineno = -1; # account for the assembly header
341tie( *OBJ, 'VirtFile' );
342eval { assemble_fh( \*ASM, \&putobj ); };
343print "eval: $@" if $dbg;
344close( ASM );
345close( OBJ );
346$SIG{'__WARN__'} = 'DEFAULT';
347
348# disassemble
349#
350print "--- disassembling ---\n" if $dbg;
351$lineno = 0;
352tie( *DIS, 'VirtFile' );
353disassemble_fh( \*OBJ, \&putdis );
354close( OBJ );
355close( DIS );
356
357# get header (for debugging only)
358#
359if( $dbg ){
360    my( $magic, $archname, $blversion, $ivsize, $ptrsize, $byteorder ) =
361        get_header();
362    printf "Magic:        0x%08x\n", $magic;
363    print  "Architecture: $archname\n";
364    print  "Byteloader V: $blversion\n";
365    print  "ivsize:       $ivsize\n";
366    print  "ptrsize:      $ptrsize\n";
367    print  "Byteorder:    $byteorder\n";
368}
369
370# check by comparing files line by line
371#
372print "--- checking ---\n" if $dbg;
373$lineno = 0;
374my( $asmline, $disline );
375while( defined( $asmline = <ASM> ) ){
376    $disline = <DIS>;
377    ++$lineno;
378    last if $lineno eq $firstbadline; # bail out where errors begin
379    ok( $asmline eq $disline, $descr[$lineno] );
380    printf "%5d %s\n", $lineno, $asmline if $dbg;
381}
382close( ASM );
383close( DIS );
384
385__END__
386