[llvm] update_test_checks: keep meta variables stable by default (PR #76748)

Jannik Silvanus via llvm-commits llvm-commits at lists.llvm.org
Wed Jan 10 07:51:29 PST 2024


Nicolai =?utf-8?q?Hähnle?= <nicolai.haehnle at amd.com>,
Nicolai =?utf-8?q?Hähnle?= <nicolai.haehnle at amd.com>,
Nicolai =?utf-8?q?Hähnle?= <nicolai.haehnle at amd.com>,
Nicolai =?utf-8?q?Hähnle?= <nicolai.haehnle at amd.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/76748 at github.com>


================
@@ -1176,20 +1215,236 @@ def may_clash_with_default_check_prefix_name(check_prefix, var):
     )
 
 
+VARIABLE_TAG = "[[@@]]"
+METAVAR_RE = re.compile(r"\[\[([A-Z0-9_]+)(?::[^]]+)?\]\]")
+NUMERIC_SUFFIX_RE = re.compile(r"[0-9]*$")
+
+
+class CheckValueInfo:
+    def __init__(
+        self,
+        nameless_value: NamelessValue,
+        var: str,
+        prefix: str,
+    ):
+        self.nameless_value = nameless_value
+        self.var = var
+        self.prefix = prefix
+
+
+class CheckLineInfo:
+    def __init__(self, line, values):
+        self.line: str = line
+        self.values: List[CheckValueInfo] = values
+
+    def __repr__(self):
+        return f"CheckLineInfo(line={self.line}, self.values={self.values})"
+
+
+def remap_metavar_names(
+    orig_line_infos: List[CheckLineInfo],
+    new_line_infos: List[CheckLineInfo],
+    committed_names: Set[str],
+) -> Mapping[str, str]:
+    """
+    Map all FileCheck variable names that appear in new_line_infos to new
+    FileCheck variable names in an attempt to reduce the diff from orig_line_infos
+    to new_line_infos.
+    """
+    # Initialize uncommitted identity mappings
+    new_mapping = {}
+    for line in new_line_infos:
+        for value in line.values:
+            new_mapping[value.var] = value.var
+
+    # Recursively commit to the identity mapping or find a better one
+    def recurse(
+        orig_line_infos: List[CheckLineInfo], new_line_infos: List[CheckLineInfo]
+    ):
+        if not new_line_infos or not orig_line_infos:
+            return
+
+        lines = set()
+
+        # Search for lines that are identical on both sides, including meta
+        # variable names, and commit to those names immediately
+        for line in orig_line_infos:
+            key = (line.line.strip(), tuple(value.var for value in line.values))
+            lines.add(key)
+
+        for line in new_line_infos:
+            key = (
+                line.line.strip(),
+                tuple(new_mapping[value.var] for value in line.values),
+            )
+            if key in lines:
+                for value in line.values:
+                    committed_names.add(new_mapping[value.var])
+
+        # Search for lines that are unique on both sides if we only consider
+        # variable names that have been committed.
+        lines = collections.defaultdict(lambda: [None, None])
+        for i, line in enumerate(orig_line_infos):
+            key = (
+                line.line.strip(),
+                tuple(
+                    value.var for value in line.values if value.var in committed_names
+                ),
+            )
+            entry = lines[key]
+            if entry[0] is None:
+                entry[0] = i
+            else:
+                entry[0] = False
+
+        for i, line in enumerate(new_line_infos):
+            key = (
+                line.line.strip(),
+                tuple(
+                    new_mapping[value.var]
+                    for value in line.values
+                    if new_mapping[value.var] in committed_names
+                ),
+            )
+            entry = lines[key]
+            if entry[1] is None:
+                entry[1] = i
+            else:
+                entry[1] = False
+
+        unique_matches = []
+        for entry in lines.values():
+            if (
+                entry[0] is not None
+                and entry[0] is not False
+                and entry[1] is not None
+                and entry[1] is not False
+            ):
+                unique_matches.append((entry[0], entry[1]))
+
+        if not unique_matches:
+            # There are no unique matches. This is the recursion base case.
+            return
+
+        # Compute a maximal crossing-free matching via dynamic programming
+        unique_matches.sort(key=lambda entry: entry[0])
+
+        backlinks = []
+        table = []
+        for _, new_idx in unique_matches:
+            ti = bisect.bisect_left(table, new_idx, key=lambda entry: entry[0])
+            if ti < len(table):
+                table[ti] = (new_idx, len(backlinks))
+            else:
+                table.append((new_idx, len(backlinks)))
+            if ti > 0:
+                backlinks.append(table[ti - 1][1])
+            else:
+                backlinks.append(None)
+
+        # Commit to names in the matching, re-checking compatibility as we go along
+        match_idx = table[-1][1]
+        matches = [(len(orig_line_infos), len(new_line_infos))]
+        while match_idx is not None:
+            orig_idx, new_idx = unique_matches[match_idx]
+            local_commits = {}
+
+            for orig_value, new_value in zip(
+                orig_line_infos[orig_idx].values, new_line_infos[new_idx].values
+            ):
+                if new_mapping[new_value.var] in committed_names:
+                    # The new value has already been committed by a previous match we considered
+                    # during the outer loop. If it was mapped to the same name as the original value,
+                    # we can consider committing other values from this line. Otherwise, we should
+                    # ignore this line.
+                    if new_mapping[new_value.var] == orig_value.var:
+                        continue
+                    else:
+                        break
+
+                if new_value.var in local_commits:
+                    # Same, but for a possible commit happening on the same line
+                    if local_commits[new_value.var] == orig_value.var:
+                        continue
+                    else:
+                        break
+
+                if orig_value.var in committed_names:
+                    # We can't map this value because the name we would map it to has already been
+                    # committed for something else. Give up on this line.
+                    break
+
+                local_commits[new_value.var] = orig_value.var
+            else:
+                # No reason not to add any commitments for this line
+                for new_var, orig_var in local_commits.items():
+                    new_mapping[new_var] = orig_var
+                    committed_names.add(orig_var)
+
+                    if (
+                        orig_var != new_var
+                        and orig_var in new_mapping
+                        and new_mapping[orig_var] == orig_var
+                    ):
+                        new_mapping[orig_var] = "conflict_" + orig_var
+
+                matches.append((orig_idx, new_idx))
+
+            match_idx = backlinks[match_idx]
+
----------------
jasilvanus wrote:

Maybe add a comment (and possibly assert) that now matches is a list of all line pairs to be matched in decreasing order, and we are about to recurse into each interval *between* consecutive matches?

https://github.com/llvm/llvm-project/pull/76748


More information about the llvm-commits mailing list