| #! /usr/bin/env python3 |
| # -*- coding: UTF-8 -*- |
| |
| # Polly/LLVM update_check.py |
| # Update lit FileCheck files by replacing the 'CHECK:' lines by the actual output of the 'RUN:' command. |
| |
| import argparse |
| import os |
| import subprocess |
| import shlex |
| import re |
| |
| |
| polly_src_dir = '''@POLLY_SOURCE_DIR@''' |
| polly_lib_dir = '''@POLLY_LIB_DIR@''' |
| shlibext = '''@LLVM_SHLIBEXT@''' |
| llvm_tools_dir = '''@LLVM_TOOLS_DIR@''' |
| link_polly_into_tools = not '''@LINK_POLLY_INTO_TOOLS@'''.lower() in {'','0','n','no','off','false','notfound','link_polly_into_tools-notfound'} |
| |
| runre = re.compile(r'\s*\;\s*RUN\s*\:(?P<tool>.*)') |
| filecheckre = re.compile(r'\s*(?P<tool>.*)\|\s*(?P<filecheck>FileCheck\s[^|]*)') |
| emptyline = re.compile(r'\s*(\;\s*)?') |
| commentline = re.compile(r'\s*(\;.*)?') |
| |
| |
| def ltrim_emptylines(lines,meta=None): |
| while len(lines) and emptyline.fullmatch(lines[0]): |
| del lines[0] |
| if meta is not None: |
| del meta[0] |
| |
| |
| def rtrim_emptylines(lines): |
| while len(lines) and emptyline.fullmatch(lines[-1]): |
| del lines[-1] |
| |
| |
| def trim_emptylines(lines): |
| ltrim_emptylines(lines) |
| rtrim_emptylines(lines) |
| |
| |
| def complete_exename(path, filename): |
| complpath = os.path.join(path, filename) |
| if os.path.isfile(complpath): |
| return complpath |
| elif os.path.isfile(complpath + '.exe'): |
| return complpath + '.exe' |
| return filename |
| |
| |
| def indention(line): |
| for i,c in enumerate(line): |
| if c != ' ' and c != '\t': |
| return i |
| return None |
| |
| |
| def common_indent(lines): |
| indentions = (indention(line) for line in lines) |
| indentions = (indent for indent in indentions if indent is not None) |
| return min(indentions,default=0) |
| |
| |
| funcre = re.compile(r'^ Function: \S*$') |
| regionre = re.compile(r'^ Region: \S*$') |
| depthre = re.compile(r'^ Max Loop Depth: .*') |
| paramre = re.compile(r' [0-9a-z-A-Z_]+\: .*') |
| |
| def classyfier1(lines): |
| i = iter(lines) |
| line = i.__next__() |
| while True: |
| if line.startswith("Printing analysis 'Polly - Calculate dependences' for region: "): |
| yield {'PrintingDependenceInfo'} |
| elif line.startswith("remark: "): |
| yield {'Remark'} |
| elif funcre.fullmatch(line): |
| yield {'Function'} |
| elif regionre.fullmatch(line): |
| yield { 'Region'} |
| elif depthre.fullmatch(line): |
| yield {'MaxLoopDepth'} |
| elif line == ' Invariant Accesses: {': |
| while True: |
| yield { 'InvariantAccesses'} |
| if line == ' }': |
| break |
| line = i.__next__() |
| elif line == ' Context:': |
| yield {'Context'} |
| line = i.__next__() |
| yield {'Context'} |
| elif line == ' Assumed Context:': |
| yield {'AssumedContext'} |
| line = i.__next__() |
| yield {'AssumedContext'} |
| elif line == ' Invalid Context:': |
| yield {'InvalidContext'} |
| line = i.__next__() |
| yield {'InvalidContext'} |
| elif line == ' Boundary Context:': |
| yield {'BoundaryContext'} |
| line = i.__next__() |
| yield {'BoundaryContext'} |
| line = i.__next__() |
| while paramre.fullmatch(line): |
| yield {'Param'} |
| line = i.__next__() |
| continue |
| elif line == ' Arrays {': |
| while True: |
| yield {'Arrays'} |
| if line == ' }': |
| break |
| line = i.__next__() |
| elif line == ' Arrays (Bounds as pw_affs) {': |
| while True: |
| yield {'PwAffArrays'} |
| if line == ' }': |
| break |
| line = i.__next__() |
| elif line.startswith(' Alias Groups ('): |
| while True: |
| yield {'AliasGroups'} |
| line = i.__next__() |
| if not line.startswith(' '): |
| break |
| continue |
| elif line == ' Statements {': |
| while True: |
| yield {'Statements'} |
| if line == ' }': |
| break |
| line = i.__next__() |
| elif line == ' RAW dependences:': |
| yield {'RAWDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| while line.startswith(' '): |
| yield {'RAWDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| continue |
| elif line == ' WAR dependences:': |
| yield {'WARDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| while line.startswith(' '): |
| yield {'WARDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| continue |
| elif line == ' WAW dependences:': |
| yield {'WAWDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| while line.startswith(' '): |
| yield {'WAWDep','BasicDep','Dep','DepInfo'} |
| line = i.__next__() |
| continue |
| elif line == ' Reduction dependences:': |
| yield {'RedDep','Dep','DepInfo'} |
| line = i.__next__() |
| while line.startswith(' '): |
| yield {'RedDep','Dep','DepInfo'} |
| line = i.__next__() |
| continue |
| elif line == ' Transitive closure of reduction dependences:': |
| yield {'TransitiveClosureDep','DepInfo'} |
| line = i.__next__() |
| while line.startswith(' '): |
| yield {'TransitiveClosureDep','DepInfo'} |
| line = i.__next__() |
| continue |
| elif line.startswith("New access function '"): |
| yield {'NewAccessFunction'} |
| elif line == 'Schedule before flattening {': |
| while True: |
| yield {'ScheduleBeforeFlattening'} |
| if line == '}': |
| break |
| line = i.__next__() |
| elif line == 'Schedule after flattening {': |
| while True: |
| yield {'ScheduleAfterFlattening'} |
| if line == '}': |
| break |
| line = i.__next__() |
| else: |
| yield set() |
| line = i.__next__() |
| |
| |
| def classyfier2(lines): |
| i = iter(lines) |
| line = i.__next__() |
| while True: |
| if funcre.fullmatch(line): |
| while line.startswith(' '): |
| yield {'FunctionDetail'} |
| line = i.__next__() |
| continue |
| elif line.startswith("Printing analysis 'Polly - Generate an AST from the SCoP (isl)' for region: "): |
| yield {'PrintingIslAst'} |
| line = i.__next__() |
| while not line.startswith('Printing analysis'): |
| yield {'AstDetail'} |
| line = i.__next__() |
| continue |
| else: |
| yield set() |
| line = i.__next__() |
| |
| |
| replrepl = {'{{':'{{[{][{]}}','}}': '{{[}][}]}}', '[[':'{{\[\[}}',']]': '{{\]\]}}'} |
| replre = re.compile('|'.join(re.escape(k) for k in replrepl.keys())) |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="Update CHECK lines") |
| parser.add_argument('testfile',help="File to update (absolute or relative to --testdir)") |
| parser.add_argument('--check-style',choices=['CHECK','CHECK-NEXT'],default='CHECK-NEXT',help="What kind of checks lines to generate") |
| parser.add_argument('--check-position',choices=['end','before-content','autodetect'],default='autodetect',help="Where to add the CHECK lines into the file; 'autodetect' searches for the first 'CHECK' line ind inserts it there") |
| parser.add_argument('--check-include',action='append',default=[], help="What parts of the output lines to check; use syntax 'CHECK=include' to apply to one CHECK-prefix only (by default, everything)") |
| parser.add_argument('--check-label-include',action='append',default=[],help="Use CHECK-LABEL for these includes") |
| parser.add_argument('--check-part-newline',action='store_true',help="Add empty line between different check parts") |
| parser.add_argument('--prefix-only',action='append',default=None,help="Update only these prefixes (default: all)") |
| parser.add_argument('--bindir',help="Location of the opt program") |
| parser.add_argument('--testdir',help="Root dir for unit tests") |
| parser.add_argument('--inplace','-i',action='store_true',help="Replace input file") |
| parser.add_argument('--output','-o',help="Write changed input to this file") |
| known = parser.parse_args() |
| |
| if not known.inplace and known.output is None: |
| print("Must specify what to do with output (--output or --inplace)") |
| exit(1) |
| if known.inplace and known.output is not None: |
| print("--inplace and --output are mutually exclusive") |
| exit(1) |
| |
| outfile = known.output |
| |
| filecheckparser = argparse.ArgumentParser(add_help=False) |
| filecheckparser.add_argument('-check-prefix','--check-prefix',default='CHECK') |
| |
| filename = known.testfile |
| for dir in ['.', known.testdir, os.path.join(polly_src_dir,'test'), polly_src_dir]: |
| if not dir: |
| continue |
| testfilename = os.path.join(dir,filename) |
| if os.path.isfile(testfilename): |
| filename = testfilename |
| break |
| |
| if known.inplace: |
| outfile = filename |
| |
| allchecklines = [] |
| checkprefixes = [] |
| |
| with open(filename, 'r') as file: |
| oldlines = [line.rstrip('\r\n') for line in file.readlines()] |
| |
| runlines = [] |
| for line in oldlines: |
| m = runre.match(line) |
| if m: |
| runlines.append(m.group('tool')) |
| |
| continuation = '' |
| newrunlines = [] |
| for line in runlines: |
| if line.endswith('\\'): |
| continuation += line[:-2] + ' ' |
| else: |
| newrunlines.append(continuation + line) |
| continuation = '' |
| if continuation: |
| newrunlines.append(continuation) |
| |
| for line in newrunlines: |
| m = filecheckre.match(line) |
| if not m: |
| continue |
| |
| tool, filecheck = m.group('tool', 'filecheck') |
| filecheck = shlex.split(filecheck) |
| tool = shlex.split(tool) |
| if known.bindir is not None: |
| tool[0] = complete_exename(known.bindir, tool[0]) |
| if os.path.isdir(llvm_tools_dir): |
| tool[0] = complete_exename(llvm_tools_dir, tool[0]) |
| check_prefix = filecheckparser.parse_known_args(filecheck)[0].check_prefix |
| if known.prefix_only is not None and not check_prefix in known.prefix_only: |
| continue |
| if check_prefix in checkprefixes: |
| continue |
| checkprefixes.append(check_prefix) |
| |
| newtool = [] |
| optstderr = None |
| for toolarg in tool: |
| toolarg = toolarg.replace('%s', filename) |
| toolarg = toolarg.replace('%S', os.path.dirname(filename)) |
| if toolarg == '%loadPolly': |
| if not link_polly_into_tools: |
| newtool += ['-load',os.path.join(polly_lib_dir,'LLVMPolly' + shlibext)] |
| newtool.append('-polly-process-unprofitable') |
| newtool.append('-polly-remarks-minimal') |
| elif toolarg == '2>&1': |
| optstderr = subprocess.STDOUT |
| else: |
| newtool.append(toolarg) |
| tool = newtool |
| |
| inpfile = None |
| i = 1 |
| while i < len(tool): |
| if tool[i] == '<': |
| inpfile = tool[i + 1] |
| del tool[i:i + 2] |
| continue |
| i += 1 |
| if inpfile: |
| with open(inpfile) as inp: |
| retlines = subprocess.check_output(tool,universal_newlines=True,stdin=inp,stderr=optstderr) |
| else: |
| retlines = subprocess.check_output(tool,universal_newlines=True,stderr=optstderr) |
| retlines = [line.replace('\t', ' ') for line in retlines.splitlines()] |
| check_include = [] |
| for checkme in known.check_include + known.check_label_include: |
| parts = checkme.split('=') |
| if len(parts) == 2: |
| if parts[0] == check_prefix: |
| check_include.append(parts[1]) |
| else: |
| check_include.append(checkme) |
| |
| if check_include: |
| filtered_retlines = [] |
| classified_retlines = [] |
| lastmatch = None |
| for line,kind in ((line,class1.union(class2)) for line,class1,class2 in zip(retlines,classyfier1(retlines), classyfier2(retlines))): |
| match = kind.intersection(check_include) |
| if match: |
| if lastmatch != match: |
| filtered_retlines.append('') |
| classified_retlines.append({'Separator'}) |
| filtered_retlines.append(line) |
| classified_retlines.append(kind) |
| lastmatch = match |
| |
| retlines = filtered_retlines |
| else: |
| classified_retlines = (set() for line in retlines) |
| |
| rtrim_emptylines(retlines) |
| ltrim_emptylines(retlines,classified_retlines) |
| retlines = [replre.sub(lambda m: replrepl[m.group(0)], line) for line in retlines] |
| indent = common_indent(retlines) |
| retlines = [line[indent:] for line in retlines] |
| checklines = [] |
| previous_was_empty = True |
| for line,kind in zip(retlines,classified_retlines): |
| if line: |
| if known.check_style == 'CHECK' and known.check_label_include: |
| if not kind.isdisjoint(known.check_label_include): |
| checklines.append('; ' + check_prefix + '-LABEL: ' + line) |
| else: |
| checklines.append('; ' + check_prefix + ': ' + line) |
| elif known.check_style == 'CHECK': |
| checklines.append('; ' + check_prefix + ': ' + line) |
| elif known.check_label_include and known.check_label_include: |
| if not kind.isdisjoint(known.check_label_include): |
| checklines.append('; ' + check_prefix + '-LABEL: ' + line) |
| elif previous_was_empty: |
| checklines.append('; ' + check_prefix + ': ' + line) |
| else: |
| checklines.append('; ' + check_prefix + '-NEXT: ' + line) |
| else: |
| if previous_was_empty: |
| checklines.append('; ' + check_prefix + ': ' + line) |
| else: |
| checklines.append('; ' + check_prefix + '-NEXT: ' + line) |
| previous_was_empty = False |
| else: |
| if not 'Separator' in kind or known.check_part_newline: |
| checklines.append(';') |
| previous_was_empty = True |
| allchecklines.append(checklines) |
| |
| if not checkprefixes: |
| return |
| |
| checkre = re.compile(r'^\s*\;\s*(' + '|'.join([re.escape(s) for s in checkprefixes]) + ')(\-NEXT|\-DAG|\-NOT|\-LABEL|\-SAME)?\s*\:') |
| firstcheckline = None |
| firstnoncommentline = None |
| headerlines = [] |
| newlines = [] |
| uptonowlines = [] |
| emptylines = [] |
| lastwascheck = False |
| for line in oldlines: |
| if checkre.match(line): |
| if firstcheckline is None: |
| firstcheckline = len(newlines) + len(emptylines) |
| if not lastwascheck: |
| uptonowlines += emptylines |
| emptylines = [] |
| lastwascheck = True |
| elif emptyline.fullmatch(line): |
| emptylines.append(line) |
| else: |
| newlines += uptonowlines |
| newlines += emptylines |
| newlines.append(line) |
| emptylines = [] |
| uptonowlines = [] |
| lastwascheck = False |
| |
| for i,line in enumerate(newlines): |
| if not commentline.fullmatch(line): |
| firstnoncommentline = i |
| break |
| |
| with open(outfile,'w',newline='') as file: |
| def writelines(lines): |
| for line in lines: |
| file.write(line) |
| file.write('\n') |
| |
| if firstcheckline is not None and known.check_position == 'autodetect': |
| writelines(newlines[:firstcheckline]) |
| writelines(uptonowlines) |
| for i,checklines in enumerate(allchecklines): |
| if i != 0: |
| file.write('\n') |
| writelines(checklines) |
| writelines(newlines[firstcheckline:]) |
| writelines(emptylines) |
| elif firstnoncommentline is not None and known.check_position == 'before-content': |
| headerlines = newlines[:firstnoncommentline] |
| rtrim_emptylines(headerlines) |
| contentlines = newlines[firstnoncommentline:] |
| ltrim_emptylines(contentlines) |
| |
| writelines(headerlines) |
| for checklines in allchecklines: |
| file.write('\n') |
| writelines(checklines) |
| file.write('\n') |
| writelines(contentlines) |
| writelines(uptonowlines) |
| writelines(emptylines) |
| else: |
| writelines(newlines) |
| rtrim_emptylines(newlines) |
| for checklines in allchecklines: |
| file.write('\n\n') |
| writelines(checklines) |
| |
| |
| if __name__ == '__main__': |
| main() |