Esempio n. 1
0
def RunAnalysisPasses(
    bytecode: str,
    passes: typing.List[str],
    opt_path: typing.Optional[pathlib.Path] = None,
) -> typing.Iterable[AnalysisOutput]:
    """Run the given opt analysis passes and parse the output.

  TODO(github.com/ChrisCummins/phd/issues/54): this requires using an LLVM
  built with debug+assert flags, which we do not currently have. You must
  therefore use the `opt_path` argument to pass in the path of an opt binary
  with the debug+assert flags enabled. Care should be taken to match the
  version of opt against the one included in this project as the output format
  may have changed between releases. See //:WORKSPACE for current llvm version.

  Args:
    bytecode: The bytecode to analyse.
    passes: A list of passes to run. See:
      <https://llvm.org/docs/Passes.html#analysis-passes> for a list.

  Returns:
    A generator of AnalysisOutput tuples.
  """
    cmd = ["-analyze", "-stats", "-"] + passes
    p = opt.Exec(cmd, stdin=bytecode, opt=opt_path)
    if p.returncode:
        raise opt.OptException(
            "opt analysis failed",
            returncode=p.returncode,
            stderr=p.stderr,
            command=cmd,
        )
    return ParseAnalysisPassesOutput(p.stdout)
Esempio n. 2
0
def GetAliasSetsByFunction(
    bytecode: str, ) -> typing.Dict[str, typing.List[AliasSet]]:
    """Get the alias sets of a bytecode.

  Args:
    bytecode: An LLVM bytecode.

  Returns:
    A dictionary of alias sets, keyed by the function name.

  Raises:
    OptException: In case the opt pass fails.
  """
    process = opt.Exec(
        ["-basicaa", "-print-alias-sets", "-disable-output"],
        stdin=bytecode,
        universal_newlines=True,
        log=False,
    )

    # Propagate failures from opt as OptExceptions.
    if process.returncode:
        raise opt.OptException(returncode=process.returncode,
                               stderr=process.stderr)

    return ParseAliasSetsOutput(process.stderr)
Esempio n. 3
0
def BytecodeToPollyCanonicalized(source: str) -> str:
    process = opt.Exec(["-polly-canonicalize", "-S", "-", "-o", "-"],
                       stdin=source)
    if process.returncode:
        raise opt.OptException("Error in canonicalization opt execution (%d)" %
                               process.returncode)
    return process.stdout
Esempio n. 4
0
def DotControlFlowGraphsFromBytecode(bytecode: str) -> typing.Iterator[str]:
    """Create a control flow graph from an LLVM bytecode file.

  Args:
    bytecode: The LLVM bytecode to create CFG dots from.

  Returns:
    An iterator of dotfile strings.

  Raises:
    OptException: In case the opt pass fails.
    UnicodeDecodeError: If generated dotfile can't be read.
  """
    with tempfile.TemporaryDirectory(prefix="phd_") as d:
        output_dir = pathlib.Path(d)
        # Change into the output directory, because the -dot-cfg pass writes files
        # to the current working directory.
        with fs.chdir(output_dir):
            # We run with universal_newlines=False because the stdout of opt is the
            # binary bitcode, which we completely ignore (we're only interested in
            # stderr). This means we must encode stdin and decode stderr ourselves.
            process = opt.Exec(
                ["-dot-cfg"],
                stdin=bytecode.encode("utf-8"),
                universal_newlines=False,
                log=False,
            )
            stderr = process.stderr.decode("utf-8")

            # Propagate failures from opt as OptExceptions.
            if process.returncode:
                raise opt.OptException(returncode=process.returncode,
                                       stderr=stderr)

            for file in output_dir.iterdir():
                # Opt pass prints the name of the dot files it generates, e.g.:
                #
                #     $ opt -dot-cfg < foo.ll
                #     WARNING: You're attempting to print out a bitcode file.
                #     This is inadvisable as it may cause display problems. If
                #     you REALLY want to taste LLVM bitcode first-hand, you
                #     can force output with the `-f' option.
                #
                #     Writing 'cfg.DoSomething.dot'...
                #     Writing 'cfg.main.dot'...
                if f"Writing '{file.name}'..." not in stderr:
                    raise OSError(
                        f"Could not find reference to file '{file.name}' in "
                        f"opt stderr:\n{process.stderr}")
                with open(file) as f:
                    yield f.read()
