[llvm] r264482 - Put my abtest scripts into the util directory

Quentin Colombet via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 25 17:36:14 PDT 2016


Hi Matthias,

Thanks for sharing those, now, you are on the spot for improvement requests :).

> On Mar 25, 2016, at 5:24 PM, Matthias Braun via llvm-commits <llvm-commits at lists.llvm.org> wrote:
> 
> Author: matze
> Date: Fri Mar 25 19:23:59 2016
> New Revision: 264482
> 
> URL: http://llvm.org/viewvc/llvm-project?rev=264482&view=rev
> Log:
> Put my abtest scripts into the util directory
> 
> See http://lists.llvm.org/pipermail/llvm-dev/2016-March/097640.html
> There is also a description/tutorial in the comments of the abtest.py
> file.
> 
> Added:
>    llvm/trunk/utils/abtest/
>    llvm/trunk/utils/abtest/abtest.py   (with props)
>    llvm/trunk/utils/abtest/mark_aarch64fns.py   (with props)
>    llvm/trunk/utils/abtest/mark_armfns.py   (with props)
> 
> Added: llvm/trunk/utils/abtest/abtest.py
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/utils/abtest/abtest.py?rev=264482&view=auto
> ==============================================================================
> --- llvm/trunk/utils/abtest/abtest.py (added)
> +++ llvm/trunk/utils/abtest/abtest.py Fri Mar 25 19:23:59 2016

Could you have the script also accept .o inputs (not the bisecting function part of course)?

> @@ -0,0 +1,223 @@
> +#!/usr/bin/env python
> +#
> +# Given a previous good compile narrow down miscompiles.
> +# Expectes two directories named "before" and "after" each containing a set of
> +# assembly files where the "after" version is assumed to be broken.
> +# Also assumes the presence of a executable or script "link_test" which when
> +# called with a set of assembly files will link them together and test if the
> +# resulting executable is "good".
> +#
> +# Example usage:
> +#    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
> +#       ...
> +#    3. If you want to replace+test with the functions inside a single file
> +#       you first have to mark function begins and ends in the .s files
> +#       this directory comes with some: mark_XXX.py example scripts,
> +#       unfortunately you usually have to adapt them to each environment.
> +#       > for i in before/*.s after/*.s; do mark_xxx.py $i; done
> +#    4. Run the tests on a single file
> +#       > ./abtest.py
> +#       funcname1 [0/XX]: ok
> +#       funcname2 [1/XX]: ok
> +#       funcname3 [2/XX]: skipped: same content
> +#       funcname4 [3/XX]: failed: './link_test' exitcode != 0
> +#       ...
> +import sys
> +import os
> +from os import system, mkdir, walk, makedirs, errno, getenv
> +from os.path import dirname, isdir
> +from shutil import rmtree, copyfile
> +from fnmatch import filter
> +from itertools import chain
> +from subprocess import call
> +from sys import stderr
> +import argparse
> +import filecmp
> +
> +LINKTEST="./link_test"
> +ESCAPE="\033[%sm"
> +BOLD=ESCAPE % "1"
> +RED=ESCAPE % "31"
> +NORMAL=ESCAPE % "0"
> +FAILED=RED+"failed"+NORMAL
> +
> +def mkdirtree(path):
> +    try:
> +        makedirs(path)
> +    except OSError as exc:
> +        if exc.errno == errno.EEXIST and isdir(path):
> +            pass
> +        else:
> +            raise
> +
> +def find(dir, file_filter=None):
> +    files = [walkdir[0]+"/"+file for walkdir in 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 notice(message):
> +    stderr.write("%s\n" % message)
> +
> +def extract_functions(file):
> +    functions = []
> +    in_function = None
> +    for line in open(file):
> +        if line.startswith("# -- Begin  "):
> +            if in_function != None:
> +                warn("Missing end of function %s" % (in_function,))
> +            funcname = line[12:-1]
> +            in_function = funcname
> +            text = line
> +        elif line.startswith("# -- End  "):
> +            function_name = line[10:-1]
> +            if in_function != function_name:
> +                warn("End %s does not match begin %s" % (function_name, in_function))
> +            else:
> +                text += line
> +                functions.append( (in_function, text) )
> +            in_function = None
> +        elif 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):
> +        if line.startswith("# -- Begin  "):
> +            if in_function != None:
> +                warn("Missing end of function %s" % (in_function,))
> +            funcname = line[12:-1]
> +            in_function = funcname
> +            if in_function == function:
> +                out.write(replacement)
> +                skip = True
> +        elif line.startswith("# -- End  "):
> +            function_name = line[10:-1]
> +            if in_function != function_name:
> +                warn("End %s does not match begin %s" % (function_name, in_function))
> +            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 = call(linkline, shell=True)
> +    if res != 0:
> +        announce_result(FAILED, "'%s' exitcode != 0" % LINKTEST)
> +    else:
> +        announce_result("ok", "")
> +
> +def check_files():
> +    """Check files mode"""
> +    for f in NO_PREFIX:
> +        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)
> +
> +        # 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, 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('file', metavar='file', nargs='?')
> +config = parser.parse_args()
> +
> +# Check if environment is sane
> +if not os.access(LINKTEST, os.X_OK):
> +    error("Expect '%s' to be present and executable" % (LINKTEST,))
> +    exit(1)
> +
> +gooddir=config.dir_a
> +baddir=config.dir_b
> +
> +BAD_FILES=find(baddir, "*.s")
> +GOOD_FILES=find(gooddir, "*.s")
> +NO_PREFIX=sorted([x[len(gooddir)+1:] for x in GOOD_FILES])
> +
> +if config.file is not None:
> +    goodfile = gooddir+"/"+config.file
> +    badfile = baddir+"/"+config.file
> +    check_functions_in_file(config.file, goodfile, badfile)
> +else:
> +    check_files()
> 
> Propchange: llvm/trunk/utils/abtest/abtest.py
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> Added: llvm/trunk/utils/abtest/mark_aarch64fns.py

This script may be useful for hand written assembly (though the compiler is not involved for those), but in general, I believe we could teach MC to emit those.
Comment are cheap in assembly files :).

Cheers,
-Quentin

> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/utils/abtest/mark_aarch64fns.py?rev=264482&view=auto
> ==============================================================================
> --- llvm/trunk/utils/abtest/mark_aarch64fns.py (added)
> +++ llvm/trunk/utils/abtest/mark_aarch64fns.py Fri Mar 25 19:23:59 2016
> @@ -0,0 +1,65 @@
> +#!/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)
> 
> Propchange: llvm/trunk/utils/abtest/mark_aarch64fns.py
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> Added: llvm/trunk/utils/abtest/mark_armfns.py
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/utils/abtest/mark_armfns.py?rev=264482&view=auto
> ==============================================================================
> --- llvm/trunk/utils/abtest/mark_armfns.py (added)
> +++ llvm/trunk/utils/abtest/mark_armfns.py Fri Mar 25 19:23:59 2016
> @@ -0,0 +1,54 @@
> +#!/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)
> 
> Propchange: llvm/trunk/utils/abtest/mark_armfns.py
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> 
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits



More information about the llvm-commits mailing list