Add 'projects/rocprofiler/' from commit '16ae2e90c6157e98e846d2bccbaaf533ca5e662a'
git-subtree-dir: projects/rocprofiler git-subtree-mainline:2a52e3974dgit-subtree-split:16ae2e90c6
This commit is contained in:
commit
2e5bcec303
386 files changed
+216890
No files matched your search
Whitespace-only changes.
+353
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
################################################################################
|
||||
# Copyright (c) 2018-2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
################################################################################
|
||||
|
||||
import os, sys, re
|
||||
import CppHeaderParser
|
||||
import argparse
|
||||
import string
|
||||
|
||||
LICENSE = (
|
||||
"/*\n"
|
||||
+ "Copyright (c) 2018 Advanced Micro Devices, Inc. All rights reserved.\n"
|
||||
+ "\n"
|
||||
+ "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
|
||||
+ 'of this software and associated documentation files (the "Software"), to deal\n'
|
||||
+ "in the Software without restriction, including without limitation the rights\n"
|
||||
+ "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
|
||||
+ "copies of the Software, and to permit persons to whom the Software is\n"
|
||||
+ "furnished to do so, subject to the following conditions:\n"
|
||||
+ "\n"
|
||||
+ "The above copyright notice and this permission notice shall be included in\n"
|
||||
+ "all copies or substantial portions of the Software.\n"
|
||||
+ "\n"
|
||||
+ 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n'
|
||||
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
|
||||
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
|
||||
+ "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
|
||||
+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
|
||||
+ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
|
||||
+ "THE SOFTWARE.\n"
|
||||
+ "*/\n"
|
||||
)
|
||||
|
||||
|
||||
header_basic = (
|
||||
"namespace detail {\n"
|
||||
+ "template <typename T>\n"
|
||||
+ " inline static std::ostream& operator<<(std::ostream& out, const T& v) {\n"
|
||||
+ " using std::operator<<;\n"
|
||||
+ " static bool recursion = false;\n"
|
||||
+ " if (recursion == false) { recursion = true; out << v; recursion = false; }\n"
|
||||
+ " return out;\n }\n"
|
||||
+ "\n"
|
||||
+ " inline static std::ostream &operator<<(std::ostream &out, const unsigned char &v) {\n"
|
||||
+ " out << (unsigned int)v;\n"
|
||||
+ " return out;\n }\n"
|
||||
+ "\n"
|
||||
+ " inline static std::ostream &operator<<(std::ostream &out, const char &v) {\n"
|
||||
+ " out << (unsigned char)v;\n"
|
||||
+ " return out;\n }\n"
|
||||
)
|
||||
|
||||
structs_analyzed = {}
|
||||
global_ops = ""
|
||||
global_str = ""
|
||||
output_filename_h = None
|
||||
apiname = ""
|
||||
|
||||
|
||||
# process_struct traverses recursively all structs to extract all fields
|
||||
def process_struct(file_handle, cppHeader_struct, cppHeader, parent_hier_name, apiname):
|
||||
# file_handle: handle for output file {api_name}_ostream_ops.h to be generated
|
||||
# cppHeader_struct: cppHeader struct being processed
|
||||
# cppHeader: cppHeader object created by CppHeaderParser.CppHeader(...)
|
||||
# parent_hier_name: parent hierarchical name used for nested structs/enums
|
||||
# apiname: for example hip.
|
||||
global global_str
|
||||
|
||||
if (
|
||||
cppHeader_struct == "max_align_t"
|
||||
): # function pointers not working in cppheaderparser
|
||||
return
|
||||
if cppHeader_struct not in cppHeader.classes:
|
||||
return
|
||||
if cppHeader_struct in structs_analyzed:
|
||||
return
|
||||
structs_analyzed[cppHeader_struct] = 1
|
||||
for l in reversed(
|
||||
range(len(cppHeader.classes[cppHeader_struct]["properties"]["public"]))
|
||||
):
|
||||
key = "name"
|
||||
name = ""
|
||||
if key in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||
if parent_hier_name != "":
|
||||
name = (
|
||||
parent_hier_name
|
||||
+ "."
|
||||
+ cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key]
|
||||
)
|
||||
else:
|
||||
name = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key]
|
||||
if name == "":
|
||||
continue
|
||||
key2 = "type"
|
||||
mtype = ""
|
||||
if key2 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||
mtype = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key2]
|
||||
if mtype == "":
|
||||
continue
|
||||
key3 = "array_size"
|
||||
array_size = ""
|
||||
if key3 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||
array_size = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][
|
||||
key3
|
||||
]
|
||||
key4 = "property_of_class"
|
||||
prop = ""
|
||||
if key4 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||
prop = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key4]
|
||||
|
||||
str = ""
|
||||
if "union" not in mtype:
|
||||
indent = ""
|
||||
str += (
|
||||
' if (std::string("'
|
||||
+ cppHeader_struct
|
||||
+ "::"
|
||||
+ name
|
||||
+ '").find('
|
||||
+ apiname.upper()
|
||||
+ "_structs_regex"
|
||||
+ ") != std::string::npos) {\n"
|
||||
)
|
||||
indent = " "
|
||||
str += (
|
||||
indent
|
||||
+ " roctracer::"
|
||||
+ apiname.lower()
|
||||
+ '_support::detail::operator<<(out, "'
|
||||
+ name
|
||||
+ '=");\n'
|
||||
)
|
||||
str += (
|
||||
indent
|
||||
+ " roctracer::"
|
||||
+ apiname.lower()
|
||||
+ "_support::detail::operator<<(out, v."
|
||||
+ name
|
||||
+ ");\n"
|
||||
)
|
||||
str += (
|
||||
indent
|
||||
+ " roctracer::"
|
||||
+ apiname.lower()
|
||||
+ '_support::detail::operator<<(out, ", ");\n'
|
||||
)
|
||||
str += " }\n"
|
||||
if "void" not in mtype:
|
||||
global_str += str
|
||||
else:
|
||||
if prop != "":
|
||||
next_cppHeader_struct = prop + "::"
|
||||
process_struct(
|
||||
file_handle, next_cppHeader_struct, cppHeader, name, apiname
|
||||
)
|
||||
next_cppHeader_struct = prop + "::" + mtype + " "
|
||||
process_struct(
|
||||
file_handle, next_cppHeader_struct, cppHeader, name, apiname
|
||||
)
|
||||
next_cppHeader_struct = cppHeader_struct + "::"
|
||||
process_struct(file_handle, next_cppHeader_struct, cppHeader, name, apiname)
|
||||
|
||||
|
||||
# Parses API header file and generates ostream ops files ostream_ops.h
|
||||
def gen_cppheader(infilepath, outfilepath, rank):
|
||||
# infilepath: API Header file to be parsed
|
||||
# outfilepath: Output file where ostream operators are written
|
||||
global global_ops
|
||||
global output_filename_h
|
||||
global apiname
|
||||
global global_str
|
||||
try:
|
||||
cppHeader = CppHeaderParser.CppHeader(infilepath)
|
||||
except CppHeaderParser.CppParseError as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
if rank == 0 or rank == 2:
|
||||
mpath = os.path.dirname(outfilepath)
|
||||
if mpath == "":
|
||||
mpath = os.getcwd()
|
||||
apiname = outfilepath.replace(mpath + "/", "")
|
||||
output_filename_h = open(outfilepath, "w+")
|
||||
apiname = apiname.replace("_ostream_ops.h", "")
|
||||
apiname = apiname.upper()
|
||||
output_filename_h.write("// automatically generated\n")
|
||||
output_filename_h.write(LICENSE + "\n")
|
||||
header_s = (
|
||||
"#ifndef INC_"
|
||||
+ apiname
|
||||
+ "_OSTREAM_OPS_H_\n"
|
||||
+ "#define INC_"
|
||||
+ apiname
|
||||
+ "_OSTREAM_OPS_H_\n"
|
||||
+ "\n"
|
||||
)
|
||||
if apiname.upper() == 'HIP':
|
||||
header_s = (
|
||||
header_s
|
||||
+ "#include <hip/hip_runtime.h>\n"
|
||||
+ "#include <hip/hip_deprecated.h>\n"
|
||||
)
|
||||
header_s = (
|
||||
header_s
|
||||
+ '#include "src/core/session/tracer/src/roctracer.h"\n'
|
||||
+ "\n"
|
||||
+ "#ifdef __cplusplus\n"
|
||||
+ "#include <iostream>\n"
|
||||
+ "#include <string>\n"
|
||||
)
|
||||
|
||||
output_filename_h.write(header_s)
|
||||
output_filename_h.write("\n")
|
||||
output_filename_h.write("namespace roctracer {\n")
|
||||
output_filename_h.write("namespace " + apiname.lower() + "_support {\n")
|
||||
output_filename_h.write("static int " + apiname.upper() + "_depth_max = 1;\n")
|
||||
output_filename_h.write("static int " + apiname.upper() + "_depth_max_cnt = 0;\n")
|
||||
output_filename_h.write(
|
||||
"static std::string " + apiname.upper() + '_structs_regex = "";\n'
|
||||
)
|
||||
output_filename_h.write("// begin ostream ops for " + apiname + " \n")
|
||||
output_filename_h.write("// basic ostream ops\n")
|
||||
output_filename_h.write(header_basic)
|
||||
output_filename_h.write("// End of basic ostream ops\n\n")
|
||||
|
||||
for c in cppHeader.classes.copy():
|
||||
# Types defined inside of unions are incorrectly prepended with "union " after parsing by CppHeaderParser
|
||||
# Remove "union " from the beginning of the full class name to correct the eventual output
|
||||
if "union " in c[0:6] and "::union" not in c[-8:]:
|
||||
new_name = c[6:]
|
||||
cppHeader.classes[new_name] = cppHeader.classes[c]
|
||||
del cppHeader.classes[c]
|
||||
|
||||
for c in cppHeader.classes:
|
||||
if c[-2] == ":" and c[-1] == ":":
|
||||
continue # ostream operator cannot be overloaded for anonymous struct therefore it is skipped
|
||||
if "::union" in c:
|
||||
continue
|
||||
if c in structs_analyzed:
|
||||
continue
|
||||
if (
|
||||
c == "max_align_t" or c == "__fsid_t"
|
||||
): # Skipping as it is defined in multiple domains
|
||||
continue
|
||||
if len(cppHeader.classes[c]["properties"]["public"]) != 0:
|
||||
output_filename_h.write(
|
||||
"inline static std::ostream& operator<<(std::ostream& out, const "
|
||||
+ c
|
||||
+ "& v)\n"
|
||||
)
|
||||
output_filename_h.write("{\n")
|
||||
output_filename_h.write(" std::operator<<(out, '{');\n")
|
||||
output_filename_h.write(" " + apiname.upper() + "_depth_max_cnt++;\n")
|
||||
output_filename_h.write(
|
||||
" if ("
|
||||
+ apiname.upper()
|
||||
+ "_depth_max == -1 || "
|
||||
+ apiname.upper()
|
||||
+ "_depth_max_cnt <= "
|
||||
+ apiname.upper()
|
||||
+ "_depth_max"
|
||||
+ ") {\n"
|
||||
)
|
||||
process_struct(output_filename_h, c, cppHeader, "", apiname)
|
||||
global_str = "\n".join(global_str.split("\n")[0:-3])
|
||||
if global_str != "":
|
||||
global_str += "\n }\n"
|
||||
output_filename_h.write(global_str)
|
||||
output_filename_h.write(" };\n")
|
||||
output_filename_h.write(" " + apiname.upper() + "_depth_max_cnt--;\n")
|
||||
output_filename_h.write(" std::operator<<(out, '}');\n")
|
||||
output_filename_h.write(" return out;\n")
|
||||
output_filename_h.write("}\n")
|
||||
global_str = ""
|
||||
global_ops += (
|
||||
"inline static std::ostream& operator<<(std::ostream& out, const "
|
||||
+ c
|
||||
+ "& v)\n"
|
||||
+ "{\n"
|
||||
+ " roctracer::"
|
||||
+ apiname.lower()
|
||||
+ "_support::detail::operator<<(out, v);\n"
|
||||
+ " return out;\n"
|
||||
+ "}\n\n"
|
||||
)
|
||||
|
||||
if rank == 1 or rank == 2:
|
||||
footer = "// end ostream ops for " + apiname + " \n"
|
||||
footer += "};};};\n\n"
|
||||
output_filename_h.write(footer)
|
||||
output_filename_h.write(global_ops)
|
||||
footer = (
|
||||
"#endif //__cplusplus\n"
|
||||
+ "#endif // INC_"
|
||||
+ apiname
|
||||
+ "_OSTREAM_OPS_H_\n"
|
||||
+ " \n"
|
||||
)
|
||||
output_filename_h.write(footer)
|
||||
output_filename_h.write("#include <hip/amd_detail/hip_prof_str.h>")
|
||||
output_filename_h.close()
|
||||
print("File " + outfilepath + " generated")
|
||||
|
||||
return
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="genOstreamOps.py: generates ostream operators for all typedefs in provided input file."
|
||||
)
|
||||
requiredNamed = parser.add_argument_group("Required arguments")
|
||||
requiredNamed.add_argument(
|
||||
"-in",
|
||||
metavar="fileList",
|
||||
help="Comma separated list of header files to be parsed",
|
||||
required=True,
|
||||
)
|
||||
requiredNamed.add_argument(
|
||||
"-out", metavar="file", help="Output file with ostream operators", required=True
|
||||
)
|
||||
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
if __name__ == "__main__":
|
||||
flist = args["in"].split(",")
|
||||
if len(flist) == 1:
|
||||
gen_cppheader(flist[0], args["out"], 2)
|
||||
else:
|
||||
for i in range(len(flist)):
|
||||
if i == 0:
|
||||
gen_cppheader(flist[i], args["out"], 0)
|
||||
elif i == len(flist) - 1:
|
||||
gen_cppheader(flist[i], args["out"], 1)
|
||||
else:
|
||||
gen_cppheader(flist[i], args["out"], -1)
|
||||
Executable
+691
@@ -0,0 +1,691 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
################################################################################
|
||||
# Copyright (c) 2018-2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
################################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
import os, sys, re
|
||||
|
||||
H_OUT = "hsa_prof_str.h"
|
||||
CPP_OUT = "hsa_prof_str.inline.h"
|
||||
API_TABLES_H = "hsa_api_trace.h"
|
||||
API_HEADERS_H = (
|
||||
("CoreApi", "hsa.h"),
|
||||
("AmdExt", "hsa_ext_amd.h"),
|
||||
("ImageExt", "hsa_ext_image.h"),
|
||||
("AmdExt", API_TABLES_H),
|
||||
)
|
||||
|
||||
LICENSE = (
|
||||
"/* Copyright (c) 2018-2022 Advanced Micro Devices, Inc.\n"
|
||||
+ "\n"
|
||||
+ " Permission is hereby granted, free of charge, to any person obtaining a copy\n"
|
||||
+ ' of this software and associated documentation files (the "Software"), to deal\n'
|
||||
+ " in the Software without restriction, including without limitation the rights\n"
|
||||
+ " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
|
||||
+ " copies of the Software, and to permit persons to whom the Software is\n"
|
||||
+ " furnished to do so, subject to the following conditions:\n"
|
||||
+ "\n"
|
||||
+ " The above copyright notice and this permission notice shall be included in\n"
|
||||
+ " all copies or substantial portions of the Software.\n"
|
||||
+ "\n"
|
||||
+ ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n'
|
||||
+ " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
|
||||
+ " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
|
||||
+ " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
|
||||
+ " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
|
||||
+ " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
|
||||
+ " THE SOFTWARE. */\n"
|
||||
)
|
||||
|
||||
|
||||
#############################################################
|
||||
# Error handler
|
||||
def fatal(module, msg):
|
||||
print(module + ' Error: "' + msg + '"', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Get next text block
|
||||
def NextBlock(pos, record):
|
||||
if len(record) == 0:
|
||||
return pos
|
||||
|
||||
space_pattern = re.compile(r"(\s+)")
|
||||
word_pattern = re.compile(r"([\w\*]+)")
|
||||
if record[pos] != "(":
|
||||
m = space_pattern.match(record, pos)
|
||||
if not m:
|
||||
m = word_pattern.match(record, pos)
|
||||
if m:
|
||||
return pos + len(m.group(1))
|
||||
else:
|
||||
fatal("NextBlock", "bad record '" + record + "' pos(" + str(pos) + ")")
|
||||
else:
|
||||
count = 0
|
||||
for index in range(pos, len(record)):
|
||||
if record[index] == "(":
|
||||
count = count + 1
|
||||
elif record[index] == ")":
|
||||
count = count - 1
|
||||
if count == 0:
|
||||
index = index + 1
|
||||
break
|
||||
if count != 0:
|
||||
fatal("NextBlock", "count is not zero (" + str(count) + ")")
|
||||
if record[index - 1] != ")":
|
||||
fatal("NextBlock", "last char is not ')' '" + record[index - 1] + "'")
|
||||
return index
|
||||
|
||||
|
||||
#############################################################
|
||||
# API table parser class
|
||||
class API_TableParser:
|
||||
def fatal(self, msg):
|
||||
fatal("API_TableParser", msg)
|
||||
|
||||
def __init__(self, header, name):
|
||||
self.name = name
|
||||
|
||||
if not os.path.isfile(header):
|
||||
self.fatal("file '" + header + "' not found")
|
||||
|
||||
self.inp = open(header, "r")
|
||||
|
||||
self.beg_pattern = re.compile("^\s*struct\s+" + name + "Table\s*{\s*$")
|
||||
self.end_pattern = re.compile("^\s*};\s*$")
|
||||
self.array = []
|
||||
self.parse()
|
||||
|
||||
# normalizing a line
|
||||
def norm_line(self, line):
|
||||
return re.sub(r"^\s+", r" ", line[:-1])
|
||||
|
||||
# check for start record
|
||||
def is_start(self, record):
|
||||
return self.beg_pattern.match(record)
|
||||
|
||||
# check for end record
|
||||
def is_end(self, record):
|
||||
return self.end_pattern.match(record)
|
||||
|
||||
# check for declaration entry record
|
||||
def is_entry(self, record):
|
||||
return re.match(r"^\s*decltype\(([^\)]*)\)", record)
|
||||
|
||||
# parse method
|
||||
def parse(self):
|
||||
active = 0
|
||||
for line in self.inp.readlines():
|
||||
record = self.norm_line(line)
|
||||
if self.is_start(record):
|
||||
active = 1
|
||||
if active != 0:
|
||||
if self.is_end(record):
|
||||
return
|
||||
m = self.is_entry(record)
|
||||
if m:
|
||||
self.array.append(m.group(1))
|
||||
|
||||
|
||||
#############################################################
|
||||
# API declaration parser class
|
||||
class API_DeclParser:
|
||||
def fatal(self, msg):
|
||||
fatal("API_DeclParser", msg)
|
||||
|
||||
def __init__(self, header, array, data):
|
||||
if not os.path.isfile(header):
|
||||
self.fatal("file '" + header + "' not found")
|
||||
|
||||
self.inp = open(header, "r")
|
||||
|
||||
self.end_pattern = re.compile("\);\s*$")
|
||||
self.data = data
|
||||
for call in array:
|
||||
if call in data:
|
||||
self.fatal(call + " is already found")
|
||||
self.parse(call)
|
||||
|
||||
# api record filter
|
||||
def api_filter(self, record):
|
||||
record = re.sub(r"\sHSA_API\s", r" ", record)
|
||||
record = re.sub(r"\sHSA_DEPRECATED\s", r" ", record)
|
||||
return record
|
||||
|
||||
# check for start record
|
||||
def is_start(self, call, record):
|
||||
return re.search("\s" + call + "\s*\(", record)
|
||||
|
||||
# check for API method record
|
||||
def is_api(self, call, record):
|
||||
record = self.api_filter(record)
|
||||
return re.match("\s+\S+\s+" + call + "\s*\(", record)
|
||||
|
||||
# check for end record
|
||||
def is_end(self, record):
|
||||
return self.end_pattern.search(record)
|
||||
|
||||
# parse method args
|
||||
def get_args(self, record):
|
||||
struct = {"ret": "", "args": "", "astr": {}, "alst": [], "tlst": []}
|
||||
record = re.sub(r"^\s+", r"", record)
|
||||
record = re.sub(r"\s*(\*+)\s*", r"\1 ", record)
|
||||
rind = NextBlock(0, record)
|
||||
struct["ret"] = record[0:rind]
|
||||
pos = record.find("(")
|
||||
end = NextBlock(pos, record)
|
||||
args = record[pos:end]
|
||||
args = re.sub(r"^\(\s*", r"", args)
|
||||
args = re.sub(r"\s*\)$", r"", args)
|
||||
args = re.sub(r"\s*,\s*", r",", args)
|
||||
struct["args"] = re.sub(r",", r", ", args)
|
||||
if len(args) == 0:
|
||||
return struct
|
||||
|
||||
pos = 0
|
||||
args = args + ","
|
||||
while pos < len(args):
|
||||
ind1 = NextBlock(pos, args) # type
|
||||
ind2 = NextBlock(ind1, args) # space
|
||||
if args[ind2] != "(":
|
||||
while ind2 < len(args):
|
||||
end = NextBlock(ind2, args)
|
||||
if args[end] == ",":
|
||||
break
|
||||
else:
|
||||
ind2 = end
|
||||
name = args[ind2:end]
|
||||
else:
|
||||
ind3 = NextBlock(ind2, args) # field
|
||||
m = re.match(r"\(\s*\*\s*(\S+)\s*\)", args[ind2:ind3])
|
||||
if not m:
|
||||
self.fatal("bad block3 '" + args + "' : '" + args[ind2:ind3] + "'")
|
||||
name = m.group(1)
|
||||
end = NextBlock(ind3, args) # the rest
|
||||
item = args[pos:end]
|
||||
struct["astr"][name] = item
|
||||
struct["alst"].append(name)
|
||||
struct["tlst"].append(item)
|
||||
if args[end] != ",":
|
||||
self.fatal("no comma '" + args + "'")
|
||||
pos = end + 1
|
||||
|
||||
return struct
|
||||
|
||||
# parse given api
|
||||
def parse(self, call):
|
||||
record = ""
|
||||
active = 0
|
||||
found = 0
|
||||
api_name = ""
|
||||
prev_line = ""
|
||||
|
||||
self.inp.seek(0)
|
||||
for line in self.inp.readlines():
|
||||
record += " " + line[:-1]
|
||||
record = re.sub(r"^\s*", r" ", record)
|
||||
|
||||
if active == 0:
|
||||
if self.is_start(call, record):
|
||||
active = 1
|
||||
m = self.is_api(call, record)
|
||||
if not m:
|
||||
record = " " + prev_line + " " + record
|
||||
m = self.is_api(call, record)
|
||||
if not m:
|
||||
self.fatal("bad api '" + line + "'")
|
||||
|
||||
if active == 1:
|
||||
if self.is_end(record):
|
||||
self.data[call] = self.get_args(record)
|
||||
active = 0
|
||||
found = 0
|
||||
|
||||
if active == 0:
|
||||
record = ""
|
||||
prev_line = line
|
||||
|
||||
|
||||
#############################################################
|
||||
# API description parser class
|
||||
class API_DescrParser:
|
||||
def fatal(self, msg):
|
||||
fatal("API_DescrParser", msg)
|
||||
|
||||
def __init__(self, out_h_file, hsa_dir, api_table_h, api_headers, license):
|
||||
out_macro = re.sub(r"[\/\.]", r"_", out_h_file.upper()) + "_"
|
||||
|
||||
self.h_content = ""
|
||||
self.cpp_content = ""
|
||||
self.api_names = []
|
||||
self.api_calls = {}
|
||||
self.api_rettypes = set()
|
||||
self.api_id = {}
|
||||
|
||||
api_data = {}
|
||||
api_list = []
|
||||
ns_calls = []
|
||||
|
||||
for i in range(0, len(api_headers)):
|
||||
(name, header) = api_headers[i]
|
||||
|
||||
if i < len(api_headers) - 1:
|
||||
api = API_TableParser(hsa_dir + api_table_h, name)
|
||||
api_list = api.array
|
||||
self.api_names.append(name)
|
||||
self.api_calls[name] = api_list
|
||||
else:
|
||||
api_list = ns_calls
|
||||
ns_calls = []
|
||||
|
||||
for call in api_list:
|
||||
if call in api_data:
|
||||
self.fatal("call '" + call + "' is already found")
|
||||
|
||||
API_DeclParser(hsa_dir + header, api_list, api_data)
|
||||
|
||||
for call in api_list:
|
||||
if not call in api_data:
|
||||
# Not-supported functions
|
||||
ns_calls.append(call)
|
||||
else:
|
||||
# API ID map
|
||||
self.api_id[call] = "HSA_API_ID_" + call
|
||||
# Return types
|
||||
self.api_rettypes.add(api_data[call]["ret"])
|
||||
|
||||
self.api_rettypes.discard("void")
|
||||
self.api_data = api_data
|
||||
self.ns_calls = ns_calls
|
||||
|
||||
self.h_content += (
|
||||
"/* Generated by " + os.path.basename(__file__) + " */\n" + license + "\n\n"
|
||||
)
|
||||
|
||||
self.h_content += "/* HSA API tracing primitives\n"
|
||||
for name, header in api_headers:
|
||||
self.h_content += (
|
||||
" '"
|
||||
+ name
|
||||
+ "', header '"
|
||||
+ header
|
||||
+ "', "
|
||||
+ str(len(self.api_calls[name]))
|
||||
+ " funcs\n"
|
||||
)
|
||||
for call in self.ns_calls:
|
||||
self.h_content += " " + call + " was not parsed\n"
|
||||
self.h_content += " */\n"
|
||||
self.h_content += "\n"
|
||||
self.h_content += "#ifndef " + out_macro + "\n"
|
||||
self.h_content += "#define " + out_macro + "\n"
|
||||
|
||||
self.h_content += self.add_section("API ID enumeration", " ", self.gen_id_enum)
|
||||
|
||||
self.h_content += "/* Declarations of APIs intended for use only by tools. */\n"
|
||||
self.h_content += "typedef void (*hsa_amd_queue_intercept_packet_writer)(const void*, uint64_t);\n"
|
||||
self.h_content += "typedef void (*hsa_amd_queue_intercept_handler)(const void*, uint64_t, uint64_t, void*,\n"
|
||||
self.h_content += " hsa_amd_queue_intercept_packet_writer);\n"
|
||||
self.h_content += "typedef void (*hsa_amd_runtime_queue_notifier)(const hsa_queue_t*, hsa_agent_t, void*);\n"
|
||||
|
||||
self.h_content += self.add_section(
|
||||
"API arg structure", " ", self.gen_arg_struct
|
||||
)
|
||||
self.h_content += self.add_section(
|
||||
"API output stream", " ", self.gen_out_stream
|
||||
)
|
||||
self.h_content += "#endif /* " + out_macro + " */\n"
|
||||
|
||||
self.cpp_content += (
|
||||
"/* Generated by " + os.path.basename(__file__) + " */\n" + license + "\n\n"
|
||||
)
|
||||
|
||||
self.cpp_content += "#include <hsa/hsa_api_trace.h>\n"
|
||||
self.cpp_content += "#include <atomic>\n"
|
||||
self.cpp_content += "namespace roctracer::hsa_support::detail {\n"
|
||||
|
||||
self.cpp_content += "static CoreApiTable CoreApi_saved_before_cb;\n"
|
||||
self.cpp_content += "static AmdExtTable AmdExt_saved_before_cb;\n"
|
||||
self.cpp_content += "static ImageExtTable ImageExt_saved_before_cb;\n\n"
|
||||
|
||||
self.cpp_content += self.add_section(
|
||||
"API callback functions", "", self.gen_callbacks
|
||||
)
|
||||
self.cpp_content += self.add_section(
|
||||
"API intercepting code", "", self.gen_intercept
|
||||
)
|
||||
self.cpp_content += self.add_section(
|
||||
"API get_name function", " ", self.gen_get_name
|
||||
)
|
||||
self.cpp_content += self.add_section(
|
||||
"API get_code function", " ", self.gen_get_code
|
||||
)
|
||||
self.cpp_content += "\n};\n"
|
||||
|
||||
# add code section
|
||||
def add_section(self, title, gap, fun):
|
||||
content = ""
|
||||
n = 0
|
||||
content += "\n/* section: " + title + " */\n\n"
|
||||
content += fun(-1, "-", "-", {})
|
||||
for index in range(len(self.api_names)):
|
||||
last = index == len(self.api_names) - 1
|
||||
name = self.api_names[index]
|
||||
if n != 0:
|
||||
if gap == "":
|
||||
content += fun(n, name, "-", {})
|
||||
content += "\n"
|
||||
content += gap + "/* block: " + name + " API */\n"
|
||||
for call in self.api_calls[name]:
|
||||
content += fun(n, name, call, self.api_data[call])
|
||||
n += 1
|
||||
content += fun(n, "-", "-", {})
|
||||
return content
|
||||
|
||||
# generate API ID enumeration
|
||||
def gen_id_enum(self, n, name, call, data):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "enum hsa_api_id_t {\n"
|
||||
return content
|
||||
if call != "-":
|
||||
content += " " + self.api_id[call] + " = " + str(n) + ",\n"
|
||||
else:
|
||||
content += "\n"
|
||||
content += " HSA_API_ID_DISPATCH = " + str(n) + ",\n"
|
||||
content += " HSA_API_ID_NUMBER = " + str(n + 1) + ",\n"
|
||||
content += "};\n"
|
||||
return content
|
||||
|
||||
# generate API args structure
|
||||
def gen_arg_struct(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "typedef struct hsa_api_data_s {\n"
|
||||
content += " uint64_t correlation_id;\n"
|
||||
content += " uint32_t phase;\n"
|
||||
content += " union {\n"
|
||||
for ret_type in self.api_rettypes:
|
||||
content += " " + ret_type + " " + ret_type + "_retval;\n"
|
||||
content += " };\n"
|
||||
content += " union {\n"
|
||||
return content
|
||||
if call != "-":
|
||||
content += " struct {\n"
|
||||
for var, item in struct["astr"].items():
|
||||
content += " " + item + ";\n"
|
||||
if (
|
||||
call == "hsa_amd_memory_async_copy_rect"
|
||||
and item == "const hsa_dim3_t* range"
|
||||
):
|
||||
content += " hsa_dim3_t range__val;\n"
|
||||
content += " } " + call + ";\n"
|
||||
else:
|
||||
content += " } args;\n"
|
||||
content += " uint64_t *phase_data;\n"
|
||||
content += "} hsa_api_data_t;\n"
|
||||
return content
|
||||
|
||||
# generate API callbacks
|
||||
def gen_callbacks(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "/* section: Static declarations */\n"
|
||||
content += "\n"
|
||||
if call != "-":
|
||||
call_id = self.api_id[call]
|
||||
ret_type = struct["ret"]
|
||||
content += (
|
||||
"static "
|
||||
+ ret_type
|
||||
+ " "
|
||||
+ call
|
||||
+ "_callback("
|
||||
+ struct["args"]
|
||||
+ ") {\n"
|
||||
)
|
||||
|
||||
content += " hsa_trace_data_t trace_data;\n"
|
||||
content += " bool enabled{false};\n"
|
||||
content += "\n"
|
||||
content += " if (auto function = report_activity.load(std::memory_order_relaxed); function &&\n"
|
||||
content += " (enabled =\n"
|
||||
content += (
|
||||
" function(ACTIVITY_DOMAIN_HSA_API, "
|
||||
+ call_id
|
||||
+ ", &trace_data) == 0)) {\n"
|
||||
)
|
||||
content += " if (trace_data.phase_enter != nullptr) {\n"
|
||||
|
||||
for var in struct["alst"]:
|
||||
item = struct["astr"][var]
|
||||
if re.search(r"char\* ", item):
|
||||
# FIXME: we should not strdup the char* arguments here, as the callback will not outlive the scope of this function. Instead, we
|
||||
# should generate a helper function to capture the content of the arguments similar to hipApiArgsInit for HIP. We also need a
|
||||
# helper to free the memory that is allocated to capture the content.
|
||||
content += (
|
||||
" trace_data.api_data.args."
|
||||
+ call
|
||||
+ "."
|
||||
+ var
|
||||
+ " = "
|
||||
+ "("
|
||||
+ var
|
||||
+ " != NULL) ? strdup("
|
||||
+ var
|
||||
+ ")"
|
||||
+ " : NULL;\n"
|
||||
)
|
||||
else:
|
||||
content += (
|
||||
" trace_data.api_data.args."
|
||||
+ call
|
||||
+ "."
|
||||
+ var
|
||||
+ " = "
|
||||
+ var
|
||||
+ ";\n"
|
||||
)
|
||||
if call == "hsa_amd_memory_async_copy_rect" and var == "range":
|
||||
content += (
|
||||
" trace_data.api_data.args."
|
||||
+ call
|
||||
+ "."
|
||||
+ var
|
||||
+ "__val = "
|
||||
+ "*("
|
||||
+ var
|
||||
+ ");\n"
|
||||
)
|
||||
|
||||
content += " trace_data.phase_enter(" + call_id + ", &trace_data);\n"
|
||||
content += " }\n"
|
||||
content += " }\n"
|
||||
content += "\n"
|
||||
|
||||
if ret_type != "void":
|
||||
content += " trace_data.api_data." + ret_type + "_retval = "
|
||||
content += (
|
||||
" "
|
||||
+ name
|
||||
+ "_saved_before_cb."
|
||||
+ call
|
||||
+ "_fn("
|
||||
+ ", ".join(struct["alst"])
|
||||
+ ");\n"
|
||||
)
|
||||
|
||||
content += "\n"
|
||||
content += " if (enabled && trace_data.phase_exit != nullptr)\n"
|
||||
content += " trace_data.phase_exit(" + call_id + ", &trace_data);\n"
|
||||
|
||||
if ret_type != "void":
|
||||
content += " return trace_data.api_data." + ret_type + "_retval;\n"
|
||||
content += "}\n"
|
||||
|
||||
return content
|
||||
|
||||
# generate API intercepting code
|
||||
def gen_intercept(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n > 0 and call == "-":
|
||||
content += "};\n"
|
||||
if n == 0 or (call == "-" and name != "-"):
|
||||
content += (
|
||||
"static void Install" + name + "Wrappers(" + name + "Table* table) {\n"
|
||||
)
|
||||
content += " " + name + "_saved_before_cb = *table;\n"
|
||||
if call != "-":
|
||||
if call != "hsa_shut_down":
|
||||
content += " table->" + call + "_fn = " + call + "_callback;\n"
|
||||
else:
|
||||
content += " { void* p = (void*)" + call + "_callback; (void)p; }\n"
|
||||
return content
|
||||
|
||||
# generate API name function
|
||||
def gen_get_name(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "static const char* GetApiName(uint32_t id) {\n"
|
||||
content += " switch (id) {\n"
|
||||
return content
|
||||
if call != "-":
|
||||
content += " case " + self.api_id[call] + ': return "' + call + '";\n'
|
||||
else:
|
||||
content += " }\n"
|
||||
content += ' return "unknown";\n'
|
||||
content += "}\n"
|
||||
return content
|
||||
|
||||
# generate API code function
|
||||
def gen_get_code(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "static uint32_t GetApiCode(const char* str) {\n"
|
||||
return content
|
||||
if call != "-":
|
||||
content += (
|
||||
' if (strcmp("'
|
||||
+ call
|
||||
+ '", str) == 0) return '
|
||||
+ self.api_id[call]
|
||||
+ ";\n"
|
||||
)
|
||||
else:
|
||||
content += " return HSA_API_ID_NUMBER;\n"
|
||||
content += "}\n"
|
||||
return content
|
||||
|
||||
# generate stream operator
|
||||
def gen_out_stream(self, n, name, call, struct):
|
||||
content = ""
|
||||
if n == -1:
|
||||
content += "#ifdef __cplusplus\n"
|
||||
content += '#include "hsa_ostream_ops.h"\n'
|
||||
content += (
|
||||
"typedef std::pair<uint32_t, hsa_api_data_t> hsa_api_data_pair_t;\n"
|
||||
)
|
||||
content += "inline std::ostream& operator<< (std::ostream& out, const hsa_api_data_pair_t& data_pair) {\n"
|
||||
content += " const uint32_t cid = data_pair.first;\n"
|
||||
content += " const hsa_api_data_t& api_data = data_pair.second;\n"
|
||||
content += " switch(cid) {\n"
|
||||
return content
|
||||
if call != "-":
|
||||
content += " case " + self.api_id[call] + ": {\n"
|
||||
content += ' out << "' + call + '(";\n'
|
||||
arg_list = struct["alst"]
|
||||
if len(arg_list) != 0:
|
||||
for ind in range(len(arg_list)):
|
||||
arg_var = arg_list[ind]
|
||||
arg_val = "api_data.args." + call + "." + arg_var
|
||||
if re.search(r"char\* ", struct["astr"][arg_var]):
|
||||
content += ' out << "0x" << std::hex << (uint64_t)' + arg_val
|
||||
else:
|
||||
content += " out << " + arg_val
|
||||
if (
|
||||
call == "hsa_amd_memory_async_copy_rect"
|
||||
and arg_var == "range"
|
||||
):
|
||||
content += ' << ", ";\n'
|
||||
content += " out << " + arg_val + "__val"
|
||||
"""
|
||||
arg_item = struct['tlst'][ind]
|
||||
if re.search(r'\(\* ', arg_item): arg_pref = ''
|
||||
elif re.search(r'void\* ', arg_item): arg_pref = ''
|
||||
elif re.search(r'\*\* ', arg_item): arg_pref = '**'
|
||||
elif re.search(r'\* ', arg_item): arg_pref = '*'
|
||||
else: arg_pref = ''
|
||||
if arg_pref != '':
|
||||
content += ' if (' + arg_val + ') out << ' + arg_pref + '(' + arg_val + '); else out << ' + arg_val
|
||||
else:
|
||||
content += ' out << ' + arg_val
|
||||
"""
|
||||
if ind < len(arg_list) - 1:
|
||||
content += ' << ", ";\n'
|
||||
else:
|
||||
content += ";\n"
|
||||
if struct["ret"] != "void":
|
||||
content += (
|
||||
' out << ") = " << api_data.' + struct["ret"] + "_retval;\n"
|
||||
)
|
||||
else:
|
||||
content += ' out << ") = void";\n'
|
||||
content += " break;\n"
|
||||
content += " }\n"
|
||||
else:
|
||||
content += " default:\n"
|
||||
content += ' out << "ERROR: unknown API";\n'
|
||||
content += " abort();\n"
|
||||
content += " }\n"
|
||||
content += " return out;\n"
|
||||
content += "}\n"
|
||||
content += "#endif\n"
|
||||
return content
|
||||
|
||||
|
||||
#############################################################
|
||||
# main
|
||||
# Usage
|
||||
if len(sys.argv) != 3:
|
||||
print(
|
||||
"Usage:", sys.argv[0], " <OUT prefix> <HSA runtime include path>", file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
PREFIX = sys.argv[1] + "/"
|
||||
HSA_DIR = sys.argv[2] + "/"
|
||||
|
||||
descr = API_DescrParser(H_OUT, HSA_DIR, API_TABLES_H, API_HEADERS_H, LICENSE)
|
||||
|
||||
out_file = PREFIX + H_OUT
|
||||
print('Generating "' + out_file + '"')
|
||||
f = open(out_file, "w")
|
||||
f.write(descr.h_content[:-1])
|
||||
f.close()
|
||||
|
||||
out_file = PREFIX + CPP_OUT
|
||||
print('Generating "' + out_file + '"')
|
||||
f = open(out_file, "w")
|
||||
f.write(descr.cpp_content[:-1])
|
||||
f.close()
|
||||
#############################################################
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# LeakSanitizer suppressions file for rocprofiler project.
|
||||
#
|
||||
|
||||
leak:amd_comgr
|
||||
leak:hsa-runtime
|
||||
leak:amdhip
|
||||
leak:python
|
||||
Executable
+454
@@ -0,0 +1,454 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import socket
|
||||
import shutil
|
||||
import argparse
|
||||
import multiprocessing
|
||||
|
||||
# this constant is used to define CTEST_PROJECT_NAME
|
||||
# and default value for CTEST_SUBMIT_URL
|
||||
_PROJECT_NAME = "rocprofiler"
|
||||
_BASE_URL = "10.194.116.31/cdash"
|
||||
|
||||
|
||||
def which(cmd, require):
|
||||
v = shutil.which(cmd)
|
||||
if require and v is None:
|
||||
raise RuntimeError(f"{cmd} not found")
|
||||
return v if v is not None else ""
|
||||
|
||||
|
||||
def generate_custom(args, cmake_args, ctest_args):
|
||||
if not os.path.exists(args.binary_dir):
|
||||
os.makedirs(args.binary_dir)
|
||||
|
||||
if args.memcheck is not None:
|
||||
if args.coverage:
|
||||
raise ValueError(
|
||||
f"Enabling --memcheck={args.memcheck} and --coverage not supported"
|
||||
)
|
||||
cmake_args += [f"-DROCPROFILER_MEMCHECK={args.memcheck}"]
|
||||
|
||||
NAME = args.name
|
||||
SITE = args.site
|
||||
BUILD_JOBS = args.build_jobs
|
||||
SUBMIT_URL = args.submit_url
|
||||
SOURCE_DIR = os.path.realpath(args.source_dir)
|
||||
BINARY_DIR = os.path.realpath(args.binary_dir)
|
||||
CMAKE_ARGS = " ".join(cmake_args)
|
||||
CTEST_ARGS = " ".join(ctest_args)
|
||||
|
||||
GIT_CMD = which("git", require=True)
|
||||
GCOV_CMD = which("gcov", require=False)
|
||||
CMAKE_CMD = which("cmake", require=True)
|
||||
# CTEST_CMD = which("ctest", require=True)
|
||||
|
||||
NAME = re.sub(r"(.*)-([0-9]+)/merge", "PR_\\2_\\1", NAME)
|
||||
|
||||
DEFAULT_CMAKE_ARGS = " ".join(
|
||||
[f"-DROCPROFILER_BUILD_{x}=ON" for x in ["CI", "TESTS", "SAMPLES"]]
|
||||
)
|
||||
|
||||
GPU_TARGETS = ";".join(args.gpu_targets)
|
||||
MEMCHECK_TYPE = "" if args.memcheck is None else args.memcheck
|
||||
|
||||
MEMCHECK_SANITIZER_OPTIONS = ""
|
||||
MEMCHECK_SUPPRESSION_FILE = ""
|
||||
|
||||
if MEMCHECK_TYPE == "AddressSanitizer":
|
||||
MEMCHECK_SANITIZER_OPTIONS = "detect_leaks=0 use_sigaltstack=0"
|
||||
MEMCHECK_SUPPRESSION_FILE = f"{SOURCE_DIR}/script/address-sanitizer-suppr.txt"
|
||||
elif MEMCHECK_TYPE == "LeakSanitizer":
|
||||
MEMCHECK_SUPPRESSION_FILE = f"{SOURCE_DIR}/script/leak-sanitizer-suppr.txt"
|
||||
elif MEMCHECK_TYPE == "ThreadSanitizer":
|
||||
external_symbolizer_path = ""
|
||||
for version in range(8, 20):
|
||||
_symbolizer = shutil.which(f"llvm-symbolizer-{version}")
|
||||
if _symbolizer:
|
||||
external_symbolizer_path = f"external_symbolizer_path={_symbolizer}"
|
||||
os.environ["TSAN_OPTIONS"] = " ".join(
|
||||
[
|
||||
"history_size=5",
|
||||
"second_deadlock_stack=1",
|
||||
f"suppressions={SOURCE_DIR}/script/thread-sanitizer-suppr.txt",
|
||||
external_symbolizer_path,
|
||||
os.environ.get("TSAN_OPTIONS", ""),
|
||||
]
|
||||
)
|
||||
|
||||
return f"""
|
||||
set(CTEST_PROJECT_NAME "{_PROJECT_NAME}")
|
||||
set(CTEST_NIGHTLY_START_TIME "05:00:00 UTC")
|
||||
|
||||
set(CTEST_DROP_METHOD "http")
|
||||
set(CTEST_DROP_SITE_CDASH TRUE)
|
||||
set(CTEST_SUBMIT_URL "http://{SUBMIT_URL}")
|
||||
|
||||
set(CTEST_UPDATE_TYPE git)
|
||||
set(CTEST_UPDATE_VERSION_ONLY TRUE)
|
||||
set(CTEST_GIT_COMMAND {GIT_CMD})
|
||||
set(CTEST_GIT_INIT_SUBMODULES FALSE)
|
||||
|
||||
set(CTEST_OUTPUT_ON_FAILURE TRUE)
|
||||
set(CTEST_USE_LAUNCHERS TRUE)
|
||||
set(CMAKE_CTEST_ARGUMENTS --output-on-failure {CTEST_ARGS})
|
||||
|
||||
set(CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS "100")
|
||||
set(CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS "100")
|
||||
set(CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE "51200")
|
||||
set(CTEST_CUSTOM_COVERAGE_EXCLUDE "/usr/.*;/opt/.*;.*external/.*;.*samples/.*;.*test/.*;.*tests-v2/.*;.*perfetto/perfetto_sdk/.*;.*ctf/barectf.*")
|
||||
|
||||
set(CTEST_MEMORYCHECK_TYPE "{MEMCHECK_TYPE}")
|
||||
set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE "{MEMCHECK_SUPPRESSION_FILE}")
|
||||
set(CTEST_MEMORYCHECK_SANITIZER_OPTIONS "{MEMCHECK_SANITIZER_OPTIONS}")
|
||||
|
||||
set(CTEST_SITE "{SITE}")
|
||||
set(CTEST_BUILD_NAME "{NAME}")
|
||||
|
||||
set(CTEST_SOURCE_DIRECTORY {SOURCE_DIR})
|
||||
set(CTEST_BINARY_DIRECTORY {BINARY_DIR})
|
||||
|
||||
set(CTEST_CONFIGURE_COMMAND "{CMAKE_CMD} -B {BINARY_DIR} {SOURCE_DIR} {DEFAULT_CMAKE_ARGS} -DGPU_TARGETS={GPU_TARGETS} {CMAKE_ARGS}")
|
||||
set(CTEST_BUILD_COMMAND "{CMAKE_CMD} --build {BINARY_DIR} --target all --parallel {BUILD_JOBS}")
|
||||
set(CTEST_COVERAGE_COMMAND {GCOV_CMD})
|
||||
"""
|
||||
|
||||
|
||||
def generate_dashboard_script(args):
|
||||
CODECOV = 1 if args.coverage else 0
|
||||
DASHBOARD_MODE = args.mode
|
||||
SOURCE_DIR = os.path.realpath(args.source_dir)
|
||||
BINARY_DIR = os.path.realpath(args.binary_dir)
|
||||
MEMCHECK = 1 if args.memcheck is not None else 0
|
||||
SUBMIT = 0 if args.disable_cdash else 1
|
||||
ARGN = "${ARGN}"
|
||||
|
||||
if args.memcheck == "ThreadSanitizer":
|
||||
MEMCHECK = 0
|
||||
|
||||
_script = f"""
|
||||
macro(dashboard_submit)
|
||||
if("{SUBMIT}" GREATER 0)
|
||||
ctest_submit({ARGN})
|
||||
endif()
|
||||
endmacro()
|
||||
"""
|
||||
|
||||
_script += """
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CTestCustom.cmake")
|
||||
|
||||
macro(handle_error _message _ret)
|
||||
if(NOT ${${_ret}} EQUAL 0)
|
||||
dashboard_submit(PARTS Done RETURN_VALUE _submit_ret)
|
||||
message(FATAL_ERROR "${_message} failed: ${${_ret}}")
|
||||
endif()
|
||||
endmacro()
|
||||
"""
|
||||
|
||||
_script += f"""
|
||||
ctest_start({DASHBOARD_MODE})
|
||||
ctest_update(SOURCE "{SOURCE_DIR}" RETURN_VALUE _update_ret
|
||||
CAPTURE_CMAKE_ERROR _update_err)
|
||||
ctest_configure(BUILD "{BINARY_DIR}" RETURN_VALUE _configure_ret)
|
||||
dashboard_submit(PARTS Start Update Configure RETURN_VALUE _submit_ret)
|
||||
|
||||
if(NOT _update_err EQUAL 0)
|
||||
message(WARNING "ctest_update failed")
|
||||
endif()
|
||||
|
||||
handle_error("Configure" _configure_ret)
|
||||
|
||||
ctest_build(BUILD "{BINARY_DIR}" RETURN_VALUE _build_ret)
|
||||
dashboard_submit(PARTS Build RETURN_VALUE _submit_ret)
|
||||
|
||||
handle_error("Build" _build_ret)
|
||||
|
||||
if("{MEMCHECK}" GREATER 0)
|
||||
ctest_memcheck(BUILD "{BINARY_DIR}" RETURN_VALUE _test_ret)
|
||||
dashboard_submit(PARTS Test RETURN_VALUE _submit_ret)
|
||||
else()
|
||||
ctest_test(BUILD "{BINARY_DIR}" RETURN_VALUE _test_ret)
|
||||
dashboard_submit(PARTS Test RETURN_VALUE _submit_ret)
|
||||
endif()
|
||||
|
||||
if("{CODECOV}" GREATER 0)
|
||||
ctest_coverage(
|
||||
BUILD "{BINARY_DIR}"
|
||||
RETURN_VALUE _coverage_ret
|
||||
CAPTURE_CMAKE_ERROR _coverage_err)
|
||||
dashboard_submit(PARTS Coverage RETURN_VALUE _submit_ret)
|
||||
endif()
|
||||
|
||||
handle_error("Testing" _test_ret)
|
||||
|
||||
dashboard_submit(PARTS Done RETURN_VALUE _submit_ret)
|
||||
"""
|
||||
return _script
|
||||
|
||||
|
||||
def parse_cdash_args(args):
|
||||
BUILD_JOBS = multiprocessing.cpu_count()
|
||||
DASHBOARD_MODE = "Continuous"
|
||||
DASHBOARD_STAGES = [
|
||||
"Start",
|
||||
"Update",
|
||||
"Configure",
|
||||
"Build",
|
||||
"Test",
|
||||
"MemCheck",
|
||||
"Coverage",
|
||||
"Submit",
|
||||
]
|
||||
SOURCE_DIR = os.getcwd()
|
||||
BINARY_DIR = os.path.join(SOURCE_DIR, "build")
|
||||
SITE = socket.gethostname()
|
||||
SUBMIT_URL = f"{_BASE_URL}/submit.php?project={_PROJECT_NAME}"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"-n", "--name", help="Job name", default=None, type=str, required=True
|
||||
)
|
||||
parser.add_argument("-s", "--site", help="Site name", default=SITE, type=str)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", help="Disable printing logs", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--coverage", help="Enable code coverage", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--build-jobs",
|
||||
help="Number of build tasks",
|
||||
default=BUILD_JOBS,
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-B",
|
||||
"--binary-dir",
|
||||
help="Build directory",
|
||||
default=BINARY_DIR,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-S",
|
||||
"--source-dir",
|
||||
help="Source directory",
|
||||
default=SOURCE_DIR,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-F",
|
||||
"--clean",
|
||||
help="Remove existing build directory",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-M",
|
||||
"--mode",
|
||||
help="Dashboard mode",
|
||||
default=DASHBOARD_MODE,
|
||||
choices=("Continuous", "Nightly", "Experimental"),
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-T",
|
||||
"--stages",
|
||||
help="Dashboard stages",
|
||||
nargs="+",
|
||||
default=DASHBOARD_STAGES,
|
||||
choices=DASHBOARD_STAGES,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--submit-url",
|
||||
help="CDash submission site",
|
||||
default=SUBMIT_URL,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repeat-until-pass",
|
||||
help="<N> for --repeat until-pass:<N>",
|
||||
default=None,
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repeat-until-fail",
|
||||
help="<N> for --repeat until-fail:<N>",
|
||||
default=None,
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repeat-after-timeout",
|
||||
help="<N> for --repeat after-timeout:<N>",
|
||||
default=None,
|
||||
type=int,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--disable-cdash",
|
||||
help="Disable submitting results to CDash dashboard",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gpu-targets",
|
||||
help="GPU build architectures",
|
||||
default="gfx900 gfx906 gfx908 gfx90a gfx942 gfx1030 gfx1031 gfx1032 gfx1100 gfx1101 gfx1102 gfx1150 gfx1151".split(),
|
||||
type=str,
|
||||
nargs="+",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--memcheck",
|
||||
help="Run dynamic analysis tool",
|
||||
default=None,
|
||||
type=str,
|
||||
choices=(
|
||||
"ThreadSanitizer",
|
||||
"AddressSanitizer",
|
||||
"LeakSanitizer",
|
||||
"MemorySanitizer",
|
||||
"UndefinedBehaviorSanitizer",
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--linter",
|
||||
help="Enable linting tool",
|
||||
default=None,
|
||||
type=str,
|
||||
choices=("clang-tidy",),
|
||||
)
|
||||
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
index = 0
|
||||
input_args = []
|
||||
ctest_args = []
|
||||
cmake_args = []
|
||||
data = [input_args, cmake_args, ctest_args]
|
||||
cmd = os.path.basename(sys.argv[0])
|
||||
|
||||
for itr in args:
|
||||
if itr == "--":
|
||||
index += 1
|
||||
if index > 2:
|
||||
raise RuntimeError(
|
||||
f"Usage: {cmd} <options> -- <cmake-args> -- <ctest-args>"
|
||||
)
|
||||
else:
|
||||
data[index].append(itr)
|
||||
|
||||
cdash_args = parse_cdash_args(input_args)
|
||||
|
||||
if cdash_args.coverage:
|
||||
cmake_args += ["-DROCPROFILER_BUILD_CODECOV=ON"]
|
||||
|
||||
if cdash_args.linter == "clang-tidy":
|
||||
cmake_args += ["-DROCPROFILER_ENABLE_CLANG_TIDY=ON"]
|
||||
|
||||
def get_repeat_val(_param):
|
||||
_value = getattr(cdash_args, f"repeat_{_param}".replace("-", "_"))
|
||||
return [f"{_param}:{_value}"] if _value is not None and _value > 1 else []
|
||||
|
||||
repeat_args = (
|
||||
get_repeat_val("until-pass")
|
||||
+ get_repeat_val("until-fail")
|
||||
+ get_repeat_val("after-timeout")
|
||||
)
|
||||
ctest_args += ["--repeat"] + repeat_args if len(repeat_args) > 0 else []
|
||||
|
||||
return [cdash_args, cmake_args, ctest_args]
|
||||
|
||||
|
||||
def run(*args, **kwargs):
|
||||
import subprocess
|
||||
|
||||
return subprocess.run(*args, **kwargs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args, cmake_args, ctest_args = parse_args()
|
||||
|
||||
if args.clean and os.path.exists(args.binary_dir):
|
||||
if args.source_dir == args.binary_dir:
|
||||
raise RuntimeError(
|
||||
f"cannot clean binary directory == source directory ({args.source_dir})"
|
||||
)
|
||||
|
||||
shutil.rmtree(args.binary_dir)
|
||||
|
||||
if not os.path.exists(args.binary_dir):
|
||||
os.makedirs(args.binary_dir)
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
_config = dedent(generate_custom(args, cmake_args, ctest_args))
|
||||
_script = dedent(generate_dashboard_script(args))
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write(f"##### CTestCustom.cmake #####\n\n{_config}\n\n")
|
||||
sys.stderr.write(f"##### dashboard.cmake #####\n\n{_script}\n\n")
|
||||
|
||||
with open(os.path.join(args.binary_dir, "CTestCustom.cmake"), "w") as f:
|
||||
f.write(f"{_config}\n")
|
||||
|
||||
with open(os.path.join(args.binary_dir, "dashboard.cmake"), "w") as f:
|
||||
f.write(f"{_script}\n")
|
||||
|
||||
CTEST_CMD = which("ctest", require=True)
|
||||
|
||||
dashboard_args = ["-D"]
|
||||
for itr in args.stages:
|
||||
dashboard_args.append(f"{args.mode}{itr}")
|
||||
|
||||
try:
|
||||
if not args.quiet and len(ctest_args) == 0:
|
||||
ctest_args = ["--output-on-failure", "-V"]
|
||||
|
||||
run(
|
||||
[CTEST_CMD]
|
||||
+ dashboard_args
|
||||
+ [
|
||||
"-S",
|
||||
os.path.join(args.binary_dir, "dashboard.cmake"),
|
||||
]
|
||||
+ ctest_args,
|
||||
check=True,
|
||||
)
|
||||
finally:
|
||||
if "-VV" not in ctest_args and not args.quiet:
|
||||
for file in glob.glob(
|
||||
os.path.join(args.binary_dir, "Testing/Temporary/**"),
|
||||
recursive=True,
|
||||
):
|
||||
if not os.path.isfile(file):
|
||||
continue
|
||||
if (
|
||||
re.match(
|
||||
r"Last(Start|Update|Configure|Build|Test).*\.log$",
|
||||
os.path.basename(file),
|
||||
)
|
||||
is None
|
||||
):
|
||||
continue
|
||||
|
||||
print(f"\n\n\n###### Reading {file}... ######\n\n\n")
|
||||
with open(file, "r") as inpf:
|
||||
fdata = inpf.read()
|
||||
if "LastTest" not in file and "Coverage" not in file:
|
||||
print(fdata)
|
||||
oname = os.path.basename(file)
|
||||
if oname.endswith(".log"):
|
||||
oname += ".log"
|
||||
with open(os.path.join(args.binary_dir, oname), "w") as outf:
|
||||
print(f"\n\n###### Writing {oname}... ######\n\n")
|
||||
outf.write(fdata)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# ThreadSanitizer suppressions file for rocprofiler project.
|
||||
#
|
||||
|
||||
# leaked thread
|
||||
thread:libhsa-runtime64.so
|
||||
|
||||
# unlock of an unlocked mutex (or by a wrong thread)
|
||||
mutex:librocm_smi64.so
|
||||
Reference in new issue
Block a user