Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:23:54

0001 #!/bin/python3
0002 
0003 
0004 # This file is part of the ACTS project.
0005 #
0006 # Copyright (C) 2016 CERN for the benefit of the ACTS project
0007 #
0008 # This Source Code Form is subject to the terms of the Mozilla Public
0009 # License, v. 2.0. If a copy of the MPL was not distributed with this
0010 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
0011 
0012 
0013 import argparse
0014 import re
0015 import collections
0016 import pathlib
0017 
0018 
0019 class InstructionCounter:
0020     """
0021     Class for counting the use of certain instructions in translation units.
0022 
0023     The instructions and translation units are counted, not linked. So this
0024     class cannot reconstruct that a particular instruction was emitted in a
0025     particular translation unit. But I don't think that's necessary at this
0026     time."""
0027 
0028     def __init__(self):
0029         """
0030         Initialize the counter.
0031 
0032         This creates some empty integer dicts with default value zero."""
0033         self.instructions = collections.defaultdict(int)
0034         self.translations = collections.defaultdict(int)
0035 
0036     def add(self, instr, trans):
0037         """Register the occurrence of an instruction in a translation unit."""
0038         self.instructions[instr] += 1
0039         self.translations[trans] += 1
0040 
0041 
0042 def oxford_join(lst):
0043     """
0044     Format a list of strings in a human-readable way using an Oxford comma.
0045 
0046     This function takes ["a", "b", "c"] to the string "a, b, and c"."""
0047     if not lst:
0048         return ""
0049     elif len(lst) == 1:
0050         return str(lst[0])
0051     elif len(lst) == 2:
0052         return f"{str(lst[0])} and {str(lst[1])}"
0053     return f"{', '.join(lst[:-1])}, and {lst[-1]}"
0054 
0055 
0056 def run(files, source, build):
0057     """
0058     Perform a search for FP64 instructions in a list of files.
0059 
0060     This function takes a list of file paths as well as the root path of the
0061     source code and the build path. These are necessary because the paths
0062     reported in the PTX emitted by NVCC are relative to the build directory. In
0063     order to get the paths relative to the GitHub root directory (which GitHub
0064     Action Commands require), we need to do some path magic."""
0065     # Create a dictionary of counters. The keys in this dictionary are line
0066     # information tuples (source file name and line) and the values are counter
0067     # objects which count how many times that line generates each instruction,
0068     # and how many times it generates instructions in a given translation unit.
0069     counter = collections.defaultdict(InstructionCounter)
0070 
0071     # Resolve the source and build paths if they are relevant. Since these are
0072     # constant, we can move this operation out of the loop.
0073     source_path = source.resolve()
0074     build_path = build.resolve()
0075 
0076     # Iterate over the list of files that we are given by the user. We do this
0077     # multi-file analysis so we can analyse the mapping of shared source code
0078     # to multiple translation units.
0079     for n in files:
0080         # Read the PTX file and split it into multiple lines. This is NOT a
0081         # proper parsing of PTX and could break, but works for now.
0082         with open(n, "r") as f:
0083             lines = f.read().split("\n")
0084 
0085         # At the beginning of the file, the line data is unknown.
0086         linedata = None
0087 
0088         # Iterate over the source lines in the PTX.
0089         for l in lines:
0090             if m := re.match(r"^//(?P<file>[/\w\-. ]*):(?P<line>\d+)", l):
0091                 # If the line of the form "//[filename]:[line] [code]", we
0092                 # parse the file name and line number, then update the line
0093                 # data. Any subsequent instructions will be mapped onto this
0094                 # source line.
0095                 linedata = (m.group("file"), int(m.group("line")))
0096             elif m := re.match(
0097                 r"^\s*(?P<instruction>(?:[a-z][a-z0-9]*)(?:\.[a-z][a-z0-9]+)*)", l
0098             ):
0099                 # If the line is of the form "    [instruction] [operands]", we
0100                 # parse the instruction. The operands are irrelevant.
0101                 if "f64" in m.group("instruction"):
0102                     # PTX has the pleasant property that all instructions
0103                     # explicitly specify their operand types (Intel x86 syntax
0104                     # could learn from this), so if "f64" is contained in the
0105                     # instruction it will be a double-precision operator. We
0106                     # now proceed to compute the real path of the line that
0107                     # produced this instruction.
0108                     real_path = (build_path / linedata[0]).resolve()
0109                     if linedata is not None:
0110                         # If the line data is not none, we have a line to link
0111                         # this instruction to. We compute the relative path of
0112                         # the source file to the root of the source directory,
0113                         # and add the result to the counting dictionary.
0114                         try:
0115                             counter[
0116                                 (real_path.relative_to(source_path), linedata[1])
0117                             ].add(m.group("instruction"), n)
0118                         except ValueError:
0119                             pass
0120                     else:
0121                         # If we do not have line data, we register an FP64
0122                         # instruction of unknown origin.
0123                         counter[None].add(m.group("instruction"), n)
0124 
0125     # After we complete our analysis, we print some output to stdout which will
0126     # be parsed by GitHub Actions. For the syntax, please refer to
0127     # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
0128     for dt in counter:
0129         instrs = oxford_join(
0130             [f"{counter[dt].instructions[i]} × `{i}`" for i in counter[dt].instructions]
0131         )
0132         units = oxford_join(
0133             [f"`{pathlib.Path(f).name}`" for f in counter[dt].translations]
0134         )
0135         details = (
0136             f"Instruction(s) generated are {instrs} in translation unit(s) {units}."
0137         )
0138 
0139         # Handle the cases where the source line information is unknown and
0140         # known, respectively.
0141         if dt is None:
0142             print(
0143                 f"::warning title=FP64 instructions emitted in unknown locations::{details}"
0144             )
0145         else:
0146             print(
0147                 f"::warning file={dt[0]},line={dt[1]},title=FP64 instructions emitted::{details}"
0148             )
0149 
0150 
0151 if __name__ == "__main__":
0152     # Construct an argument parser, asking the user for a set of files, as well
0153     # as their source and build directories, in a fashion similar to what CMake
0154     # does.
0155     parser = argparse.ArgumentParser(
0156         description="Find unwanted 64-bit float operations in annotated PTX."
0157     )
0158 
0159     parser.add_argument("files", type=str, help="PTX file to use", nargs="+")
0160     parser.add_argument(
0161         "--source", "-S", type=pathlib.Path, help="source directory", required=True
0162     )
0163     parser.add_argument(
0164         "--build", "-B", type=pathlib.Path, help="build directory", required=True
0165     )
0166 
0167     args = parser.parse_args()
0168 
0169     # Finally, run the analysis!
0170     run(args.files, args.source, args.build)