Merge pull request #6251 from gottesmm/bug_reducer

[sil-bug-reducer] Initial version of bug_reducer.
diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def
index 2c6db74..24a5841 100644
--- a/include/swift/SILOptimizer/PassManager/Passes.def
+++ b/include/swift/SILOptimizer/PassManager/Passes.def
@@ -237,7 +237,9 @@
      "Builtin.unsafeGuaranteed")
 PASS(UsePrespecialized, "use-prespecialized",
      "Use pre-specialized functions")
-PASS_RANGE(AllPasses, AADumper, UsePrespecialized)
+PASS(BugReducerTester, "bug-reducer-tester",
+     "Utility pass for testing sil-bug-reducer. Asserts when visits an apply that calls a specific function")
+PASS_RANGE(AllPasses, AADumper, BugReducerTester)
 
 #undef PASS
 #undef PASS_RANGE
diff --git a/lib/SILOptimizer/UtilityPasses/BugReducerTester.cpp b/lib/SILOptimizer/UtilityPasses/BugReducerTester.cpp
new file mode 100644
index 0000000..8c2b2fb
--- /dev/null
+++ b/lib/SILOptimizer/UtilityPasses/BugReducerTester.cpp
@@ -0,0 +1,58 @@
+//===--- BugReducerTester.cpp ---------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+///
+/// This pass is a testing pass for sil-bug-reducer. It asserts when it visits a
+/// function that calls a function specified by an llvm::cl::opt.
+///
+//===----------------------------------------------------------------------===//
+
+#include "swift/SIL/SILFunction.h"
+#include "swift/SIL/SILInstruction.h"
+#include "swift/SILOptimizer/PassManager/Passes.h"
+#include "swift/SILOptimizer/PassManager/Transforms.h"
+#include "llvm/Support/CommandLine.h"
+
+using namespace swift;
+
+static llvm::cl::opt<std::string> FunctionTarget(
+    "bug-reducer-tester-target-func",
+    llvm::cl::desc("Function that when called by an apply should cause "
+                   "BugReducerTester to blow up if the pass visits the apply"));
+
+namespace {
+
+class BugReducerTester : public SILFunctionTransform {
+
+  void run() override {
+    if (FunctionTarget.empty())
+      return;
+    for (auto &BB : *getFunction()) {
+      for (auto &II : BB) {
+        auto *Apply = dyn_cast<ApplyInst>(&II);
+        if (!Apply)
+          continue;
+        FullApplySite FAS(Apply);
+        if (!FAS || !FAS.getCalleeFunction()->getName().equals(FunctionTarget))
+          continue;
+        llvm_unreachable("Found the target!");
+      }
+    }
+  }
+
+  StringRef getName() override { return "Bug Reducer Tester"; }
+};
+
+} // end anonymous namespace
+
+SILTransform *swift::createBugReducerTester() { return new BugReducerTester(); }
diff --git a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt
index ad58b3b..7dbbc42 100644
--- a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt
+++ b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt
@@ -2,6 +2,7 @@
   UtilityPasses/AADumper.cpp
   UtilityPasses/BasicCalleePrinter.cpp
   UtilityPasses/BasicInstructionPropertyDumper.cpp
+  UtilityPasses/BugReducerTester.cpp
   UtilityPasses/CallerAnalysisPrinter.cpp
   UtilityPasses/CFGPrinter.cpp
   UtilityPasses/ComputeDominanceInfo.cpp
