llvm-project
528 строк · 17.7 Кб
1#!/usr/bin/env python3
2# -*- coding: UTF-8 -*-
3
4# Polly/LLVM update_check.py
5# Update lit FileCheck files by replacing the 'CHECK:' lines by the actual output of the 'RUN:' command.
6
7import argparse8import os9import subprocess10import shlex11import re12
13
14polly_src_dir = """@POLLY_SOURCE_DIR@"""15polly_lib_dir = """@POLLY_LIB_DIR@"""16shlibext = """@LLVM_SHLIBEXT@"""17llvm_tools_dir = """@LLVM_TOOLS_DIR@"""18llvm_polly_link_into_tools = not """@LLVM_POLLY_LINK_INTO_TOOLS@""".lower() in {19"",20"0",21"n",22"no",23"off",24"false",25"notfound",26"llvm_polly_link_into_tools-notfound",27}
28
29runre = re.compile(r"\s*\;\s*RUN\s*\:(?P<tool>.*)")30filecheckre = re.compile(r"\s*(?P<tool>.*)\|\s*(?P<filecheck>FileCheck\s[^|]*)")31emptyline = re.compile(r"\s*(\;\s*)?")32commentline = re.compile(r"\s*(\;.*)?")33
34
35def ltrim_emptylines(lines, meta=None):36while len(lines) and emptyline.fullmatch(lines[0]):37del lines[0]38if meta is not None:39del meta[0]40
41
42def rtrim_emptylines(lines):43while len(lines) and emptyline.fullmatch(lines[-1]):44del lines[-1]45
46
47def trim_emptylines(lines):48ltrim_emptylines(lines)49rtrim_emptylines(lines)50
51
52def complete_exename(path, filename):53complpath = os.path.join(path, filename)54if os.path.isfile(complpath):55return complpath56elif os.path.isfile(complpath + ".exe"):57return complpath + ".exe"58return filename59
60
61def indention(line):62for i, c in enumerate(line):63if c != " " and c != "\t":64return i65return None66
67
68def common_indent(lines):69indentions = (indention(line) for line in lines)70indentions = (indent for indent in indentions if indent is not None)71return min(indentions, default=0)72
73
74funcre = re.compile(r"^ Function: \S*$")75regionre = re.compile(r"^ Region: \S*$")76depthre = re.compile(r"^ Max Loop Depth: .*")77paramre = re.compile(r" [0-9a-z-A-Z_]+\: .*")78
79
80def classyfier1(lines):81i = iter(lines)82line = i.__next__()83while True:84if line.startswith(85"Printing analysis 'Polly - Calculate dependences' for region: "86):87yield {"PrintingDependenceInfo"}88elif line.startswith("remark: "):89yield {"Remark"}90elif funcre.fullmatch(line):91yield {"Function"}92elif regionre.fullmatch(line):93yield {"Region"}94elif depthre.fullmatch(line):95yield {"MaxLoopDepth"}96elif line == " Invariant Accesses: {":97while True:98yield {"InvariantAccesses"}99if line == " }":100break101line = i.__next__()102elif line == " Context:":103yield {"Context"}104line = i.__next__()105yield {"Context"}106elif line == " Assumed Context:":107yield {"AssumedContext"}108line = i.__next__()109yield {"AssumedContext"}110elif line == " Invalid Context:":111yield {"InvalidContext"}112line = i.__next__()113yield {"InvalidContext"}114elif line == " Boundary Context:":115yield {"BoundaryContext"}116line = i.__next__()117yield {"BoundaryContext"}118line = i.__next__()119while paramre.fullmatch(line):120yield {"Param"}121line = i.__next__()122continue123elif line == " Arrays {":124while True:125yield {"Arrays"}126if line == " }":127break128line = i.__next__()129elif line == " Arrays (Bounds as pw_affs) {":130while True:131yield {"PwAffArrays"}132if line == " }":133break134line = i.__next__()135elif line.startswith(" Alias Groups ("):136while True:137yield {"AliasGroups"}138line = i.__next__()139if not line.startswith(" "):140break141continue142elif line == " Statements {":143while True:144yield {"Statements"}145if line == " }":146break147line = i.__next__()148elif line == " RAW dependences:":149yield {"RAWDep", "BasicDep", "Dep", "DepInfo"}150line = i.__next__()151while line.startswith(" "):152yield {"RAWDep", "BasicDep", "Dep", "DepInfo"}153line = i.__next__()154continue155elif line == " WAR dependences:":156yield {"WARDep", "BasicDep", "Dep", "DepInfo"}157line = i.__next__()158while line.startswith(" "):159yield {"WARDep", "BasicDep", "Dep", "DepInfo"}160line = i.__next__()161continue162elif line == " WAW dependences:":163yield {"WAWDep", "BasicDep", "Dep", "DepInfo"}164line = i.__next__()165while line.startswith(" "):166yield {"WAWDep", "BasicDep", "Dep", "DepInfo"}167line = i.__next__()168continue169elif line == " Reduction dependences:":170yield {"RedDep", "Dep", "DepInfo"}171line = i.__next__()172while line.startswith(" "):173yield {"RedDep", "Dep", "DepInfo"}174line = i.__next__()175continue176elif line == " Transitive closure of reduction dependences:":177yield {"TransitiveClosureDep", "DepInfo"}178line = i.__next__()179while line.startswith(" "):180yield {"TransitiveClosureDep", "DepInfo"}181line = i.__next__()182continue183elif line.startswith("New access function '"):184yield {"NewAccessFunction"}185elif line == "Schedule before flattening {":186while True:187yield {"ScheduleBeforeFlattening"}188if line == "}":189break190line = i.__next__()191elif line == "Schedule after flattening {":192while True:193yield {"ScheduleAfterFlattening"}194if line == "}":195break196line = i.__next__()197else:198yield set()199line = i.__next__()200
201
202def classyfier2(lines):203i = iter(lines)204line = i.__next__()205while True:206if funcre.fullmatch(line):207while line.startswith(" "):208yield {"FunctionDetail"}209line = i.__next__()210continue211elif line.startswith(212"Printing analysis 'Polly - Generate an AST from the SCoP (isl)' for region: "213):214yield {"PrintingIslAst"}215line = i.__next__()216while not line.startswith("Printing analysis"):217yield {"AstDetail"}218line = i.__next__()219continue220else:221yield set()222line = i.__next__()223
224
225replrepl = {"{{": "{{[{][{]}}", "}}": "{{[}][}]}}", "[[": "{{\[\[}}", "]]": "{{\]\]}}"}226replre = re.compile("|".join(re.escape(k) for k in replrepl.keys()))227
228
229def main():230parser = argparse.ArgumentParser(description="Update CHECK lines")231parser.add_argument(232"testfile", help="File to update (absolute or relative to --testdir)"233)234parser.add_argument(235"--check-style",236choices=["CHECK", "CHECK-NEXT"],237default="CHECK-NEXT",238help="What kind of checks lines to generate",239)240parser.add_argument(241"--check-position",242choices=["end", "before-content", "autodetect"],243default="autodetect",244help="Where to add the CHECK lines into the file; 'autodetect' searches for the first 'CHECK' line ind inserts it there",245)246parser.add_argument(247"--check-include",248action="append",249default=[],250help="What parts of the output lines to check; use syntax 'CHECK=include' to apply to one CHECK-prefix only (by default, everything)",251)252parser.add_argument(253"--check-label-include",254action="append",255default=[],256help="Use CHECK-LABEL for these includes",257)258parser.add_argument(259"--check-part-newline",260action="store_true",261help="Add empty line between different check parts",262)263parser.add_argument(264"--prefix-only",265action="append",266default=None,267help="Update only these prefixes (default: all)",268)269parser.add_argument("--bindir", help="Location of the opt program")270parser.add_argument("--testdir", help="Root dir for unit tests")271parser.add_argument(272"--inplace", "-i", action="store_true", help="Replace input file"273)274parser.add_argument("--output", "-o", help="Write changed input to this file")275known = parser.parse_args()276
277if not known.inplace and known.output is None:278print("Must specify what to do with output (--output or --inplace)")279exit(1)280if known.inplace and known.output is not None:281print("--inplace and --output are mutually exclusive")282exit(1)283
284outfile = known.output285
286filecheckparser = argparse.ArgumentParser(add_help=False)287filecheckparser.add_argument("-check-prefix", "--check-prefix", default="CHECK")288
289filename = known.testfile290for dir in [".", known.testdir, os.path.join(polly_src_dir, "test"), polly_src_dir]:291if not dir:292continue293testfilename = os.path.join(dir, filename)294if os.path.isfile(testfilename):295filename = testfilename296break297
298if known.inplace:299outfile = filename300
301allchecklines = []302checkprefixes = []303
304with open(filename, "r") as file:305oldlines = [line.rstrip("\r\n") for line in file.readlines()]306
307runlines = []308for line in oldlines:309m = runre.match(line)310if m:311runlines.append(m.group("tool"))312
313continuation = ""314newrunlines = []315for line in runlines:316if line.endswith("\\"):317continuation += line[:-2] + " "318else:319newrunlines.append(continuation + line)320continuation = ""321if continuation:322newrunlines.append(continuation)323
324for line in newrunlines:325m = filecheckre.match(line)326if not m:327continue328
329tool, filecheck = m.group("tool", "filecheck")330filecheck = shlex.split(filecheck)331tool = shlex.split(tool)332if known.bindir is not None:333tool[0] = complete_exename(known.bindir, tool[0])334if os.path.isdir(llvm_tools_dir):335tool[0] = complete_exename(llvm_tools_dir, tool[0])336check_prefix = filecheckparser.parse_known_args(filecheck)[0].check_prefix337if known.prefix_only is not None and not check_prefix in known.prefix_only:338continue339if check_prefix in checkprefixes:340continue341checkprefixes.append(check_prefix)342
343newtool = []344optstderr = None345for toolarg in tool:346toolarg = toolarg.replace("%s", filename)347toolarg = toolarg.replace("%S", os.path.dirname(filename))348if toolarg == "%loadPolly":349if not llvm_polly_link_into_tools:350newtool += [351"-load",352os.path.join(polly_lib_dir, "LLVMPolly" + shlibext),353]354newtool.append("-polly-process-unprofitable")355newtool.append("-polly-remarks-minimal")356elif toolarg == "2>&1":357optstderr = subprocess.STDOUT358else:359newtool.append(toolarg)360tool = newtool361
362inpfile = None363i = 1364while i < len(tool):365if tool[i] == "<":366inpfile = tool[i + 1]367del tool[i : i + 2]368continue369i += 1370if inpfile:371with open(inpfile) as inp:372retlines = subprocess.check_output(373tool, universal_newlines=True, stdin=inp, stderr=optstderr374)375else:376retlines = subprocess.check_output(377tool, universal_newlines=True, stderr=optstderr378)379retlines = [line.replace("\t", " ") for line in retlines.splitlines()]380check_include = []381for checkme in known.check_include + known.check_label_include:382parts = checkme.split("=")383if len(parts) == 2:384if parts[0] == check_prefix:385check_include.append(parts[1])386else:387check_include.append(checkme)388
389if check_include:390filtered_retlines = []391classified_retlines = []392lastmatch = None393for line, kind in (394(line, class1.union(class2))395for line, class1, class2 in zip(396retlines, classyfier1(retlines), classyfier2(retlines)397)398):399match = kind.intersection(check_include)400if match:401if lastmatch != match:402filtered_retlines.append("")403classified_retlines.append({"Separator"})404filtered_retlines.append(line)405classified_retlines.append(kind)406lastmatch = match407
408retlines = filtered_retlines409else:410classified_retlines = (set() for line in retlines)411
412rtrim_emptylines(retlines)413ltrim_emptylines(retlines, classified_retlines)414retlines = [415replre.sub(lambda m: replrepl[m.group(0)], line) for line in retlines416]417indent = common_indent(retlines)418retlines = [line[indent:] for line in retlines]419checklines = []420previous_was_empty = True421for line, kind in zip(retlines, classified_retlines):422if line:423if known.check_style == "CHECK" and known.check_label_include:424if not kind.isdisjoint(known.check_label_include):425checklines.append("; " + check_prefix + "-LABEL: " + line)426else:427checklines.append("; " + check_prefix + ": " + line)428elif known.check_style == "CHECK":429checklines.append("; " + check_prefix + ": " + line)430elif known.check_label_include and known.check_label_include:431if not kind.isdisjoint(known.check_label_include):432checklines.append("; " + check_prefix + "-LABEL: " + line)433elif previous_was_empty:434checklines.append("; " + check_prefix + ": " + line)435else:436checklines.append("; " + check_prefix + "-NEXT: " + line)437else:438if previous_was_empty:439checklines.append("; " + check_prefix + ": " + line)440else:441checklines.append("; " + check_prefix + "-NEXT: " + line)442previous_was_empty = False443else:444if not "Separator" in kind or known.check_part_newline:445checklines.append(";")446previous_was_empty = True447allchecklines.append(checklines)448
449if not checkprefixes:450return451
452checkre = re.compile(453r"^\s*\;\s*("454+ "|".join([re.escape(s) for s in checkprefixes])455+ ")(\-NEXT|\-DAG|\-NOT|\-LABEL|\-SAME)?\s*\:"456)457firstcheckline = None458firstnoncommentline = None459headerlines = []460newlines = []461uptonowlines = []462emptylines = []463lastwascheck = False464for line in oldlines:465if checkre.match(line):466if firstcheckline is None:467firstcheckline = len(newlines) + len(emptylines)468if not lastwascheck:469uptonowlines += emptylines470emptylines = []471lastwascheck = True472elif emptyline.fullmatch(line):473emptylines.append(line)474else:475newlines += uptonowlines476newlines += emptylines477newlines.append(line)478emptylines = []479uptonowlines = []480lastwascheck = False481
482for i, line in enumerate(newlines):483if not commentline.fullmatch(line):484firstnoncommentline = i485break486
487with open(outfile, "w", newline="") as file:488
489def writelines(lines):490for line in lines:491file.write(line)492file.write("\n")493
494if firstcheckline is not None and known.check_position == "autodetect":495writelines(newlines[:firstcheckline])496writelines(uptonowlines)497for i, checklines in enumerate(allchecklines):498if i != 0:499file.write("\n")500writelines(checklines)501writelines(newlines[firstcheckline:])502writelines(emptylines)503elif (504firstnoncommentline is not None and known.check_position == "before-content"505):506headerlines = newlines[:firstnoncommentline]507rtrim_emptylines(headerlines)508contentlines = newlines[firstnoncommentline:]509ltrim_emptylines(contentlines)510
511writelines(headerlines)512for checklines in allchecklines:513file.write("\n")514writelines(checklines)515file.write("\n")516writelines(contentlines)517writelines(uptonowlines)518writelines(emptylines)519else:520writelines(newlines)521rtrim_emptylines(newlines)522for checklines in allchecklines:523file.write("\n\n")524writelines(checklines)525
526
527if __name__ == "__main__":528main()529