blob: 4b096ee92824b98ae9311e357c45ab3e0046c57b [file] [log] [blame]
#!/usr/bin/env perl
use strict;
use warnings;
######################################################
# Binary search script for switchback
# Finds bad basic block for seg faults and bad output.
#
# To test output, you need to create test_ref
# test_ref should hold the correct output for running the test_xxx program:
# - Everything between (not including) /^---START---$/ and /^---STOP---$/
# - But NOT including output from /^---begin SWITCHBACK/
# to /^--- end SWITCHBACK/ inclusive
#
# This script can't handle other vex output,
# so e.g switchback.c::DEBUG_TRACE_FLAGS should be 0
#
######################################################
# Global consts, vars
use constant DEBUG => 0;
use constant CONST_N_MAX => 10000000000;
use constant CONST_N_MUL => 2;
my $SWITCHBACK = "./switchback";
my $N_START = 0;
my $N_LAST_GOOD = 0;
my $N_LAST_BAD = -1;
my $GIVEN_LAST_GOOD = -1;
my $GIVEN_LAST_BAD = -1;
my $TEST_REF;
######################################################
# Helper functions
sub Exit {
exit $_[0];
}
sub Usage {
print "Usage: binary_switchback.pl test_ref [last_good [last_bad]]\n";
print "where:\n";
print " test_ref = reference output from test_xxx\n";
print " last_good = last known good bb (search space minimum)\n";
print " last_bad = last known bad bb (search space maximum)\n";
print "\n";
}
sub QuitUsage {
print $_[0]."\n";
Usage();
Exit 1;
}
######################################################
# Get & check cmdline args
# - if given, override global vars.
if (@ARGV < 1 || @ARGV > 3) {
QuitUsage "Error: Bad num args\n";
}
$TEST_REF = $ARGV[0];
if ( ! -x "$SWITCHBACK" ) {
QuitUsage "File doesn't exist | not executable: '$SWITCHBACK'\n";
}
if (@ARGV >1) {
$N_LAST_GOOD = $ARGV[1];
$GIVEN_LAST_GOOD = $N_LAST_GOOD;
if (! ($N_LAST_GOOD =~ /^\d*$/)) {
QuitUsage "Error: bad arg for #last_good\n";
}
if ($N_LAST_GOOD >= CONST_N_MAX) {
QuitUsage "Error: #last_good >= N_MAX(".CONST_N_MAX.")\n";
}
}
if (@ARGV >2) {
$N_LAST_BAD = $ARGV[2];
$GIVEN_LAST_BAD = $N_LAST_BAD;
if (! ($N_LAST_BAD =~ /^\d*$/)) {
QuitUsage "Error: bad arg for 'last_bad'\n";
}
}
# Setup N_START
if ($N_LAST_BAD != -1) {
# Start halfway:
my $diff = $N_LAST_BAD - $N_LAST_GOOD;
$N_START = $N_LAST_GOOD + ($diff - ($diff % 2)) / 2;
} else {
# No known end: Start at beginning:
if ($N_LAST_GOOD > 0) { # User-given last_good
$N_START = $N_LAST_GOOD;
} else {
$N_START = 100; # Some reasonable number.
}
}
######################################################
# Sanity checks (shouldn't ever happen)
if ($N_START < $N_LAST_GOOD) {
print "Program Error: start < last_good\n";
exit 1;
}
if ($N_LAST_BAD != -1 && $N_START >= $N_LAST_BAD) {
print "Program Error: start >= last_bad\n";
exit 1;
}
if ($N_START < 1 || $N_START > CONST_N_MAX) {
print "Program Error: Bad N_START: '$N_START'\n";
exit 1;
}
if ($N_LAST_GOOD < 0 || $N_LAST_GOOD > CONST_N_MAX) {
print "Program Error: Bad N_LAST_GOOD: '$N_LAST_GOOD'\n";
exit 1;
}
if ($N_LAST_BAD < -1 || $N_LAST_BAD > CONST_N_MAX) {
print "Program Error: Bad N_LAST_BAD: '$N_LAST_BAD'\n";
exit 1;
}
######################################################
# Helper functions
# Run switchback for test, for N bbs
# returns output results
sub SwitchBack {
my $n = $_[0];
if ($n < 0 || $n > CONST_N_MAX) {
print "Error SwitchBack: Bad N: '$n'\n";
Exit 1;
}
my $TMPFILE = ".switchback_output.$n";
print "=== Calling switchback for bb $n ===\n";
system("$SWITCHBACK $n >& $TMPFILE");
my $ret = $?;
if ($ret == 256) {
print "Error running switchback - Quitting...\n---\n";
open(INFILE, "$TMPFILE");
print <INFILE>;
close(INFILE);
unlink($TMPFILE) if (! DEBUG);
exit 0;
}
if ($ret & 127) {
print "Ctrl-C pressed - Quitting...\n";
unlink($TMPFILE) if (! DEBUG);
exit 0;
}
if (DEBUG) {
if ($ret == -1) {
print "failed to execute: $!\n";
}
elsif ($ret & 127) {
printf "child died with signal %d, %s coredump\n",
($ret & 127), ($ret & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $ret >> 8;
}
}
if ($ret != 0) { # Err: maybe seg fault
open(INFILE, "$TMPFILE");
my @results = <INFILE>;
close(INFILE);
while (@results && !((shift @results) =~ /^---START---/)) {}
print @results;
unlink($TMPFILE) if (! DEBUG);
return;
}
open(INFILE, "$TMPFILE");
my @results = <INFILE>;
close(INFILE);
unlink($TMPFILE) if (! DEBUG);
return @results;
}
# Returns N simulated bbs from output lines
sub get_N_simulated {
my @lines = @{$_[0]};
pop @lines; # not the first...
my $line = pop @lines; # ...but the second line.
chomp $line;
my $n;
if (($n) = ($line =~ /^(\d*) bbs simulated$/)) {
return $n;
}
print "Error: Didn't find N bbs simultated, from output lines\n";
Exit 1;
}
# Calls test script to compare current output lines with a reference.
# Returns 1 on success, 0 on failure
sub TestOutput {
my @lines = @{$_[0]};
my $n = $_[1];
my $ref_output = "$TEST_REF";
# Get the current section we want to compare:
my @newlines;
my $ok=0;
my $halfline = "";
foreach my $line(@lines) {
chomp $line;
if ($line =~ /^---STOP---$/) { last; } # we're done
# output might be messed up here...
if ($line =~ /^.*---begin SWITCHBACK/) {
($halfline) = ($line =~ /^(.*)---begin SWITCHBACK/);
$ok = 0; # stop on prev line
}
# A valid line:
if ($ok) {
if ($halfline ne "") { # Fix broken line
$line = $halfline.$line;
$halfline = "";
}
# Ignore Vex output
if ($line =~ /^vex /) { next; }
push(@newlines, $line);
}
if ($line =~ /^---START---$/) { # start on next line
$ok = 1;
}
if ($line =~ /^--- end SWITCHBACK/) { # start on next line
$ok = 1;
}
}
if (DEBUG) {
open(OUTFILE, ">.filtered_output.$n");
print OUTFILE join("\n",@newlines);
close(OUTFILE);
}
# Read in reference lines
open(REFERENCE, "$ref_output") || die "Error: Couldn't open $ref_output\n";
my @ref_lines = <REFERENCE>;
close(REFERENCE);
# Compare reference lines with current:
my $match = 1;
my $i = 0;
foreach my $ref_line(@ref_lines) {
chomp $ref_line;
my $line = $newlines[$i++];
chomp $line;
if ($ref_line ne $line) {
print "\nMismatch on output:\n";
print "ref: '$ref_line'\n";
print "new: '$line'\n\n";
$match = 0;
last;
}
}
return $match;
}
######################################################
# Do the search
if (DEBUG) {
print "\n------------\n";
print "START: N=$N_START\n";
print "START: lg=$N_LAST_GOOD\n";
print "START: lb=$N_LAST_BAD\n";
print "START: GIVEN_LAST_GOOD=$GIVEN_LAST_GOOD\n";
print "START: GIVEN_LAST_BAD =$GIVEN_LAST_BAD\n";
print "\n";
}
my $N = $N_START;
my $success = 0;
my @sb_output;
while (1) {
if (DEBUG) {
print "\n------------\n";
print "SOL: lg=$N_LAST_GOOD\n";
print "SOL: lb=$N_LAST_BAD\n";
print "SOL: N=$N\n";
}
if ($N < 0) {
print "Error: $N<0\n";
Exit 1;
}
my $ok = 1;
# Run switchback:
@sb_output = SwitchBack($N);
if (@sb_output == 0) { # Switchback failed - maybe seg fault
$ok = 0;
}
if (DEBUG) {
open(fileOUT, ">.retrieved_output.$N") or die("Can't open file for writing: $!");
print fileOUT @sb_output;
close(fileOUT);
}
# If we're ok so far (no seg faults) then test for correct output
if ($ok) {
$ok = TestOutput( \@sb_output, $N );
}
if ($ok) {
if (get_N_simulated(\@sb_output) < $N) { # Done: No bad bbs
$success = 1;
last;
}
if ($N_LAST_BAD == -1) {
# No upper bound for search space
# Try again with a bigger N
$N_LAST_GOOD = $N;
$N *= CONST_N_MUL;
if ($N > CONST_N_MAX) {
print "\nError: Maxed out N($N): N_MAX=".CONST_N_MAX."\n";
print "\nWe're either in a loop, or this is a big test program (increase N_MAX)\n\n";
Exit 1;
}
if (DEBUG) {
print "Looks good so far: Trying bigger N...\n\n";
}
next;
}
}
# Narrow the search space:
if ($ok) { $N_LAST_GOOD = $N; }
else { $N_LAST_BAD = $N; }
# Calculate next step:
my $diff = $N_LAST_BAD - $N_LAST_GOOD;
$diff = $diff - ($diff % 2);
my $step = $diff / 2;
if ($step < 0) {
print "Error: step = $step\n";
Exit 1;
}
# This our last run-through?
if ($step!=0) {
$N = $N_LAST_GOOD + $step; # Keep on going...
} else {
last; # Get outta here
}
if (DEBUG) {
print "\nEOL: ok=$ok\n";
print "EOL: lg=$N_LAST_GOOD\n";
print "EOL: lb=$N_LAST_BAD\n";
print "EOL: s=$step\n";
print "EOL: N=$N\n";
}
}
######################################################
# Done: Report results
print "\n============================================\n";
print "Done searching.\n\n";
if ($N_LAST_BAD != -1 && $N != $N_LAST_BAD) {
print "Getting output for last bad bb:\n";
@sb_output = SwitchBack($N_LAST_BAD);
}
print @sb_output;
print "\n\n";
if ($success) {
print "*** Success! No bad bbs found. ***\n";
} else {
if ($N_LAST_BAD == $GIVEN_LAST_BAD) {
print "*** No failures detected within given bb range ***\n";
print " - check given 'last_bad' argument\n";
} else {
if ($N_LAST_BAD == $GIVEN_LAST_GOOD) {
print "*** Failed on bb given as last_good ***\n";
print " - decrease the 'last_good' argument\n";
} else {
print "*** Failure: Last failed switchback bb: $N_LAST_BAD ***\n";
print "Hence bad bb: ". ($N_LAST_BAD - 1) ."\n";
}
}
}
print "\n";
if (DEBUG) {
print "END: N=$N\n";
print "END: lg=$N_LAST_GOOD\n";
print "END: lb=$N_LAST_BAD\n";
print "END: GIVEN_LAST_BAD=$GIVEN_LAST_BAD\n";
print "\n";
}
Exit 0;