blob: ca8ffd82b8166886cdff8f1b468a118b18277995 [file] [log] [blame]
from __future__ import print_function
import json
import md5
import subprocess
import func_bug_reducer
import list_reducer
from list_reducer import TESTRESULT_KEEPPREFIX
from list_reducer import TESTRESULT_KEEPSUFFIX
from list_reducer import TESTRESULT_NOFAILURE
import swift_tools
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 suffix '%s' compiles correctly" %
suffix_joined)
result = self.invoker.invoke_with_passlist(
suffix,
self.invoker.get_suffixed_filename(suffix_hash))
# Found a miscompile! Keep the suffix
if result['exit_code'] != 0:
print("Suffix crashes! Returning suffix")
return (TESTRESULT_KEEPSUFFIX, prefix, suffix)
if len(prefix) == 0:
print("Suffix passes and no prefix passes, returning nofailure")
return (TESTRESULT_NOFAILURE, prefix, suffix)
print("Suffix '' does not crash! Current reduced program compiles "
"without optimization!")
# 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,
prefix_path)
if result['exit_code'] != 0:
print("Prefix crashes the input 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(
suffix,
self.invoker.get_suffixed_filename(suffix_hash))
# If we failed at this point, then the prefix is our new
# baseline. Return keep suffix.
if result['exit_code'] != 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(tools, config, passes, sil_opt_invoker, reduce_sil):
# Make sure that the base case /does/ crash.
filename = sil_opt_invoker.get_suffixed_filename('base_case')
result = sil_opt_invoker.invoke_with_passlist(passes, filename)
# If we succeed, there is no further work to do.
if result['exit_code'] == 0:
print("Does not crash on input passes!")
print("Base Case: {}. Passes: {}".format(filename, ' '.join(passes)))
return True
print("Crashes with initial pass list! First trying to reduce the pass "
"list!")
# Otherwise, reduce the list of passes that cause the optimizer to crash.
r = ReduceMiscompilingPasses(passes, sil_opt_invoker)
if not r.reduce_list():
print("Failed to find miscompiling pass list!")
cmdline = sil_opt_invoker.cmdline_with_passlist(r.target_list)
print("*** Found miscompiling passes!")
print("*** Final File: %s" % sil_opt_invoker.input_file)
print("*** Final Passes: %s" % (' '.join(r.target_list)))
print("*** Repro command line: %s" % (' '.join(cmdline)))
if not reduce_sil:
return False
print("*** User requested that we try to reduce SIL. Lets try.")
input_file = sil_opt_invoker.input_file
nm = swift_tools.SILNMInvoker(config, tools)
sil_extract_invoker = swift_tools.SILFuncExtractorInvoker(config,
tools,
input_file)
func_bug_reducer.function_bug_reducer(input_file, nm, sil_opt_invoker,
sil_extract_invoker,
r.target_list)
print("*** Final Passes: %s" % (' '.join(r.target_list)))
return False
def invoke_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 = swift_tools.SwiftTools(args.swift_build_dir)
config = swift_tools.SILToolInvokerConfig(args)
passes = []
if args.pass_list is None:
json_data = json.loads(subprocess.check_output(
[tools.sil_passpipeline_dumper, '-Performance']))
passes = sum((p[1:] for p in json_data), [])
passes = ['-' + x[1] for x in 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 = swift_tools.SILOptInvoker(config, tools,
args.input_file,
extra_args)
pass_bug_reducer(tools, config, passes, sil_opt_invoker, args.reduce_sil)
def add_parser_arguments(parser):
"""Add parser arguments for opt_bug_reducer"""
parser.set_defaults(func=invoke_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')
parser.add_argument('--reduce-sil', help='After finding the relevant '
'passes, try to reduce the SIL by eliminating '
'functions, blocks, etc',
action='store_true')