blob: 25fcd1b56f6f94fdc45f475519632aa3f1061125 [file] [log] [blame]
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')