Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/patch management #432

Merged
merged 23 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fd9c5d9
feat: generation of patches from individual suggestions
lukasrothenberger Oct 25, 2023
7b0e1ad
feat: create and store patches
lukasrothenberger Oct 25, 2023
349296e
feat(patch_applicator): prepared program structure
lukasrothenberger Oct 26, 2023
06b2ba2
feat(patch_applicator): apply patches implemented
lukasrothenberger Oct 26, 2023
a3318e0
feat(patch_applicator): list and rollback applied suggestions
lukasrothenberger Oct 26, 2023
bbb11d3
fix(patch_applicator)[rollback]: patch arguments
lukasrothenberger Oct 26, 2023
4641115
feat(patch_applicator)[clear]: implemented functionality
lukasrothenberger Oct 26, 2023
6a6d69a
feat(patch_applicator)[load]: implemented functionality and minor fixes
lukasrothenberger Oct 26, 2023
cc272cd
feat(line_mapping): let discopop_explorer initialize the file
lukasrothenberger Nov 1, 2023
9d76cdc
feat(line_mapping): utilities to save and load added
lukasrothenberger Nov 1, 2023
d5b552a
feat: applied modifications based on diffs & represented in line_mapping
lukasrothenberger Nov 1, 2023
cde3989
chore: cleanup debug prints
lukasrothenberger Nov 1, 2023
8396066
feat(patch_applicator): added return code
lukasrothenberger Nov 6, 2023
4a32506
feat(patch_generator): read CC /CXX from build_config.txt
lukasrothenberger Nov 6, 2023
1b6aa78
fix(patch_generator): disabled compile checks for now
lukasrothenberger Nov 6, 2023
3b6f0cd
fix(patch_generator): check for permission errors and ignore such files
lukasrothenberger Nov 7, 2023
0e89b90
fix: minor fixes and improvements
lukasrothenberger Nov 7, 2023
361cb99
fix: -Naru50 --> -Naru
goerlibe Nov 9, 2023
8a11c29
fix: offset number of added lines in case of multiple lines
lukasrothenberger Nov 9, 2023
8de3f0d
fix: offset number of added lines in case of multiple lines
lukasrothenberger Nov 9, 2023
bdfaf62
feat(patch_generator): initialize applied_suggestions.json
lukasrothenberger Nov 10, 2023
e71193e
feat(code_generator): indentation of pragmas
goerlibe Nov 10, 2023
6316739
fix: offset number of removed lines in case of multiple lines
lukasrothenberger Nov 14, 2023
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: 5 additions & 1 deletion discopop_explorer/discopop_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import pstats2 # type:ignore
from pluginbase import PluginBase # type:ignore

from discopop_library.PathManagement.PathManagement import get_path
from discopop_library.LineMapping.initialize import initialize_line_mapping
from discopop_library.PathManagement.PathManagement import get_path, load_file_mapping
from discopop_library.discopop_optimizer.Microbench.ExtrapInterpolatedMicrobench import (
ExtrapInterpolatedMicrobench,
)
Expand Down Expand Up @@ -215,6 +216,9 @@ def run(arguments: ExplorerArguments):
stats = pstats2.Stats(profile, stream=f).sort_stats("time").reverse_order()
stats.print_stats()

# initialize the line_mapping.json
initialize_line_mapping(load_file_mapping(arguments.file_mapping_file), arguments.project_path)

print("Time taken for pattern detection: {0}".format(end - start))

# demonstration of Microbenchmark possibilities
Expand Down
Empty file.
149 changes: 149 additions & 0 deletions discopop_library/LineMapping/diff_modifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import os.path
import subprocess
from typing import List, Tuple, Dict, Optional, Set

from discopop_library.LineMapping.load import load_line_mapping
from discopop_library.LineMapping.save import save_line_mapping


def apply_line_mapping_modifications_from_files(file_id: int, original_file: str, modified_file: str):
"""Calculates diff between original_file and modified_file and applied modifications from the diff to the line_mapping"""
if not os.path.exists(original_file):
raise FileNotFoundError(original_file)
if not os.path.exists(modified_file):
raise FileNotFoundError(original_file)

command = [
"diff",
original_file,
modified_file,
]
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
if result.returncode == 1:
# print("RESULT: ", result.returncode)
# print("STDERR:")
# print(result.stderr)
# print("STDOUT: ")
# print(result.stdout)
diff = result.stdout
# print("DIFF: ", diff)
else:
diff = ""

apply_line_mapping_modifications_from_diff(file_id, diff)


