Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 42 additions & 32 deletions cvise.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,29 +85,6 @@ def get_available_pass_groups():
return group_names


def get_available_cores():
try:
# try to detect only physical cores, ignore HyperThreading
# in order to speed up parallel execution
core_count = psutil.cpu_count(logical=False)
if not core_count:
core_count = psutil.cpu_count(logical=True)
# respect affinity
try:
affinity = len(psutil.Process().cpu_affinity())
assert affinity >= 1
except AttributeError:
return core_count

if core_count:
core_count = min(core_count, affinity)
else:
core_count = affinity
return core_count
except NotImplementedError:
return 1


EPILOG_TEXT = f"""
available shortcuts:
S - skip execution of the current pass
Expand All @@ -118,6 +95,7 @@ def get_available_cores():
"""

if __name__ == '__main__':
default_timeout_value = 300
parser = argparse.ArgumentParser(
description='C-Vise',
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand All @@ -127,8 +105,8 @@ def get_available_cores():
'--n',
'-n',
type=int,
default=get_available_cores(),
help='Number of cores to use; C-Vise tries to automatically pick a good setting but its choice may be too low or high for your situation',
default=-1,
help=f'Number of cores to use; C-Vise tries to automatically pick a good setting but its choice may be too low or high for your situation. Usually defaults to {testing.TestManager.get_available_cores()}. May be less on large TEST_CASEs.',
)
parser.add_argument(
'--tidy',
Expand Down Expand Up @@ -161,6 +139,12 @@ def get_available_cores():
action='store_true',
help='Print debug information (alias for --log-level=DEBUG)',
)
parser.add_argument(
'-w',
action='store_false',
default=True,
help='Do not double-check command-line arguments.',
)
parser.add_argument(
'--log-level',
type=str,
Expand Down Expand Up @@ -218,8 +202,8 @@ def get_available_cores():
'--timeout',
type=int,
nargs='?',
default=300,
help='Interestingness test timeout in seconds',
default=-1,
help=f'Interestingness test timeout in seconds. Defaults to {default_timeout_value}.',
)
parser.add_argument('--no-cache', action='store_true', help="Don't cache behavior of passes")
parser.add_argument(
Expand All @@ -233,12 +217,18 @@ def get_available_cores():
type=int,
help='Largest improvement in file size from a single transformation that C-Vise should accept (useful only to slow C-Vise down)',
)
parser.add_argument(
'--min-improvement',
metavar='BYTES',
type=int,
help='Minimum improvement in file size from a single transformation that C-Vise should accept',
)
passes_group = parser.add_mutually_exclusive_group()
passes_group.add_argument(
'--pass-group',
type=str,
choices=get_available_pass_groups(),
help='Set of passes used during the reduction',
help='Set of passes used during the reduction.',
)
passes_group.add_argument('--pass-group-file', type=str, help='JSON file defining a custom pass group')
parser.add_argument(
Expand All @@ -262,7 +252,7 @@ def get_available_cores():
action='store_true',
help='Enable all renaming passes (that are disabled by default)',
)
parser.add_argument('--list-passes', action='store_true', help='Print all available passes and exit')
parser.add_argument('--list-passes', action='store_true', help='Print all available passes and exit. Works with --not-c (showing available passes not specific to C and C++), --renaming (showing available renaming passes), and --sllooww (showing availalbe passes, activated with the --sllooww option).')
parser.add_argument(
'--version',
action='version',
Expand All @@ -274,7 +264,7 @@ def get_available_cores():
'-c',
help='Use shell commands instead of an interestingness test case',
)
parser.add_argument('--shell', default='bash', help='Use selected shell for the --commands option')
parser.add_argument('--shell', default='bash', help='Use selected shell for the --commands option. Defaults to "bash"')
parser.add_argument(
'--to-utf8',
action='store_true',
Expand All @@ -296,10 +286,17 @@ def get_available_cores():
'--stopping-threshold',
default=1.0,
type=float,
help='CVise will stop reducing a test case once it has reduced by this fraction of its original size. Between 0.0 and 1.0.',
help='CVise will stop reducing a test case once it has reduced by this fraction of its original size. Between 0.0 and 1.0. Defaults to 1.0',
)

args = parser.parse_args()
# TEST_CASE may not be required, when `--list-passes` is provided. But the parser doesn't know.
# Thus, we insert a dummy element so that the parser won't complain.
if '--list-passes' in sys.argv:
cmd_args = sys.argv[1:]
cmd_args.append("/dev/null")
args = parser.parse_args(cmd_args)
else:
args = parser.parse_args()

log_config = {}

Expand Down Expand Up @@ -358,13 +355,21 @@ def get_available_cores():
)
if args.list_passes:
logging.info('Available passes:')
logging.info('==============')
logging.info('INITIAL PASSES')
logging.info('==============')
for p in pass_group['first']:
logging.info(str(p))
logging.info('')
logging.info('==============')
logging.info('MAIN PASSES')
logging.info('==============')
for p in pass_group['main']:
logging.info(str(p))
logging.info('')
logging.info('==============')
logging.info('CLEANUP PASSES')
logging.info('==============')
for p in pass_group['last']:
logging.info(str(p))
sys.exit(0)
Expand Down Expand Up @@ -408,6 +413,9 @@ def get_available_cores():
logging.info(f'Using temporary interestingness test: {script.name}')
args.interestingness_test = script.name