diff --git a/test/SILOptimizer/bug-reducer-tester.sil b/test/SILOptimizer/bug-reducer-tester.sil
new file mode 100644
index 0000000..51be484
--- /dev/null
+++ b/test/SILOptimizer/bug-reducer-tester.sil
@@ -0,0 +1,22 @@
+// RUN: not --crash %target-sil-opt -bug-reducer-tester %s -bug-reducer-tester-target-func='target_func'
+// RUN: %target-sil-opt -bug-reducer-tester %s -bug-reducer-tester-target-func='target_func_2' | %FileCheck %s
+// RUN: %target-sil-opt -bug-reducer-tester %s | %FileCheck %s
+sil_stage canonical
+
+// CHECK-LABEL: sil @target_func : $@convention(thin) () -> () {
+sil @target_func : $@convention(thin) () -> () {
+bb0:
+  %9999 = tuple()
+  return %9999 : $()
+}
+
+// CHECK-LABEL: sil @func_2 : $@convention(thin) () -> () {
+// CHECK: [[FUNC:%.*]] = function_ref @target_func : $@convention(thin) () -> ()
+// CHECK: apply [[FUNC]]()
+sil @func_2 : $@convention(thin) () -> () {
+bb0:
+  %0 = function_ref @target_func : $@convention(thin) () -> ()
+  apply %0() : $@convention(thin) () -> ()
+  %9999 = tuple()
+  return %9999 : $()
+}
diff --git a/utils/bug_reducer/README.md b/utils/bug_reducer/README.md
new file mode 100644
index 0000000..a42a679
--- /dev/null
+++ b/utils/bug_reducer/README.md
@@ -0,0 +1,44 @@
+
+# SIL Bug Reducer
+
+This directory contains a script called bug_reducer. It is a reimplementation of
+llvm's bugpoint using a python script + high level IR utilities. This will
+enable bugpoint to easily be ported to newer IRs.
+
+An example invocation would be:
+
+./swift/utils/bug-reducer/bug_reducer.py \
+    opt \
+    --sdk=$(xcrun --sdk macosx --toolchain default --show-sdk-path) \
+    --module-name=${MODULE_NAME} \
+    --work-dir=${PWD}/bug_reducer \
+    --module-cache=${PWD}/bug_reducer/module-cache \
+    ${SWIFT_BUILD_DIR} \
+    ${SIB_FILE}
+
+This is still very alpha and needs a lot of work including tests, so please do
+not expect it to work at all until tests are available. Once testing is
+available, please only use this if you are willing to tolerate bugs (and
+hopefully help fix them/add tests).
+
+# Tasks
+
+1. A lot of this code was inspired by llvm's bisect tool. This includes a bit of
+   the code style, we should clean this up and pythonify these parts of the
+   tool.
+2. Once this is done, we should consider implementing function reducing
+   functionality. We already have sil-func-extractor which can reduce functions
+   in modules just how a bugpoint tool would like. We should wire up code to use
+   that tool to reduce functions. After this point, our tool should be good
+   enough for reducing test cases. Later we can implement block/instruction
+   extraction, but this is a first step.
+3. Then we need to implement miscompile detection support. This implies
+   implementing support for codegening code from this tool using swiftc. This
+   implies splitting modules into optimized and unoptimized parts. Luckily,
+   sil-func-extractor can perform this task modulo one task*.
+
+* Specifically, we need to be able to create internal shims that call shared
+  functions so that if a shared function is partitioned on the opposite side of
+  any of its call sites, we can avoid having to create a shared function
+  declaration in the other partition. We avoid this since creating shared
+  function declarations is illegal in SIL.
diff --git a/utils/bug_reducer/bug_reducer/__init__.py b/utils/bug_reducer/bug_reducer/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/__init__.py
diff --git a/utils/bug_reducer/bug_reducer/bug_reducer.py b/utils/bug_reducer/bug_reducer/bug_reducer.py
new file mode 100755
index 0000000..95d87d6
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/bug_reducer.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+import argparse
+
+import opt_bug_reducer
+
+import random_bug_finder
+
+
+def main():
+    parser = argparse.ArgumentParser(description="""\
+A program for reducing sib/sil crashers""")
+    subparsers = parser.add_subparsers()
+
+    opt_subparser = subparsers.add_parser("opt")
+    opt_subparser.add_argument('swift_build_dir',
+                               help='Path to the swift build directory '
+                               'containing tools to use')
+    opt_bug_reducer.add_parser_arguments(opt_subparser)
+
+    random_search_subparser = subparsers.add_parser("random-search")
+    random_search_subparser.add_argument('swift_build_dir',
+                                         help='Path to the swift build '
+                                         'directory containing tools to use')
+    random_bug_finder.add_parser_arguments(random_search_subparser)
+
+    args = parser.parse_args()
+    args.func(args)
+
+
+main()
diff --git a/utils/bug_reducer/bug_reducer/bug_reducer_utils.py b/utils/bug_reducer/bug_reducer/bug_reducer_utils.py
new file mode 100644
index 0000000..a23d6e6
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/bug_reducer_utils.py
@@ -0,0 +1,155 @@
+
+import json
+import os
+import subprocess
+
+DRY_RUN = False
+
+
+def br_call(args):
+    if DRY_RUN:
+        print('DRY RUN: ' + ' '.join(args))
+        return 0
+    return subprocess.call(args, stdout=open('/dev/null'), stderr=open('/dev/null'))
+
+
+class SwiftTools(object):
+    """A utility class that enables users to easily find sil-tools without needing
+to constantly reform paths to the build directory. Also provides safety by
+asserting if one of the tools does not exist at the specified path"""
+
+    def __init__(self, swift_build_dir):
+        self.swift_build_dir = swift_build_dir
+
+    def _get_tool(self, name):
+        path = os.path.join(self.swift_build_dir, 'bin', name)
+        if not os.access(path, os.F_OK):
+            error_msg = "Error! {} does not exist at: {}".format(name, path)
+            raise RuntimeError(error_msg)
+        return path
+
+    @property
+    def sil_nm(self):
+        """Return the path to sil-nm in the specified swift build directory. Throws a
+runtime error if the tool does not exist"""
+        return self._get_tool('sil-nm')
+
+    @property
+    def swiftc(self):
+        """Return the path to swiftc in the specified swift build directory. Throws a
+runtime error if the tool does not exist"""
+        return self._get_tool('swiftc')
+
+    @property
+    def sil_opt(self):
+        """Return the path to sil-opt in the specified swift build directory. Throws a
+runtime error if the tool does not exist"""
+        return self._get_tool('sil-opt')
+
+    @property
+    def sil_func_extractor(self):
+        """Return the path to sil-func-extractor in the specified swift build
+directory. Throws a runtime error if the tool does not exist."""
+        return self._get_tool('sil-opt')
+
+    @property
+    def sil_passpipeline_dumper(self):
+        """Return the path to sil-passpipeline-dumper in the specified swift build
+directory. Throws a runtime error if the tool does not exist
+
+        """
+        return self._get_tool('sil-passpipeline-dumper')
+
+
+def maybe_abspath(x):
+    if x is None:
+        return x
+    return os.path.abspath(x)
+
+class SILOptInvoker(object):
+
+    def __init__(self, args, tools, early_passes, extra_args):
+        self.tools = tools
+        self.module_cache = args.module_cache
+        self.sdk = args.sdk
+        self.target = args.target
+        self.resource_dir = maybe_abspath(args.resource_dir)
+        self.work_dir = maybe_abspath(args.work_dir)
+        self.module_name = args.module_name
+        self.extra_args = extra_args
+
+        # Start by creating our workdir if necessary
+        subprocess.check_call(["mkdir", "-p", self.work_dir])
+
+        # Then copy our input file into the work dir
+        base_input_file = os.path.basename(args.input_file)
+        (base, ext) = os.path.splitext(base_input_file)
+        self.base_input_file_stem = base
+        self.base_input_file_ext = ".sib"
+
+        # First emit an initial *.sib file. This ensures no matter if we have a
+        # *.swiftmodule, *.sil, or *.sib file, we are always using *.sib.
+        self._invoke(args.input_file, self.get_suffixed_filename('initial'),
+                     [])
+        self.input_file = self.get_suffixed_filename('initial+early')
+
+        # Finally, run the initial sil-opt invocation running the
+        # early-passes. We will not run them again.
+        self._invoke(self.get_suffixed_filename('initial'),
+                     self.input_file,
+                     early_passes)
+
+    def get_suffixed_filename(self, suffix):
+        basename = self.base_input_file_stem + '_' + suffix
+        basename += self.base_input_file_ext
+        return os.path.join(self.work_dir, basename)
+
+    @property
+    def base_args(self):
+        x = [self.tools.sil_opt, "-emit-sib"]
+        if self.sdk is not None:
+            x.append("-sdk=%s" % self.sdk)
+        if self.target is not None:
+            x.append("-target=%s" % self.target)
+        if self.resource_dir is not None:
+            x.append("-resource-dir=%s" % self.resource_dir)
+        if self.module_cache is not None:
+            x.append("-module-cache-path=%s" % self.module_cache)
+        if self.module_name is not None:
+            x.append("-module-name=%s" % self.module_name)
+        return x
+
+    def _invoke(self, input_file, output_file, passes):
+        base_args = self.base_args
+        base_args.extend([
+            input_file,
+            '-o', output_file])
+        base_args.extend(self.extra_args)
+        base_args.extend(passes)
+        return br_call(base_args)
+
+    def invoke_with_passlist(self, output_filename, passes):
+        return self._invoke(self.input_file, output_filename, passes)
+
+
+def get_silopt_invoker(args):
+    tools = SwiftTools(args.swift_build_dir)
+
+    passes = []
+    if len(args.pass_list) == 0:
+        json_data = json.loads(subprocess.check_output(
+            [tools.sil_passpipeline_dumper, '-Performance']))
+        passes = sum((p[2:] for p in json_data if p[0] != 'EarlyModulePasses'), [])
+        passes = ['-' + x[1] for x in passes]
+    else:
+        passes = ['-' + x for x in args.pass_list]
+
+    # We assume we have an early module passes that runs until fix point and
+    # that is strictly not what is causing the issue.
+    #
+    # Everything else runs one iteration.
+    early_module_passes = [p[2:] for p in json_data
+                           if p[0] == 'EarlyModulePasses'][0]
+    early_module_passes = ['-' + x[1] for x in early_module_passes]
+
+    return SILOptInvoker(args, tools, early_module_passes)
diff --git a/utils/bug_reducer/bug_reducer/list_reducer.py b/utils/bug_reducer/bug_reducer/list_reducer.py
new file mode 100644
index 0000000..d14ece5
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/list_reducer.py
@@ -0,0 +1,186 @@
+
+import random
+
+TESTRESULT_NOFAILURE = "NoFailure"
+TESTRESULT_KEEPSUFFIX = "KeepSuffix"
+TESTRESULT_KEEPPREFIX = "KeepPrefix"
+TESTRESULTS = set([TESTRESULT_NOFAILURE, TESTRESULT_KEEPSUFFIX,
+                   TESTRESULT_KEEPPREFIX])
+
+
+class ListReducer(object):
+    """Reduce lists of objects. Inspired by llvm bugpoint"""
+
+    def __init__(self, l):
+        self.target_list = l
+        # Maximal number of allowed splitting iterations,
+        # before the elements are randomly shuffled.
+        self.max_iters_without_progress = 3
+
+        # Maximal number of allowed single-element trim iterations. We add a
+        # threshhold here as single-element reductions may otherwise take a
+        # very long time to complete.
+        self.max_trim_iterations_without_back_jump = 3
+        self.shuffling_enabled = True
+
+        self.num_iters_without_progress = 0
+        self.mid_top = 0
+        self.max_iters = self.max_iters_without_progress
+
+    def _reset_progress(self):
+        self.max_iters = self.max_iters_without_progress
+        self.num_iters_without_progress = 0
+
+    def run_test(self, prefix, suffix):
+        raise RuntimeError("Abstract method")
+
+    def _should_continue(self, result):
+        if result == TESTRESULT_KEEPPREFIX:
+            # We are done, we have the base case and the base case fails.
+            if len(self.target_list) == 1:
+                return {'should_continue': False, 'result': True}
+            else:
+                # There is an error, and we can narrow it down further.
+                return {'should_continue': True, 'result': None}
+
+        if result == TESTRESULT_KEEPSUFFIX:
+            raise RuntimeError("ListReducer internal error: Selected empty "
+                               "set!")
+        raise RuntimeError('Unknown test result: %s' % result)
+
+    def _test_shuffle_slow_converging_list(self):
+        if not self.shuffling_enabled or \
+           self.num_iters_without_progress <= self.max_iters_without_progress:
+            return
+
+        print("*** Testing shuffled set...")
+        shuffled_list = list(self.target_list)
+        random.shuffle(shuffled_list)
+        # TODO: Is this correct? I guess we are always doing something.
+        self.num_iters_without_progress = 0
+
+        # Check that the random shuffle does not lose the bug.
+        (result, _, _) = self.run_test(shuffled_list, [])
+        if result != TESTRESULT_KEEPPREFIX:
+            # If we fail here, disable any further shuffling...
+            self.shuffling_enabled = False
+            print("*** Shuffling hides the bug...")
+            return
+
+        self.mid_top = len(shuffled_list)
+        self.max_iters = self.max_iters + 2
+        print("*** Shuffling does not hide the bug...")
+        self.target_list = shuffled_list
+
+    def _test_prefix_suffix(self, mid, prefix, suffix):
+        (result, prefix, suffix) = self.run_test(prefix, suffix)
+
+        if result == TESTRESULT_KEEPSUFFIX:
+            # The property still holds. We can just drop the prefix
+            # elements, and shorten the list to the "kept" elements.
+            self.target_list = suffix
+            self.mid_top = len(self.target_list)
+
+            # Reset the progress threshold
+            self._reset_progress()
+            return False
+
+        if result == TESTRESULT_KEEPPREFIX:
+            # The predicate still holds, shorten the list to the prefix
+            # elements.
+            self.target_list = prefix
+            self.mid_top = len(self.target_list)
+            self._reset_progress()
+            return False
+
+        assert(result == TESTRESULT_NOFAILURE)
+        # The property does not hold. Some of the elements we removed must
+        # be necessary to maintain the property.
+        self.mid_top = mid
+        self.num_iters_without_progress = \
+            self.num_iters_without_progress + 1
+        return False
+
+    def _trim_target_list(self):
+        self.mid_top = len(self.target_list)
+        self.max_iters = self.max_iters_without_progress
+
+        # Binary split reduction loop
+        while self.mid_top > 1:
+            # If the loop doesn't make satisfying progress, try shuffling.
+            # The purpose of shuffling is to avoid the heavy tails of the
+            # distribution (improving the speed of convergence).
+            self._test_shuffle_slow_converging_list()
+
+            # Split the list into a prefix, suffix list and then run test on
+            # those.
+            mid = self.mid_top/2
+            if not self._test_prefix_suffix(mid, self.target_list[:mid],
+                                            self.target_list[mid:]):
+                # If we returned false, then we did some sort of work and there
+                # was not an error, so continue.
+                continue
+
+            # Otherwise, the test routine signaled an error, so return True to
+            # signal error.
+            return True
+        # If we reach this point, return False, we have no further work we can
+        # do.
+        return False
+
+    def _trim_try_backjump_and_trim_suffix(self):
+        backjump_probability = 10
+        if len(self.target_list) <= 2:
+            return False
+        changed = True
+        trim_iters = 0
+
+        # Trimming loop
+        while changed:
+            changed = False
+
+            # If the binary split reduction loop made an unfortunate sequence
+            # of splits, the trimming loop might be left off with a huge
+            # number of remaining elements (large search space). Backjumping
+            # out of that search space and attempting a different split can
+            # significantly improve the convergence speed.
+            if random.randint(0, 100) < backjump_probability:
+                return True
+
+            # Check interior elements, using an offset to make sure we do not
+            # skip elements when we trim.
+            offset = 0
+            for i in range(1, len(self.target_list)-1):
+                real_i = i + offset
+                test_list = self.target_list[real_i:]
+                (result, prefix, suffix) = self.run_test([], test_list)
+                if result == TESTRESULT_KEEPSUFFIX:
+                    # We can trim the list!
+                    self.target_list = test_list
+                    offset = offset - 1
+                    changed = True
+            if trim_iters >= self.max_trim_iterations_without_back_jump:
+                return False
+            trim_iters = trim_iters + 1
+
+    def reduce_list(self):
+        random.seed(0x6e5ea738)  # Seed the random number generator
+        (result, self.target_list, kept) = self.run_test(self.target_list, [])
+        assert(result in TESTRESULTS)
+        (should_continue, result) = self._should_continue(result)
+        if not should_continue:
+            return result
+
+        # Now try to trim the list.
+        should_backjump = True
+        while should_backjump:
+            # If self._trim_target_list returns True, then we failed to
+            # reduce. Bail!
+            if self._trim_target_list():
+                return False
+
+            # Finally decide if we should back_jump
+            should_backjump = self._trim_try_backjump_and_trim_suffix()
+
+        # There are some failure and we've narrowed them down
+        return True
diff --git a/utils/bug_reducer/bug_reducer/opt_bug_reducer.py b/utils/bug_reducer/bug_reducer/opt_bug_reducer.py
new file mode 100644
index 0000000..25fcd1b
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/opt_bug_reducer.py
@@ -0,0 +1,150 @@
+
+import json
+import md5
+import subprocess
+
+import bug_reducer_utils
+
+import list_reducer
+from list_reducer import TESTRESULT_KEEPPREFIX
+from list_reducer import TESTRESULT_KEEPSUFFIX
+from list_reducer import TESTRESULT_NOFAILURE
+
+
+class ReduceMiscompilingPasses(list_reducer.ListReducer):
+
+    def __init__(self, l, invoker):
+        list_reducer.ListReducer.__init__(self, l)
+        self.invoker = invoker
+
+    def run_test(self, prefix, suffix):
+        # First, run the program with just the Suffix passes.  If it is still
+        # broken with JUST the kept passes, discard the prefix passes.
+        suffix_joined = ' '.join(suffix)
+        suffix_hash = md5.md5(suffix_joined).hexdigest()
+        print("Checking to see if '%s' compiles correctly" % suffix_joined)
+
+        result = self.invoker.invoke_with_passlist(
+            self.invoker.get_suffixed_filename(suffix_hash),
+            suffix)
+
+        # Found a miscompile! Keep the suffix
+        if result != 0:
+            print("Suffix maintains the predicate. Returning suffix")
+            return (TESTRESULT_KEEPSUFFIX, prefix, suffix)
+
+        if len(prefix) == 0:
+            print("Suffix passes and no prefix, returning nofailure")
+            return (TESTRESULT_NOFAILURE, prefix, suffix)
+
+        # Next see if the program is broken if we run the "prefix" passes
+        # first, then separately run the "kept" passes.
+        prefix_joined = ' '.join(prefix)
+        prefix_hash = md5.md5(prefix_joined).hexdigest()
+        print("Checking to see if '%s' compiles correctly" % prefix_joined)
+
+        # If it is not broken with the kept passes, it's possible that the
+        # prefix passes must be run before the kept passes to break it.  If
+        # the program WORKS after the prefix passes, but then fails if running
+        # the prefix AND kept passes, we can update our bitcode file to
+        # include the result of the prefix passes, then discard the prefix
+        # passes.
+        prefix_path = self.invoker.get_suffixed_filename(prefix_hash)
+        result = self.invoker.invoke_with_passlist(
+            prefix_path,
+            prefix)
+        if result != 0:
+            print("Prefix maintains the predicate by itself. Returning keep "
+                  "prefix")
+            return (TESTRESULT_KEEPPREFIX, prefix, suffix)
+
+        # Ok, so now we know that the prefix passes work, first check if we
+        # actually have any suffix passes. If we don't, just return.
+        if len(suffix) == 0:
+            print("No suffix, and prefix passes, returning no failure")
+            return (TESTRESULT_NOFAILURE, prefix, suffix)
+
+        # Otherwise, treat the prefix as our new baseline and see if suffix on
+        # the new baseline finds the crash.
+        original_input_file = self.invoker.input_file
+        self.invoker.input_file = prefix_path
+        print("Checking to see if '%s' compiles correctly after the '%s' "
+              "passes" % (suffix_joined, prefix_joined))
+        result = self.invoker.invoke_with_passlist(
+            self.invoker.get_suffixed_filename(suffix_hash),
+            suffix)
+
+        # If we failed at this point, then the prefix is our new
+        # baseline. Return keep suffix.
+        if result != 0:
+            print("Suffix failed. Keeping prefix as new baseline")
+            return (TESTRESULT_KEEPSUFFIX, prefix, suffix)
+
+        # Otherwise, we must not be running the bad pass anymore. Restore the
+        # original input_file and return NoFailure.
+        self.invoker.input_file = original_input_file
+        return (TESTRESULT_NOFAILURE, prefix, suffix)
+
+
+def pass_bug_reducer(args):
+    """Given a path to a sib file with canonical sil, attempt to find a perturbed
+list of passes that the perf pipeline"""
+    tools = bug_reducer_utils.SwiftTools(args.swift_build_dir)
+
+    passes = []
+    early_module_passes = []
+    if args.pass_list is None:
+        json_data = json.loads(subprocess.check_output(
+            [tools.sil_passpipeline_dumper, '-Performance']))
+        passes = sum((p[2:] for p in json_data if p[0] != 'EarlyModulePasses'), [])
+        passes = ['-' + x[1] for x in passes]
+        # We assume we have an early module passes that runs until fix point and
+        # that is strictly not what is causing the issue.
+        #
+        # Everything else runs one iteration.
+        early_module_passes = [p[2:] for p in json_data
+                               if p[0] == 'EarlyModulePasses'][0]
+        early_module_passes = ['-' + x[1] for x in early_module_passes]
+    else:
+        passes = ['-' + x for x in args.pass_list]
+
+    extra_args = []
+    if args.extra_args is not None:
+        extra_args = args.extra_args
+    sil_opt_invoker = bug_reducer_utils.SILOptInvoker(args, tools,
+                                                      early_module_passes,
+                                                      extra_args)
+
+    # Make sure that the base case /does/ crash.
+    filename = sil_opt_invoker.get_suffixed_filename('base_case')
+    result = sil_opt_invoker.invoke_with_passlist(filename, passes)
+    # If we succeed, there is no further work to do.
+    if result == 0:
+        print("Success with PassList: %s" % (' '.join(passes)))
+        return
+
+    # Otherwise, reduce the list of pases that cause the optimzier to crash.
+    r = ReduceMiscompilingPasses(passes, sil_opt_invoker)
+    if not r.reduce_list():
+        print("Failed to find miscompiling pass list!")
+    print("Found miscompiling passes: %s" % (' '.join(r.target_list)))
+
+
+def add_parser_arguments(parser):
+    """Add parser arguments for opt_bug_reducer"""
+    parser.set_defaults(func=pass_bug_reducer)
+    parser.add_argument('input_file', help='The input file to optimize')
+    parser.add_argument('--module-cache', help='The module cache to use')
+    parser.add_argument('--sdk', help='The sdk to pass to sil-opt')
+    parser.add_argument('--target', help='The target to pass to sil-opt')
+    parser.add_argument('--resource-dir',
+                        help='The resource-dir to pass to sil-opt')
+    parser.add_argument('--work-dir',
+                        help='Working directory to use for temp files',
+                        default='bug_reducer')
+    parser.add_argument('--module-name',
+                        help='The name of the module we are optimizing')
+    parser.add_argument('--pass', help='pass to test', dest='pass_list',
+                        action='append')
+    parser.add_argument('--extra-arg', help='extra argument to pass to sil-opt',
+                        dest='extra_args', action='append')
diff --git a/utils/bug_reducer/bug_reducer/random_bug_finder.py b/utils/bug_reducer/bug_reducer/random_bug_finder.py
new file mode 100644
index 0000000..514139d
--- /dev/null
+++ b/utils/bug_reducer/bug_reducer/random_bug_finder.py
@@ -0,0 +1,67 @@
+
+import json
+import random
+import subprocess
+import sys
+
+import bug_reducer_utils
+
+
+def construct_pipeline(p):
+    return (p[0], {'execution_kind': p[1], 'passes': p[2:]})
+
+
+def random_bug_finder(args):
+    """Given a path to a sib file with canonical sil, attempt to find a perturbed
+list of passes that the perf pipeline"""
+    tools = bug_reducer_utils.SwiftTools(args.swift_build_dir)
+
+    json_data = json.loads(subprocess.check_output(
+        [tools.sil_passpipeline_dumper, '-Performance']))
+    passes = sum((p[2:] for p in json_data if p[0] != 'EarlyModulePasses'), [])
+    passes = ['-' + x[1] for x in passes]
+
+    # We assume we have an early module passes that runs until fix point and
+    # that is strictly not what is causing the issue.
+    #
+    # Everything else runs one iteration.
+    early_module_passes = [p[2:] for p in json_data
+                           if p[0] == 'EarlyModulePasses'][0]
+    early_module_passes = ['-' + x[1] for x in early_module_passes]
+
+    sil_opt_invoker = bug_reducer_utils.SILOptInvoker(args, tools,
+                                                      early_module_passes)
+
+    # Make sure that the base case /does/ crash.
+    max_count = args.max_count
+    for count in range(max_count):
+        print("Running round %i/%i" % (count, max_count))
+        random.shuffle(passes)
+        filename = sil_opt_invoker.get_suffixed_filename(str(count))
+        result = sil_opt_invoker.invoke_with_passlist(filename, passes)
+        if result == 0:
+            print("Success with PassList: %s" % (' '.join(passes)))
+        else:
+            print("Fail with PassList: %s" % (' '.join(passes)))
+            print("Output File: %s" % filename)
+            sys.exit(-1)
+
+
+def add_parser_arguments(parser):
+    """Add parser arguments for random_bug_reducer"""
+    parser.set_defaults(func=random_bug_finder)
+    parser.add_argument('input_file', help='The input file to optimize')
+    parser.add_argument('--module-cache', help='The module cache to use')
+    parser.add_argument('--sdk', help='The sdk to pass to sil-opt')
+    parser.add_argument('--target', help='The target to pass to sil-opt')
+    parser.add_argument('--resource-dir',
+                        help='The resource-dir to pass to sil-opt')
+    parser.add_argument('--work-dir',
+                        help='Working directory to use for temp files',
+                        default='bug_reducer')
+    parser.add_argument('--module-name',
+                        help='The name of the module we are optimizing')
+    parser.add_argument('--max-count',
+                        help='Maximum number of permutations to try before'
+                        ' exiting',
+                        default=100)
diff --git a/utils/bug_reducer/tests/__init__.py b/utils/bug_reducer/tests/__init__.py
new file mode 100644
index 0000000..3f0c19b
--- /dev/null
+++ b/utils/bug_reducer/tests/__init__.py
@@ -0,0 +1,16 @@
+# bug_reducer/tests/__init__.py ------------------- Test module -*- python -*-
+#
+# This source file is part of the Swift.org open source project
+#
+# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See https://swift.org/LICENSE.txt for license information
+# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+#
+# ----------------------------------------------------------------------------
+#
+# This file needs to be here in order for Python to treat the
+# utils/bug_reducer/tests/ directory as a module.
+#
+# ----------------------------------------------------------------------------
diff --git a/utils/bug_reducer/tests/test_optbugreducer.py b/utils/bug_reducer/tests/test_optbugreducer.py
new file mode 100644
index 0000000..c1279ed
--- /dev/null
+++ b/utils/bug_reducer/tests/test_optbugreducer.py
@@ -0,0 +1,103 @@
+# ==--- opt_bug_reducer_test.py ------------------------------------------===#
+#
+# This source file is part of the Swift.org open source project
+#
+# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See http:#swift.org/LICENSE.txt for license information
+# See http:#swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+#
+# ==----------------------------------------------------------------------===#
+
+
+import json
+import os
+import platform
+import random
+import shutil
+import subprocess
+import unittest
+
+
+import bug_reducer.bug_reducer_utils as bug_reducer_utils
+
+
+@unittest.skipUnless(platform.system() == 'Darwin',
+                     'opt_bug_reducer is only available on Darwin for now')
+class OptBugReducerTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.file_dir = os.path.dirname(os.path.abspath(__file__))
+        self.reducer = os.path.join(os.path.dirname(self.file_dir),
+                                    'bug_reducer', 'bug_reducer.py')
+        self.build_dir = os.path.abspath(os.environ['BUGREDUCE_TEST_SWIFT_OBJ_ROOT'])
+        self.tmp_dir = os.path.abspath(os.environ['BUGREDUCE_TEST_TMP_DIR'])
+
+        self.module_cache = os.path.join(self.tmp_dir, 'module_cache')
+        self.sdk = subprocess.check_output(['xcrun', '--sdk', 'macosx',
+                                            '--toolchain', 'Default',
+                                            '--show-sdk-path']).strip("\n")
+        self.tools = bug_reducer_utils.SwiftTools(self.build_dir)
+        json_data = json.loads(subprocess.check_output(
+            [self.tools.sil_passpipeline_dumper, '-Performance']))
+        self.passes = []
+        for y in (x[2:] for x in json_data):
+            for z in y:
+                self.passes.append('--pass=-' + z[1])
+        random.seed(0xf487c07f)
+        random.shuffle(self.passes)
+        self.passes.insert(random.randint(0, len(self.passes)),
+                           '--pass=-bug-reducer-tester')
+
+        if os.access(self.tmp_dir, os.F_OK):
+            shutil.rmtree(self.tmp_dir)
+        os.makedirs(self.tmp_dir)
+        os.makedirs(self.module_cache)
+
+    def _get_test_file_path(self, module_name):
+        (root, _) = os.path.splitext(os.path.abspath(__file__))
+        root_basename = ''.join(os.path.basename(root).split('_'))
+        return os.path.join(self.file_dir,
+                            '{}_{}.swift'.format(root_basename, module_name))
+
+    def _get_sib_file_path(self, filename):
+        (root, ext) = os.path.splitext(filename)
+        return os.path.join(self.tmp_dir, os.path.basename(root) + '.sib')
+
+    def run_swiftc_command(self, name):
+        input_file_path = self._get_test_file_path(name)
+        args = [self.tools.swiftc,
+                '-module-cache-path', self.module_cache,
+                '-sdk', self.sdk,
+                '-Onone', '-parse-as-library',
+                '-module-name', name,
+                '-emit-sib',
+                '-resource-dir', os.path.join(self.build_dir, 'lib', 'swift'),
+                '-o', self._get_sib_file_path(input_file_path),
+                input_file_path]
+        #print ' '.join(args)
+        return subprocess.check_call(args)
+
+    def test_basic(self):
+        name = 'testbasic'
+        result_code = self.run_swiftc_command(name)
+        assert result_code == 0, "Failed initial compilation"
+        args = [
+            self.reducer,
+            'opt',
+            self.build_dir,
+            self._get_sib_file_path(self._get_test_file_path(name)),
+            '--sdk=%s' % self.sdk,
+            '--module-cache=%s' % self.module_cache,
+            '--module-name=%s' % name,
+            '--work-dir=%s' % self.tmp_dir,
+            '--extra-arg=-bug-reducer-tester-target-func=test_target'
+        ]
+        args.extend(self.passes)
+        output = subprocess.check_output(args).split("\n")
+        success_message = 'Found miscompiling passes: --bug-reducer-tester'
+        self.assertTrue(success_message in output)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/utils/bug_reducer/tests/testoptbugreducer_testbasic.swift b/utils/bug_reducer/tests/testoptbugreducer_testbasic.swift
new file mode 100644
index 0000000..651b75c
--- /dev/null
+++ b/utils/bug_reducer/tests/testoptbugreducer_testbasic.swift
@@ -0,0 +1,19 @@
+
+import Swift
+
+@_silgen_name("test_target")
+@inline(never)
+public func foo3() {
+}
+
+public func foo1() {
+  print("1")
+}
+
+public func foo2() {
+  print("3")
+  foo1()
+  foo3()
+}
+
+
diff --git a/validation-test/Python/bug-reducer.test-sh b/validation-test/Python/bug-reducer.test-sh
new file mode 100644
index 0000000..4042539
--- /dev/null
+++ b/validation-test/Python/bug-reducer.test-sh
@@ -0,0 +1,2 @@
+// RUN: BUGREDUCE_TEST_TMP_DIR=%t BUGREDUCE_TEST_SWIFT_OBJ_ROOT=%swift_obj_root %{python} -m unittest discover -s %utils/bug_reducer
+// REQUIRES: OS=macosx