Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

[dead2] Add basic reductions #67

Draft
wants to merge 2 commits into
base: dead2
Choose a base branch
from
Draft
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
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
repos:
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
rev: 5.11.4
hooks:
- id: isort
args: ['--profile', 'black']
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
24 changes: 21 additions & 3 deletions dead/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dead.config import dump_config, interactive_init
from dead.differential_testing import DifferentialTestingMode, generate_and_test
from dead.output import write_cases_to_directory
from dead.reduction import reduce_case


def __arg_to_compiler_exe(arg: str | None) -> CompilerExe | None:
Expand Down Expand Up @@ -84,7 +85,6 @@ def parse_args() -> argparse.Namespace:
action=argparse.BooleanOptionalAction,
help="Make the temporary configuration overrides permanent.",
)

parser.add_argument(
"compilation_command1",
type=str,
Expand All @@ -107,6 +107,12 @@ def parse_args() -> argparse.Namespace:
"first command eliminated, in the bidirectional mode (default) a case is "
"interesting as long as at least one command misses a marker",
)

parser.add_argument(
"--reduce",
action=argparse.BooleanOptionalAction,
help="Also reduce the discovered cases",
)
parser.add_argument(
"--jobs",
"-j",
Expand Down Expand Up @@ -148,7 +154,19 @@ def run_as_module() -> None:
setting1,
setting2,
__arg_to_testing_mode(args.testing_mode),
args.number_attempts,
args.number_candidates,
args.jobs,
)
write_cases_to_directory(cases, args.output_directory)
reductions = {}
if args.reduce:
for case in cases:
# XXX: how to a select marker?
target_marker = (
case.markers_only_eliminated_by_setting1
+ case.markers_only_eliminated_by_setting2
)[0]
reduction = reduce_case(case, target_marker, args.jobs)
assert reduction
reductions[case] = reduction

write_cases_to_directory(cases, reductions, args.output_directory)
53 changes: 37 additions & 16 deletions dead/differential_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,33 @@ class DifferentialTestingCase:
markers_only_eliminated_by_setting1: tuple[DCEMarker | VRMarker, ...]
markers_only_eliminated_by_setting2: tuple[DCEMarker | VRMarker, ...]

def __post_init__(self) -> None:
assert set(self.markers_only_eliminated_by_setting1).isdisjoint(
self.markers_only_eliminated_by_setting2
)


class DifferentialTestingMode(Enum):
"""
- Unidirectional: a marker is interesting only if the first
compilation setting missed it and the second eliminated
- Bidirectional: a marker is interesting if any of the compilation settings
missed it and the other found it
- Unidirectional: any marker is interesting only if the first
compilation setting missed it and the second eliminated it
- Bidirectional: any marker is interesting if any of the compilation
settings missed it and the other eliminated it
- MarkerMissedByFirst: a particular marker is interesting if the
first compilation setting missed it and the other eliminated it
"""

Unidirectional = 0
Unidirectional = 0 # AnyMissedByFirst?
Bidirectional = 1
MarkerMissedByFirst = 2


