Home | History | Annotate | Download | only in switchback
      1 #!/usr/bin/env perl
      2 use strict;
      3 use warnings;
      4 
      5 ######################################################
      6 # Binary search script for switchback
      7 # Finds bad basic block for seg faults and bad output.
      8 #
      9 # To test output, you need to create test_ref
     10 # test_ref should hold the correct output for running the test_xxx program:
     11 #  - Everything between (not including) /^---START---$/ and /^---STOP---$/
     12 #  - But NOT including output from /^---begin SWITCHBACK/
     13 #    to /^---  end SWITCHBACK/ inclusive
     14 #
     15 # This script can't handle other vex output,
     16 # so e.g switchback.c::DEBUG_TRACE_FLAGS should be 0
     17 #
     18 
     19 ######################################################
     20 # Global consts, vars
     21 use constant DEBUG => 0;
     22 use constant CONST_N_MAX => 10000000000;
     23 use constant CONST_N_MUL => 2;
     24 
     25 my $SWITCHBACK = "./switchback";
     26 my $N_START = 0;
     27 my $N_LAST_GOOD = 0;
     28 my $N_LAST_BAD = -1;
     29 my $GIVEN_LAST_GOOD = -1;
     30 my $GIVEN_LAST_BAD = -1;
     31 my $TEST_REF;
     32 
     33 
     34 
     35 ######################################################
     36 # Helper functions
     37 
     38 sub Exit {
     39     exit $_[0];
     40 }
     41 
     42 sub Usage {
     43     print "Usage: binary_switchback.pl test_ref [last_good [last_bad]]\n";
     44     print "where:\n";
     45     print "   test_ref  = reference output from test_xxx\n";
     46     print "   last_good = last known good bb (search space minimum)\n";
     47     print "   last_bad  = last known bad bb (search space maximum)\n";
     48     print "\n";
     49 }
     50 
     51 sub QuitUsage {
     52     print $_[0]."\n";
     53     Usage();
     54     Exit 1;
     55 }
     56 
     57 
     58 ######################################################
     59 # Get & check cmdline args
     60 # - if given, override global vars.
     61 
     62 if (@ARGV < 1 || @ARGV > 3) {
     63     QuitUsage "Error: Bad num args\n";
     64 }
     65 
     66 $TEST_REF = $ARGV[0];
     67 
     68 if ( ! -x "$SWITCHBACK" ) {
     69     QuitUsage "File doesn't exist | not executable: '$SWITCHBACK'\n";
     70 }
     71 
     72 if (@ARGV >1) {
     73     $N_LAST_GOOD = $ARGV[1];
     74     $GIVEN_LAST_GOOD = $N_LAST_GOOD;
     75     if (! ($N_LAST_GOOD =~ /^\d*$/)) {
     76 	QuitUsage "Error: bad arg for #last_good\n";
     77     }
     78     if ($N_LAST_GOOD >= CONST_N_MAX) {
     79 	QuitUsage "Error: #last_good >= N_MAX(".CONST_N_MAX.")\n";
     80     }
     81 }
     82 if (@ARGV >2) {
     83     $N_LAST_BAD = $ARGV[2];
     84     $GIVEN_LAST_BAD = $N_LAST_BAD;
     85     if (! ($N_LAST_BAD =~ /^\d*$/)) {
     86 	QuitUsage "Error: bad arg for 'last_bad'\n";
     87     }
     88 }
     89 
     90 # Setup N_START
     91 if ($N_LAST_BAD != -1) {
     92     # Start halfway:
     93     my $diff = $N_LAST_BAD - $N_LAST_GOOD;
     94     $N_START = $N_LAST_GOOD + ($diff - ($diff % 2)) / 2;
     95 } else {
     96     # No known end: Start at beginning:
     97     if ($N_LAST_GOOD > 0) {   # User-given last_good
     98 	$N_START = $N_LAST_GOOD;
     99     } else {
    100 	$N_START = 100;       # Some reasonable number.
    101     }
    102 }
    103 
    104 ######################################################
    105 # Sanity checks (shouldn't ever happen)
    106 
    107 if ($N_START < $N_LAST_GOOD) {
    108     print "Program Error: start < last_good\n";
    109     exit 1;
    110 }
    111 if ($N_LAST_BAD != -1 && $N_START >= $N_LAST_BAD) {
    112     print "Program Error: start >= last_bad\n";
    113     exit 1;
    114 }
    115 if ($N_START < 1 || $N_START > CONST_N_MAX) {
    116     print "Program Error: Bad N_START: '$N_START'\n";
    117     exit 1;
    118 }
    119 if ($N_LAST_GOOD < 0 || $N_LAST_GOOD > CONST_N_MAX) {
    120     print "Program Error: Bad N_LAST_GOOD: '$N_LAST_GOOD'\n";
    121     exit 1;
    122 }
    123 if ($N_LAST_BAD < -1 || $N_LAST_BAD > CONST_N_MAX) {
    124     print "Program Error: Bad N_LAST_BAD: '$N_LAST_BAD'\n";
    125     exit 1;
    126 }
    127 
    128 
    129 
    130 
    131 
    132 
    133 ######################################################
    134 # Helper functions
    135 
    136 # Run switchback for test, for N bbs
    137 # returns output results
    138 sub SwitchBack {
    139     my $n = $_[0];
    140     if ($n < 0 || $n > CONST_N_MAX) {
    141 	print "Error SwitchBack: Bad N: '$n'\n";
    142 	Exit 1;
    143     }
    144     my $TMPFILE = ".switchback_output.$n";
    145 
    146     print "=== Calling switchback for bb $n ===\n";
    147 
    148     system("$SWITCHBACK $n >& $TMPFILE");
    149     my $ret = $?;
    150 
    151     if ($ret == 256) {
    152 	print "Error running switchback - Quitting...\n---\n";
    153 	open(INFILE, "$TMPFILE");
    154 	print <INFILE>;
    155 	close(INFILE);
    156 
    157 	unlink($TMPFILE) if (! DEBUG);
    158 	exit 0;	
    159     } 
    160 
    161     if ($ret & 127) {
    162 	print "Ctrl-C pressed - Quitting...\n";
    163 	unlink($TMPFILE) if (! DEBUG);
    164 	exit 0;
    165     }
    166 
    167     if (DEBUG) {
    168 	if ($ret == -1) {
    169 	    print "failed to execute: $!\n";
    170 	}
    171 	elsif ($ret & 127) {
    172 	    printf "child died with signal %d, %s coredump\n",
    173             ($ret & 127),  ($ret & 128) ? 'with' : 'without';
    174 	}
    175 	else {
    176 	    printf "child exited with value %d\n", $ret >> 8;
    177 	}
    178     }
    179     if ($ret != 0) { # Err: maybe seg fault
    180 	open(INFILE, "$TMPFILE");
    181 	my @results = <INFILE>;
    182 	close(INFILE);
    183 
    184 	while (@results && !((shift @results) =~ /^---START---/)) {}
    185 	print @results;
    186 
    187 	unlink($TMPFILE) if (! DEBUG);
    188 	return;
    189     }
    190 
    191     open(INFILE, "$TMPFILE");
    192     my @results = <INFILE>;
    193     close(INFILE);
    194 
    195     unlink($TMPFILE) if (! DEBUG);
    196     return @results;
    197 }
    198 
    199 # Returns N simulated bbs from output lines
    200 sub get_N_simulated {
    201     my @lines = @{$_[0]};
    202     pop @lines;             # not the first...
    203     my $line = pop @lines;  # ...but the second line.
    204 
    205     chomp $line;
    206     my $n;
    207     if (($n) = ($line =~ /^(\d*) bbs simulated$/)) {
    208 	return $n;
    209     }
    210     print "Error: Didn't find N bbs simultated, from output lines\n";
    211     Exit 1;
    212 }
    213 
    214 # Calls test script to compare current output lines with a reference.
    215 # Returns 1 on success, 0 on failure
    216 sub TestOutput {
    217     my @lines = @{$_[0]};
    218     my $n = $_[1];
    219     my $ref_output = "$TEST_REF";
    220 
    221     # Get the current section we want to compare:
    222     my @newlines;
    223     my $ok=0;
    224     my $halfline = "";
    225     foreach my $line(@lines) {
    226 	chomp $line;
    227 	if ($line =~ /^---STOP---$/) { last; }     # we're done
    228 
    229 	# output might be messed up here...
    230 	if ($line =~ /^.*---begin SWITCHBACK/) {
    231 	    ($halfline) = ($line =~ /^(.*)---begin SWITCHBACK/);
    232 	    $ok = 0;  # stop on prev line
    233 	}
    234 
    235 	# A valid line:
    236 	if ($ok) {
    237 	    if ($halfline ne "") {   # Fix broken line
    238 		$line = $halfline.$line;
    239 		$halfline = "";
    240 	    }
    241 
    242 	    # Ignore Vex output
    243 	    if ($line =~ /^vex /) { next; }
    244 
    245 	    push(@newlines, $line);
    246 	}
    247 
    248 	if ($line =~ /^---START---$/) {            # start on next line
    249 	    $ok = 1;
    250 	}
    251 
    252 	if ($line =~ /^---  end SWITCHBACK/) {     # start on next line
    253 	    $ok = 1;
    254 	    
    255 	}
    256     }
    257 
    258     if (DEBUG) {
    259 	open(OUTFILE, ">.filtered_output.$n");
    260 	print OUTFILE join("\n",@newlines);
    261 	close(OUTFILE);
    262     }
    263 
    264     # Read in reference lines
    265     open(REFERENCE, "$ref_output") || die "Error: Couldn't open $ref_output\n";
    266     my @ref_lines = <REFERENCE>;
    267     close(REFERENCE);
    268 
    269     # Compare reference lines with current:
    270     my $match = 1;
    271     my $i = 0;
    272     foreach my $ref_line(@ref_lines) {
    273 	chomp $ref_line;
    274 	my $line = $newlines[$i++];
    275 	chomp $line;
    276 	if ($ref_line ne $line) {
    277 	    print "\nMismatch on output:\n";
    278 	    print "ref: '$ref_line'\n";
    279 	    print "new: '$line'\n\n";
    280 	    $match = 0;
    281 	    last;
    282 	}
    283     }
    284     return $match;
    285 }
    286 
    287 
    288 
    289 
    290 
    291 
    292 ######################################################
    293 # Do the search
    294 
    295 if (DEBUG) {
    296     print "\n------------\n";
    297     print "START:  N=$N_START\n";
    298     print "START: lg=$N_LAST_GOOD\n";
    299     print "START: lb=$N_LAST_BAD\n";
    300     print "START: GIVEN_LAST_GOOD=$GIVEN_LAST_GOOD\n";
    301     print "START: GIVEN_LAST_BAD =$GIVEN_LAST_BAD\n";
    302     print "\n";
    303 }
    304 
    305 my $N = $N_START;
    306 my $success = 0;
    307 my @sb_output;
    308 while (1) {
    309     if (DEBUG) {
    310 	print "\n------------\n";
    311 	print "SOL: lg=$N_LAST_GOOD\n";
    312 	print "SOL: lb=$N_LAST_BAD\n";
    313 	print "SOL:  N=$N\n";
    314     }
    315     if ($N < 0) {
    316 	print "Error: $N<0\n";
    317 	Exit 1;
    318     }
    319 
    320     my $ok = 1;
    321     # Run switchback:
    322     @sb_output = SwitchBack($N);
    323 
    324     if (@sb_output == 0) { # Switchback failed - maybe seg fault
    325 	$ok = 0;
    326     }
    327 
    328     if (DEBUG) {
    329 	open(fileOUT, ">.retrieved_output.$N") or die("Can't open file for writing: $!");
    330 	print fileOUT @sb_output;
    331 	close(fileOUT);
    332     }
    333 
    334     # If we're ok so far (no seg faults) then test for correct output
    335     if ($ok) {
    336 	$ok = TestOutput( \@sb_output, $N );
    337     }
    338 
    339     if ($ok) {
    340 	if (get_N_simulated(\@sb_output) < $N) { # Done: No bad bbs
    341 	    $success = 1;
    342 	    last;
    343 	}
    344 	if ($N_LAST_BAD == -1) {
    345 	    # No upper bound for search space
    346 	    # Try again with a bigger N
    347 
    348 	    $N_LAST_GOOD = $N;
    349 	    $N *= CONST_N_MUL;
    350 	    if ($N > CONST_N_MAX) {
    351 		print "\nError: Maxed out N($N): N_MAX=".CONST_N_MAX."\n";
    352 		print "\nWe're either in a loop, or this is a big test program (increase N_MAX)\n\n";
    353 		Exit 1;
    354 	    }
    355 	    if (DEBUG) {
    356 		print "Looks good so far: Trying bigger N...\n\n";
    357 	    }
    358 	    next;
    359 	}
    360     }
    361 
    362     # Narrow the search space:
    363     if ($ok) { $N_LAST_GOOD = $N; }
    364     else {     $N_LAST_BAD  = $N;  }
    365 
    366     # Calculate next step:
    367     my $diff = $N_LAST_BAD - $N_LAST_GOOD;
    368     $diff = $diff - ($diff % 2);
    369     my $step = $diff / 2;
    370 
    371     if ($step < 0) {
    372 	print "Error: step = $step\n";
    373 	Exit 1;
    374     }
    375 
    376     # This our last run-through?
    377     if ($step!=0) {
    378 	$N = $N_LAST_GOOD + $step;   # Keep on going...
    379     } else {
    380 	last;                        # Get outta here
    381     }
    382 
    383     if (DEBUG) {
    384 	print "\nEOL: ok=$ok\n";
    385 	print "EOL: lg=$N_LAST_GOOD\n";
    386 	print "EOL: lb=$N_LAST_BAD\n";
    387 	print "EOL:  s=$step\n";
    388 	print "EOL:  N=$N\n";
    389     }
    390 }
    391 
    392 
    393 
    394 ######################################################
    395 # Done: Report results
    396 
    397 print "\n============================================\n";
    398 print "Done searching.\n\n";
    399 
    400 if ($N_LAST_BAD != -1 && $N != $N_LAST_BAD) {
    401     print "Getting output for last bad bb:\n";
    402     @sb_output = SwitchBack($N_LAST_BAD);
    403 }
    404 
    405 print @sb_output;
    406 print "\n\n";
    407 if ($success) {
    408     print "*** Success!  No bad bbs found. ***\n";
    409 } else {
    410     if ($N_LAST_BAD == $GIVEN_LAST_BAD) {
    411 	print "*** No failures detected within given bb range ***\n";
    412 	print " - check given 'last_bad' argument\n";
    413     } else {
    414 	if ($N_LAST_BAD == $GIVEN_LAST_GOOD) {
    415 	    print "*** Failed on bb given as last_good ***\n";
    416 	    print " - decrease the 'last_good' argument\n";
    417 	} else {
    418 	    print "*** Failure: Last failed switchback bb: $N_LAST_BAD ***\n";
    419 	    print "Hence bad bb: ". ($N_LAST_BAD - 1) ."\n";
    420 	}
    421     }
    422 }
    423 print "\n";
    424 if (DEBUG) {
    425     print "END:  N=$N\n";
    426     print "END: lg=$N_LAST_GOOD\n";
    427     print "END: lb=$N_LAST_BAD\n";
    428     print "END: GIVEN_LAST_BAD=$GIVEN_LAST_BAD\n";
    429     print "\n";
    430 }
    431 Exit 0;
    432