diff options
Diffstat (limited to 'llvm/utils')
-rwxr-xr-x | llvm/utils/abtest.py | 232 | ||||
-rwxr-xr-x | llvm/utils/abtest/mark_aarch64fns.py | 65 | ||||
-rwxr-xr-x | llvm/utils/abtest/mark_armfns.py | 54 |
3 files changed, 232 insertions, 119 deletions
diff --git a/llvm/utils/abtest.py b/llvm/utils/abtest.py new file mode 100755 index 00000000000..1219dbae1b2 --- /dev/null +++ b/llvm/utils/abtest.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# +# Given a previous good compile narrow down miscompiles. +# Expects two directories named "before" and "after" each containing a set of +# assembly or object files where the "after" version is assumed to be broken. +# You also have to provide a script called "link_test". It is called with a list +# of files which should be linked together and result tested. "link_test" should +# returns with exitcode 0 if the linking and testing succeeded. +# +# abtest.py operates by taking all files from the "before" directory and +# in each step replacing one of them with a file from the "bad" directory. +# +# Additionally you can perform the same steps with a single .s file. In this +# mode functions are identified by " -- Begin function FunctionName" and +# " -- End function" markers. The abtest.py then takes all +# function from the file in the "before" directory and replaces one function +# with the corresponding function from the "bad" file in each step. +# +# Example usage to identify miscompiled files: +# 1. Create a link_test script, make it executable. Simple Example: +# clang "$@" -o /tmp/test && /tmp/test || echo "PROBLEM" +# 2. Run the script to figure out which files are miscompiled: +# > ./abtest.py +# somefile.s: ok +# someotherfile.s: skipped: same content +# anotherfile.s: failed: './link_test' exitcode != 0 +# ... +# Example usage to identify miscompiled functions inside a file: +# 3. Run the tests on a single file (assuming before/file.s and +# after/file.s exist) +# > ./abtest.py file.s +# funcname1 [0/XX]: ok +# funcname2 [1/XX]: ok +# funcname3 [2/XX]: skipped: same content +# funcname4 [3/XX]: failed: './link_test' exitcode != 0 +# ... +from fnmatch import filter +from sys import stderr +import argparse +import filecmp +import os +import subprocess +import sys + +LINKTEST="./link_test" +ESCAPE="\033[%sm" +BOLD=ESCAPE % "1" +RED=ESCAPE % "31" +NORMAL=ESCAPE % "0" +FAILED=RED+"failed"+NORMAL + +def find(dir, file_filter=None): + files = [walkdir[0]+"/"+file for walkdir in os.walk(dir) for file in walkdir[2]] + if file_filter != None: + files = filter(files, file_filter) + return files + +def error(message): + stderr.write("Error: %s\n" % (message,)) + +def warn(message): + stderr.write("Warning: %s\n" % (message,)) + +def extract_functions(file): + functions = [] + in_function = None + for line in open(file): + marker = line.find(" -- Begin function ") + if marker != -1: + if in_function != None: + warn("Missing end of function %s" % (in_function,)) + funcname = line[marker + 19:-1] + in_function = funcname + text = line + continue + + marker = line.find(" -- End function") + if marker != -1: + text += line + functions.append( (in_function, text) ) + in_function = None + continue + + if in_function != None: + text += line + return functions + +def replace_function(file, function, replacement, dest): + out = open(dest, "w") + skip = False + found = False + in_function = None + for line in open(file): + marker = line.find(" -- Begin function ") + if marker != -1: + if in_function != None: + warn("Missing end of function %s" % (in_function,)) + funcname = line[marker + 19:-1] + in_function = funcname + if in_function == function: + out.write(replacement) + skip = True + else: + marker = line.find(" -- End function") + if marker != -1: + in_function = None + if skip: + skip = False + continue + + if not skip: + out.write(line) + +def announce_test(name): + stderr.write("%s%s%s: " % (BOLD, name, NORMAL)) + stderr.flush() + +def announce_result(result, info): + stderr.write(result) + if info != "": + stderr.write(": %s" % info) + stderr.write("\n") + stderr.flush() + +def testrun(files): + linkline="%s %s" % (LINKTEST, " ".join(files),) + res = subprocess.call(linkline, shell=True) + if res != 0: + announce_result(FAILED, "'%s' exitcode != 0" % LINKTEST) + return False + else: + announce_result("ok", "") + return True + +def check_files(): + """Check files mode""" + for i in range(0, len(NO_PREFIX)): + f = NO_PREFIX[i] + b=baddir+"/"+f + if b not in BAD_FILES: + warn("There is no corresponding file to '%s' in %s" \ + % (gooddir+"/"+f, baddir)) + continue + + announce_test(f + " [%s/%s]" % (i+1, len(NO_PREFIX))) + + # combine files (everything from good except f) + testfiles=[] + skip=False + for c in NO_PREFIX: + badfile = baddir+"/"+c + goodfile = gooddir+"/"+c + if c == f: + testfiles.append(badfile) + if filecmp.cmp(goodfile, badfile): + announce_result("skipped", "same content") + skip = True + break + else: + testfiles.append(goodfile) + if skip: + continue + testrun(testfiles) + +def check_functions_in_file(base, goodfile, badfile): + functions = extract_functions(goodfile) + if len(functions) == 0: + warn("Couldn't find any function in %s, missing annotations?" % (goodfile,)) + return + badfunctions = dict(extract_functions(badfile)) + if len(functions) == 0: + warn("Couldn't find any function in %s, missing annotations?" % (badfile,)) + return + + COMBINED="/tmp/combined.s" + i = 0 + for (func,func_text) in functions: + announce_test(func + " [%s/%s]" % (i+1, len(functions))) + i+=1 + if func not in badfunctions: + warn("Function '%s' missing from bad file" % func) + continue + if badfunctions[func] == func_text: + announce_result("skipped", "same content") + continue + replace_function(goodfile, func, badfunctions[func], COMBINED) + testfiles=[] + for c in NO_PREFIX: + if c == base: + testfiles.append(COMBINED) + continue + testfiles.append(gooddir + "/" + c) + + testrun(testfiles) + +parser = argparse.ArgumentParser() +parser.add_argument('--a', dest='dir_a', default='before') +parser.add_argument('--b', dest='dir_b', default='after') +parser.add_argument('--insane', help='Skip sanity check', action='store_true') +parser.add_argument('file', metavar='file', nargs='?') +config = parser.parse_args() + +gooddir=config.dir_a +baddir=config.dir_b + +BAD_FILES=find(baddir, "*") +GOOD_FILES=find(gooddir, "*") +NO_PREFIX=sorted([x[len(gooddir)+1:] for x in GOOD_FILES]) + +# "Checking whether build environment is sane ..." +if not config.insane: + announce_test("sanity check") + if not os.access(LINKTEST, os.X_OK): + error("Expect '%s' to be present and executable" % (LINKTEST,)) + exit(1) + + res = testrun(GOOD_FILES) + if not res: + # "build environment is grinning and holding a spatula. Guess not." + linkline="%s %s" % (LINKTEST, " ".join(GOOD_FILES),) + stderr.write("\n%s\n\n" % linkline) + stderr.write("Returned with exitcode != 0\n") + sys.exit(1) + +if config.file is not None: + # File exchange mode + goodfile = gooddir+"/"+config.file + badfile = baddir+"/"+config.file + check_functions_in_file(config.file, goodfile, badfile) +else: + # Function exchange mode + check_files() diff --git a/llvm/utils/abtest/mark_aarch64fns.py b/llvm/utils/abtest/mark_aarch64fns.py deleted file mode 100755 index 65201479284..00000000000 --- a/llvm/utils/abtest/mark_aarch64fns.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -# -# Mark functions in an arm assembly file. This is done by surrounding the -# function with "# -- Begin Name" and "# -- End Name" -# (This script is designed for aarch64 ios assembly syntax) -import sys -import re - -inp = open(sys.argv[1], "r").readlines() - -# First pass -linenum = 0 -INVALID=-100 -last_align = INVALID -last_code = INVALID -last_globl = INVALID -last_globl_name = None -begin = INVALID -in_text_section = False -begins = dict() -for line in inp: - linenum += 1 - if re.search(r'.section\s+__TEXT,__text,regular,pure_instructions', line): - in_text_section = True - continue - elif ".section" in line: - in_text_section = False - continue - - if not in_text_section: - continue - - if ".align" in line: - last_align = linenum - gl = re.search(r'.globl\s+(\w+)', line) - if gl: - last_globl_name = gl.group(1) - last_globl = linenum - m = re.search(r'^(\w+):', line) - if m and begin == INVALID: - labelname = m.group(1) - if last_globl+2 == linenum and last_globl_name == labelname: - begin = last_globl - funcname = labelname - if line == "\n" and begin != INVALID: - end = linenum - triple = (funcname, begin, end) - begins[begin] = triple - begin = INVALID - -# Second pass: Mark -out = open(sys.argv[1], "w") -in_func = None -linenum = 0 -for line in inp: - linenum += 1 - if in_func is not None and linenum == end: - out.write("# -- End %s\n" % in_func) - in_func = None - - triple = begins.get(linenum) - if triple is not None: - in_func, begin, end = triple - out.write("# -- Begin %s\n" % in_func) - out.write(line) diff --git a/llvm/utils/abtest/mark_armfns.py b/llvm/utils/abtest/mark_armfns.py deleted file mode 100755 index 0edf42e8a83..00000000000 --- a/llvm/utils/abtest/mark_armfns.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -# -# Mark functions in an arm assembly file. This is done by surrounding the -# function with "# -- Begin Name" and "# -- End Name" -# (This script is designed for arm ios assembly syntax) -import sys -import re - -inp = open(sys.argv[1], "r").readlines() - -# First pass -linenum = 0 -INVALID=-100 -last_align = INVALID -last_code = INVALID -last_globl = INVALID -begin = INVALID -begins = dict() -for line in inp: - linenum += 1 - if ".align" in line: - last_align = linenum - if ".code" in line: - last_code = linenum - if ".globl" in line: - last_globl = linenum - m = re.search(r'.thumb_func\s+(\w+)', line) - if m: - funcname = m.group(1) - if last_code == last_align+1 and (linenum - last_code) < 4: - begin = last_align - if last_globl+1 == last_align: - begin = last_globl - if line == "\n" and begin != INVALID: - end = linenum - triple = (funcname, begin, end) - begins[begin] = triple - begin = INVALID - -# Second pass: Mark -out = open(sys.argv[1], "w") -in_func = None -linenum = 0 -for line in inp: - linenum += 1 - if in_func is not None and linenum == end: - out.write("# -- End %s\n" % in_func) - in_func = None - - triple = begins.get(linenum) - if triple is not None: - in_func, begin, end = triple - out.write("# -- Begin %s\n" % in_func) - out.write(line) |