def differential_test(
program: SourceProgram,
setting1: CompilationSetting,
setting2: CompilationSetting,
testing_mode: DifferentialTestingMode = DifferentialTestingMode.Bidirectional,
missed_marker: DCEMarker | VRMarker | None = None,
) -> DifferentialTestingCase | None:
"""Instrument `program`, compile it with `setting1` and `setting2` and
check if the set of eliminated markers differ.
Expand All @@ -87,11 +96,16 @@ def differential_test(
setting2 (CompilationSetting):
the second compilation setting with which to
compile the instrumented program
testing_direction (DifferentialTestingDirection):
testing_mode (DifferentialTestingMode):
whether to accept cases whether where any of the two settings miss
at least one marker (Bidirectional), or cases where markers are
eliminated by `setting1` and eliminated by `setting2`

missed by `setting1` and eliminated by `setting2` (Unidirectional).
In MarkerMissedByFirst mode, if `missed_marker` is not
missed by the First setting and eliminated by the other,
the case is not interesting and None is returned.
missed_marker (DCEMarker | VRMarker | None):
If `testing_mode` is MarkerMissecByFirst, only `missed_marker` is
checked: it must be missed by the first setting and found by the other.
Returns:
(DifferentialTestingCase | None):
interesting case if found
Expand All @@ -103,7 +117,9 @@ def differential_test(

# Instrument program
try:
instr_program = instrument_program(program)
instr_program = instrument_program(
setting1.preprocess_program(program, make_compiler_agnostic=True)
)
except AssertionError:
return None

Expand All @@ -114,13 +130,17 @@ def differential_test(
only_eliminated_by_setting2 = tuple(dead_markers2 - dead_markers1)

# Is the candidate interesting?
if not only_eliminated_by_setting1 and not only_eliminated_by_setting2:
return None
if testing_mode == DifferentialTestingMode.Unidirectional:
if not only_eliminated_by_setting1:
return None
else:
assert testing_mode == DifferentialTestingMode.Bidirectional
match testing_mode:
case DifferentialTestingMode.Bidirectional:
if not only_eliminated_by_setting1 and not only_eliminated_by_setting2:
return None
case DifferentialTestingMode.Unidirectional:
if not only_eliminated_by_setting1:
return None
case DifferentialTestingMode.MarkerMissedByFirst:
assert missed_marker
if missed_marker not in only_eliminated_by_setting2:
return None

return DifferentialTestingCase(
program=instr_program,
Expand All @@ -131,6 +151,7 @@ def differential_test(
)


# XXX: does this really belong in this module?
def generate_and_test(
setting1: CompilationSetting,
setting2: CompilationSetting,
Expand Down
55 changes: 52 additions & 3 deletions dead/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,51 @@
from diopter.compiler import CompilationOutput, CompilationOutputKind

from dead.differential_testing import DifferentialTestingCase
from dead.reduction import Reduction


def write_reduction_to_directory(
reduction: Reduction | None, output_directory: Path
) -> None:
if not reduction:
return
reduction_dir = output_directory / "reduction"
reduction_dir.mkdir()
code_file = (reduction_dir / "reduced_code").with_suffix(
reduction.reduced_program.language.to_suffix()
)

with open(code_file, "w") as f:
print(reduction.reduced_program.code, file=f)

with open(reduction_dir / "good_setting", "w") as f:
print(
" ".join(
reduction.good_setting.get_compilation_cmd(
(reduction.reduced_program, Path(code_file.name)),
CompilationOutput(Path("dummy1.s"), CompilationOutputKind.Assembly),
)
),
file=f,
)

with open(reduction_dir / "bad_setting", "w") as f:
print(
" ".join(
reduction.bad_setting.get_compilation_cmd(
(reduction.reduced_program, Path(code_file.name)),
CompilationOutput(Path("dummy1.s"), CompilationOutputKind.Assembly),
)
),
file=f,
)

with open(reduction_dir / "target_marker", "w") as f:
print(reduction.target_marker.to_macro(), file=f)


def write_case_to_directory(
case: DifferentialTestingCase, output_directory: Path
case: DifferentialTestingCase, reduction: Reduction | None, output_directory: Path
) -> None:
output_directory.mkdir(parents=True, exist_ok=True)
code_file = (output_directory / "code").with_suffix(
Expand Down Expand Up @@ -37,6 +78,7 @@ def write_case_to_directory(
),
file=f,
)

with open(output_directory / "markers_only_eliminated_by_setting1", "w") as f:
print(
"\n".join(
Expand All @@ -52,14 +94,21 @@ def write_case_to_directory(
),
file=f,
)
write_reduction_to_directory(reduction, output_directory)


def write_cases_to_directory(
cases: Sequence[DifferentialTestingCase], output_directory: Path
cases: Sequence[DifferentialTestingCase],
reductions: dict[DifferentialTestingCase, Reduction],
output_directory: Path,
) -> None:
output_directory.mkdir(parents=True, exist_ok=True)
output_sub_dir_n = 0
for case in cases:
while (output_directory / str(output_sub_dir_n)).exists():
output_sub_dir_n += 1
write_case_to_directory(case, output_directory / str(output_sub_dir_n))
write_case_to_directory(
case,
reductions[case] if case in reductions else None,
output_directory / str(output_sub_dir_n),
)
Loading