Esempio n. 5
0
def DotCallGraphFromBytecode(bytecode: str) -> str:
    """Create a call graph from an LLVM bytecode file.

  Args:
    bytecode: The LLVM bytecode to create call graphfrom.

  Returns:
    A dotfile string.

  Raises:
    OptException: In case the opt pass fails.
    UnicodeDecodeError: If generated dotfile can't be read.
  """
    with tempfile.TemporaryDirectory(prefix="phd_") as d:
        output_dir = pathlib.Path(d)
        # Change into the output directory, because the -dot-callgraph pass writes
        # to the current working directory.
        with fs.chdir(output_dir):
            # We run with universal_newlines=False because the stdout of opt is the
            # binary bitcode, which we completely ignore (we're only interested in
            # stderr). This means we must encode stdin and decode stderr ourselves.
            process = opt.Exec(
                ["-dot-callgraph"],
                stdin=bytecode.encode("utf-8"),
                universal_newlines=False,
                log=False,
            )
            stderr = process.stderr.decode("utf-8")

            # Propagate failures from opt as OptExceptions.
            if process.returncode:
                raise opt.OptException(returncode=process.returncode,
                                       stderr=stderr)

            callgraph = output_dir / "callgraph.dot"
            if not callgraph.is_file():
                raise OSError(f"Callgraph dotfile not produced")
            return fs.Read(callgraph)
Esempio n. 6
0
def DotGraphsFromBytecode(
    bytecode: str,
    opt_args: typing.List[str],
    opt_path: str = None,
    output_pred: typing.Callable[[str], bool] = None,
) -> typing.Tuple[typing.List[str], typing.List[str]]:
    """Obtain dot graphs from an LLVM bytecode file using an opt pass.

  Args:
    bytecode: The LLVM bytecode to create the graphs from.
    opt_args: A list of arguments to the opt tool that generate the graphs.
    opt_path: The path to a custom opt binary. Overrides the default version.
    output_pred: A predicate that receives an output file name, and returns
                 True if it should be collected in the first part of the result tuple,
                 or False if it should be collected in the second tuple element.
                 If None, all outputs are collected to the first element.

  Returns:
    A 2-tuple of lists of graphs as dot strings.

  Raises:
    OptException: In case the opt pass fails.
    UnicodeDecodeError: If generated dotfile can't be read.
  """
    graph_dots_true = []
    graph_dots_false = []

    with tempfile.TemporaryDirectory(prefix="phd_") as d:
        output_dir = pathlib.Path(d)
        # Change into the output directory, because the -dot-callgraph pass writes
        # to the current working directory.
        with fs.chdir(output_dir):
            # We run with universal_newlines=False because the stdout of opt is the
            # binary bitcode, which we completely ignore (we're only interested in
            # stderr). This means we must encode stdin and decode stderr ourselves.
            process = opt.Exec(
                opt_args,
                stdin=bytecode.encode("utf-8"),
                universal_newlines=False,
                log=False,
                opt=opt_path,
            )
            stderr = process.stderr.decode("utf-8")

            # Propagate failures from opt as OptExceptions.
            if process.returncode:
                raise opt.OptException(returncode=process.returncode,
                                       stderr=stderr)

            for file in output_dir.iterdir():
                # Opt pass prints the name of the dot files it generates, e.g.:
                #
                #     $ opt -dot-cfg < foo.ll
                #     WARNING: You're attempting to print out a bitcode file.
                #     This is inadvisable as it may cause display problems. If
                #     you REALLY want to taste LLVM bitcode first-hand, you
                #     can force output with the `-f' option.
                #
                #     Writing 'cfg.DoSomething.dot'...
                #     Writing 'cfg.main.dot'...
                if f"Writing '{file.name}'..." not in stderr:
                    raise OSError(
                        f"Could not find reference to file '{file.name}' in "
                        f"opt stderr:\n{process.stderr}")
                if not output_pred or output_pred(file.name):
                    graph_dots_true.append(fs.Read(file))
                else:
                    graph_dots_false.append(fs.Read(file))

            return graph_dots_true, graph_dots_false