def apply_line_mapping_modifications_from_diff(file_id: int, diff: str):
"""parse diff, apply line num modifications according to c,a,d values"""
# get cleaned diff
cleaned_diff: List[str] = []
for line in diff.split("\n"):
if line.startswith("<") or line.startswith(">") or line.startswith("-") or len(line) == 0:
continue
line = line.replace("\n", "")
cleaned_diff.append(line)

# get line_mapping
line_mapping: Dict[str, Dict[str, int]] = load_line_mapping()

deletions: Set[str] = set()
shifts: Dict[str, int] = dict()

# initialize
for key in line_mapping[str(file_id)]:
shifts[key] = 0

# parse diff
for diff_entry in cleaned_diff:
if "a" in diff_entry:
lhs = diff_entry[: diff_entry.index("a")]
if "," in lhs:
base_line = int(lhs.split(",")[0])
else:
base_line = int(lhs)
rhs = diff_entry[diff_entry.index("a") + 1 :]
if "," in rhs:
added_lines_count = int(rhs.split(",")[1]) - int(rhs.split(",")[0])
lukasrothenberger marked this conversation as resolved.
Show resolved Hide resolved
else:
added_lines_count = 1
for key in line_mapping[str(file_id)]:
if line_mapping[str(file_id)][key] < 0:
# invalid due to deletion
continue
if line_mapping[str(file_id)][key] > base_line:
shifts[key] += added_lines_count

if "d" in diff_entry:
lhs = diff_entry[: diff_entry.index("d")]
if "," in lhs:
base_line = int(lhs.split(",")[0])
deleted_lines_count = int(lhs.split(",")[1]) - int(lhs.split(",")[0])
else:
base_line = int(lhs)
deleted_lines_count = 1
for key in line_mapping[str(file_id)]:
if line_mapping[str(file_id)][key] < 0:
# invalid due to deletion
continue
if line_mapping[str(file_id)][key] in range(base_line, base_line + deleted_lines_count):
deletions.add(key)
elif line_mapping[str(file_id)][key] >= base_line + deleted_lines_count:
shifts[key] -= deleted_lines_count

if "c" in diff_entry:
lhs = diff_entry[: diff_entry.index("c")]
if "," in lhs:
base_line = int(lhs.split(",")[0])
deleted_lines_count = int(lhs.split(",")[1]) - int(lhs.split(",")[0])
else:
base_line = int(lhs)
deleted_lines_count = 1
rhs = diff_entry[diff_entry.index("c") + 1 :]
if "," in rhs:
added_lines_count = int(rhs.split(",")[1]) - int(rhs.split(",")[0])
lukasrothenberger marked this conversation as resolved.
Show resolved Hide resolved
else:
added_lines_count = 1

for key in line_mapping[str(file_id)]:
if line_mapping[str(file_id)][key] < 0:
# invalid due to deletion
continue
if line_mapping[str(file_id)][key] > base_line:
shifts[key] += added_lines_count

for key in line_mapping[str(file_id)]:
if line_mapping[str(file_id)][key] < 0:
# invalid due to deletion
continue
if line_mapping[str(file_id)][key] in range(base_line, base_line + deleted_lines_count):
deletions.add(key)
elif line_mapping[str(file_id)][key] >= base_line + deleted_lines_count:
shifts[key] -= deleted_lines_count

# apply deletions
for key in deletions:
line_mapping[str(file_id)][key] = -1

# apply shifts
for key in shifts:
if line_mapping[str(file_id)][key] < 0:
# invalid due to deletion
continue
line_mapping[str(file_id)][key] = line_mapping[str(file_id)][key] + shifts[key]

# save updated line mapping
save_line_mapping(line_mapping)

pass
40 changes: 40 additions & 0 deletions discopop_library/LineMapping/initialize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.

import json
import os.path
from pathlib import Path
from typing import Dict

from discopop_library.LineMapping.load import load_line_mapping
from discopop_library.LineMapping.save import save_line_mapping


def initialize_line_mapping(
file_mapping: Dict[int, Path],
discopop_path: str = ".discopop",
):
"""initializes the line mapping dictionary to track line shifts due to inserted pragmas.
The Dictionary will be stored in .discopop/line_mapping.json.
Line ids start with 1."""
line_mapping_dict: Dict[str, Dict[str, int]] = dict()

# initialize line mapping (1->1, 2->2, ...)
for file_id in file_mapping:
if str(file_id) not in line_mapping_dict:
line_mapping_dict[str(file_id)] = dict()
line_id = 1
with open(file_mapping[file_id], "r") as f:
for line in f.readlines():
line_mapping_dict[str(file_id)][str(line_id)] = line_id
line_id += 1

