#!/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@''' llvm_polly_link_into_tools = not '''@LLVM_POLLY_LINK_INTO_TOOLS@'''.lower() in {'','0','n','no','off','false','notfound','llvm_polly_link_into_tools-notfound'} runre = re.compile(r'\s*\;\s*RUN\s*\:(?P.*)') filecheckre = re.compile(r'\s*(?P.*)\|\s*(?PFileCheck\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 llvm_polly_link_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()