--- /dev/null
+#!/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 program:
+# - Everything between (not including) /^---START---$/ and /^---STOP---$/
+# - But NOT including output from /^---begin SWITCHBACK/
+# to /^--- end SWITCHBACK/ inclusive
+#
+
+######################################################
+# Global consts, vars
+use constant DEBUG => 0;
+use constant CONST_N_MAX => 100000000;
+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_BAD = -1;
+my $TEST;
+
+
+
+######################################################
+# Helper functions
+
+sub Exit {
+ exit $_[0];
+}
+
+sub Usage {
+ print "Usage: binary_switchback.pl test_name <start> <last_good> <last_bad>\n";
+ print "where:\n";
+ print " test_name = obj filename - without '.o'\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 = $ARGV[0];
+
+if ( ! -e "$TEST.o" ) {
+ QuitUsage "File doesn't exist: '$TEST.o'\n";
+}
+
+if ( ! -x "$SWITCHBACK" ) {
+ QuitUsage "File doesn't exist | not executable: '$SWITCHBACK'\n";
+}
+
+if (@ARGV >1) {
+ $N_LAST_GOOD = $ARGV[1];
+ 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 basic block $n ===\n";
+
+ system("$SWITCHBACK $TEST.o 0 $n >& $TMPFILE");
+ my $ret = $?;
+
+ if ($ret & 127) {
+ print "Ctrl-C pressed - Quitting...\n";
+ 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
+ return;
+ }
+
+ open(INFILE, "$TMPFILE");
+ my @results = <INFILE>;
+ close(INFILE);
+
+ if (! DEBUG) {
+ unlink($TMPFILE);
+ }
+
+ 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 = "";
+ }
+ 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_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;
+ }
+ 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 "Get 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 {
+ 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;