if args.timeout <= 0:
args.timeout = default_timeout_value

test_manager = testing.TestManager(
pass_statistic,
args.interestingness_test,
Expand All @@ -421,11 +429,13 @@ def get_available_cores():
args.die_on_pass_bug,
args.print_diff,
args.max_improvement,
args.min_improvement,
args.no_give_up,
args.also_interesting,
args.start_with_pass,
args.skip_after_n_transforms,
args.stopping_threshold,
args.w,
)

reducer = CVise(test_manager, args.skip_interestingness_test_check)
Expand Down
2 changes: 2 additions & 0 deletions cvise/cvise.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from cvise.passes.abstract import AbstractPass
from cvise.passes.balanced import BalancedPass
from cvise.passes.blank import BlankPass
from cvise.passes.clear import ClearPass
from cvise.passes.clang import ClangPass
from cvise.passes.clangbinarysearch import ClangBinarySearchPass
from cvise.passes.clex import ClexPass
Expand Down Expand Up @@ -41,6 +42,7 @@ class Info:
'blank': BlankPass,
'clang': ClangPass,
'clangbinarysearch': ClangBinarySearchPass,
'clear': ClearPass,
'clex': ClexPass,
'comments': CommentsPass,
'gcda-binary': GCDABinaryPass,
Expand Down
7 changes: 6 additions & 1 deletion cvise/passes/clangbinarysearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

class ClangBinarySearchPass(AbstractPass):
QUERY_TIMEOUT = 10
QUERY_MAX_TIMEOUTS = 30
QUERY_CUR_TIMEOUT = 0

def check_prerequisites(self):
return self.check_external_program('clang_delta')
Expand Down Expand Up @@ -51,6 +53,8 @@ def advance_on_success(self, test_case, state):
return state

def count_instances(self, test_case):
if self.QUERY_CUR_TIMEOUT >= self.QUERY_MAX_TIMEOUTS:
return 0
assert self.clang_delta_std
args = [
self.external_programs['clang_delta'],
Expand All @@ -64,8 +68,9 @@ def count_instances(self, test_case):
try:
proc = subprocess.run(cmd, text=True, capture_output=True, timeout=self.QUERY_TIMEOUT)
except subprocess.TimeoutExpired:
self.QUERY_CUR_TIMEOUT += 1
logging.warning(
f'clang_delta --query-instances (--std={self.clang_delta_std}) {self.QUERY_TIMEOUT}s timeout reached'
f'[{self.QUERY_CUR_TIMEOUT:{len(str(self.QUERY_MAX_TIMEOUTS))}}/{self.QUERY_MAX_TIMEOUTS:{len(str(self.QUERY_MAX_TIMEOUTS))}}] clang_delta --query-instances (--std={self.clang_delta_std}) {self.QUERY_TIMEOUT}s timeout reached. Cmd: {cmd}'
)
return 0
except subprocess.SubprocessError as e:
Expand Down
31 changes: 31 additions & 0 deletions cvise/passes/clear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os
import shutil
import tempfile

from cvise.passes.abstract import AbstractPass, PassResult


class ClearPass(AbstractPass):
def check_prerequisites(self):
return True

def new(self, test_case, _=None):
return 0

def advance(self, test_case, state):
return state + 1

def advance_on_success(self, test_case, state):
return state

@staticmethod
def __transform(test_case):
if os.path.getsize(test_case) == 0:
return False
tmp = os.path.dirname(test_case)
tmp_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, dir=tmp)
shutil.move(tmp_file.name, test_case)
return True

def transform(self, test_case, state, process_event_notifier):
return (PassResult.OK if self.__transform(test_case) else PassResult.STOP, state)
5 changes: 5 additions & 0 deletions cvise/utils/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def __str__(self):
return 'Could not find a directory with definitions for pass groups!'


class KeyboardInterruption(CViseError):
def __str__(self):
return 'Got Ctrl+C'


class PassBugError(CViseError):
MSG = """***************************************************

Expand Down
5 changes: 5 additions & 0 deletions cvise/utils/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def __init__(self, pass_name):
self.worked = 0
self.failed = 0
self.totally_executed = 0
self.improvement = 0


class PassStatistic:
Expand Down Expand Up @@ -39,6 +40,10 @@ def add_success(self, pass_):
pass_name = repr(pass_)
self.stats[pass_name].worked += 1

def add_improvement(self, pass_, bytes_):
pass_name = repr(pass_)
self.stats[pass_name].improvement += bytes_

def add_failure(self, pass_):
pass_name = repr(pass_)
self.stats[pass_name].failed += 1
Expand Down
Loading