save_line_mapping(line_mapping_dict, discopop_path)

# debug
load_line_mapping(discopop_path)
18 changes: 18 additions & 0 deletions discopop_library/LineMapping/load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import json
import os.path
from typing import Dict


def load_line_mapping(discopop_path: str = "") -> Dict[str, Dict[str, int]]:
"""Loads and returns the line_mapping dictionary"""
line_mapping_dict: Dict[str, Dict[str, int]] = dict()
with open(os.path.join(discopop_path, "line_mapping.json")) as f:
line_mapping_dict = json.load(f)
return line_mapping_dict
17 changes: 17 additions & 0 deletions discopop_library/LineMapping/save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import json
import os
from typing import Dict


def save_line_mapping(line_mapping_dict: Dict[str, Dict[str, int]], discopop_path: str = ""):
"""dumps line_mapping_dict to line_mapping.json"""
# dump line mapping to json file
with open(os.path.join(discopop_path, "line_mapping.json"), "w+") as f:
json.dump(line_mapping_dict, f)
50 changes: 50 additions & 0 deletions discopop_library/PatchApplicator/PatchApplicatorArguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import sys
from dataclasses import dataclass
from typing import List


@dataclass
class PatchApplicatorArguments(object):
"""Container Class for the arguments passed to the discopop_patch_applicator"""

verbose: bool
apply: List[str]
rollback: List[str]
clear: bool
load: bool
list: bool

def __post_init__(self):
self.__validate()

def __validate(self):
"""Validate the arguments passed to the discopop_patch_applicator, e.g check if given files exist"""
# check mutually exclusive arguments
exit_required = False
if self.clear and self.load:
print("Please use only one of `--clear` or `--load`.")
exit_required = True
if len(self.apply) > 0 and len(self.rollback) > 0:
print("Please use only one of '--apply' or '--rollback'.")
exit_required = True
if self.clear and (len(self.apply) > 0 or len(self.rollback)):
print("Please use either '--clear' or any of '--apply' or '--rollback'.")
exit_required = True
if self.load and (len(self.apply) > 0 or len(self.rollback)):
print("Please use either '--load' or any of '--apply' or '--rollback'.")
exit_required = True
if self.list:
self.apply = []
self.rollback = []
self.clear = False
self.load = False
if exit_required:
print("Exiting.")
sys.exit(0)
Empty file.
68 changes: 68 additions & 0 deletions discopop_library/PatchApplicator/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.

from argparse import ArgumentParser

from discopop_library.PatchApplicator.PatchApplicatorArguments import PatchApplicatorArguments
from discopop_library.PatchApplicator.patch_applicator import run


def parse_args() -> PatchApplicatorArguments:
"""Parse the arguments passed to the discopop_explorer"""
parser = ArgumentParser(description="DiscoPoP Patch Generator")
# all flags that are not considered stable should be added to the experimental_parser
experimental_parser = parser.add_argument_group(
"EXPERIMENTAL",
"Arguments for the task pattern detector and other experimental features. These flags are considered EXPERIMENTAL and they may or may not be removed or changed in the near future.",
)

# fmt: off
parser.add_argument("-v", "--verbose", action="store_true",
help="Enable verbose output.")
parser.add_argument('-a', '--apply', nargs='+', default=[], help="Apply the parallelization suggestions with the "
"given ids.")
parser.add_argument('-r', '--rollback', nargs='+', default=[], help="Roll back the application of the "
"parallelization suggestions with the given "
"ids.")
parser.add_argument('-C', '--clear', action="store_true", help="Reset the code to it's original state. Preserves "
"the list of applied suggestion for loading."
"Old saves will be overwritten!")
# todo: in the long run, saving configurations to different locations could be easily implemented.

parser.add_argument('-L', '--load', action="store_true", help="Load a previous state after clearing.")
parser.add_argument('-l', '--list', action="store_true", help="Show the list of applied suggestions."
"If set, nothing else will be done.")
# EXPERIMENTAL FLAGS:
# fmt: on

arguments = parser.parse_args()

return PatchApplicatorArguments(
verbose=arguments.verbose,
apply=arguments.apply,
rollback=arguments.rollback,
clear=arguments.clear,
load=arguments.load,
list=arguments.list,
)


def main() -> int:
"""Return values:"
"0: Applied successfully"
"1: Nothing applied"
"2: Some changes applied successfully
"""
retval = 0
arguments = parse_args()
retval = run(arguments)
return retval


if __name__ == "__main__":
main()
Loading