Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

591 lines
23 KiB
Python

# ===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===----------------------------------------------------------------------===##
# Modifications Copyright (c) 2024-2026 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 ast
import os
import platform
import libcudacxx.util
class CXXCompiler(object):
CM_Default = 0
CM_PreProcess = 1
CM_CheckCompileFlag = 2
CM_Compile = 3
CM_Link = 4
def __init__(
self,
path,
first_arg,
flags=None,
compile_flags=None,
link_flags=None,
warning_flags=None,
verify_supported=None,
verify_flags=None,
use_verify=False,
modules_flags=None,
use_modules=False,
use_ccache=False,
use_warnings=False,
compile_env=None,
cxx_type=None,
cxx_version=None,
cuda_path=None,
):
self.source_lang = "c++"
self.compile_timeout = 100
self.path = path
self.first_arg = first_arg or ""
self.flags = list(flags or [])
self.compile_flags = list(compile_flags or [])
self.link_flags = list(link_flags or [])
self.warning_flags = list(warning_flags or [])
self.verify_supported = verify_supported
self.use_verify = use_verify
self.verify_flags = list(verify_flags or [])
assert not use_verify or verify_supported
assert not use_verify or verify_flags is not None
self.modules_flags = list(modules_flags or [])
self.use_modules = use_modules
assert not use_modules or modules_flags is not None
self.use_ccache = use_ccache
self.use_warnings = use_warnings
if compile_env is not None:
self.compile_env = dict(compile_env)
else:
self.compile_env = None
self.type = cxx_type
self.version = cxx_version
self.cuda_path = cuda_path
if self.type is None or self.version is None:
self._initTypeAndVersion()
def isVerifySupported(self):
if self.verify_supported is None:
self.verify_supported = self.hasCompileFlag(
["-Xclang", "-verify-ignore-unexpected"]
)
if self.verify_supported:
ignore_unexpected_types='note'
if self.type=='hipcc':
ignore_unexpected_types+=',error'
self.verify_flags = [
"-Xclang",
"-verify",
"-Xclang",
'-verify-ignore-unexpected='+ignore_unexpected_types,
"-ferror-limit=1024",
]
return self.verify_supported
def useVerify(self, value=True):
self.use_verify = value
assert not self.use_verify or self.verify_flags is not None
def useModules(self, value=True):
self.use_modules = value
assert not self.use_modules or self.modules_flags is not None
def useCCache(self, value=True):
self.use_ccache = value
def useWarnings(self, value=True):
self.use_warnings = value
def _initTypeAndVersion(self):
# Get compiler type and version
try:
#this is a workaround as hipcc won't set __HIPCC__ macro when not being compiled with -x hip
self.source_lang ='hip'
macros_for_hip = self.dumpMacros()
self.source_lang= 'c++'
macros = self.dumpMacros()
compiler_type = None
major_ver = minor_ver = patchlevel = None
if "__NVCC__" in macros.keys():
compiler_type = "nvcc"
major_ver = macros["__CUDACC_VER_MAJOR__"]
minor_ver = macros["__CUDACC_VER_MINOR__"]
patchlevel = macros["__CUDACC_VER_BUILD__"]
elif '__HIPCC__' in macros_for_hip.keys():
compiler_type = 'hipcc'
major_ver = macros['__clang_major__']
minor_ver = macros['__clang_minor__']
patchlevel = macros['__clang_patchlevel__']
elif "__NVCOMPILER" in macros.keys():
compiler_type = "nvhpc"
# NVHPC, unfortunately, adds an extra space between the macro name
# and macro value in their macro dump mode.
major_ver = macros["__NVCOMPILER_MAJOR__"].strip()
minor_ver = macros["__NVCOMPILER_MINOR__"].strip()
patchlevel = macros["__NVCOMPILER_PATCHLEVEL__"].strip()
elif "__clang__" in macros.keys():
compiler_type = "clang"
# Treat Apple's LLVM fork differently.
if "__apple_build_version__" in macros.keys():
compiler_type = "apple-clang"
major_ver = macros["__clang_major__"]
minor_ver = macros["__clang_minor__"]
patchlevel = macros["__clang_patchlevel__"]
elif "__GNUC__" in macros.keys():
compiler_type = "gcc"
major_ver = macros["__GNUC__"]
minor_ver = macros["__GNUC_MINOR__"]
patchlevel = macros["__GNUC_PATCHLEVEL__"]
elif "_MSC_VER" in macros.keys():
compiler_type = "msvc"
major_ver = int(int(macros["_MSC_FULL_VER"]) / 10000000)
minor_ver = int(int(macros["_MSC_FULL_VER"]) / 100000 % 100)
patchlevel = int(int(macros["_MSC_FULL_VER"]) % 100000)
if "__cplusplus" in macros.keys():
if "_MSVC_LANG" in macros.keys():
msvc_lang = macros["_MSVC_LANG"]
if msvc_lang[-1] == "L":
msvc_lang = msvc_lang[:-1]
msvc_lang = int(msvc_lang)
if msvc_lang <= 201103:
default_dialect = "c++11"
elif msvc_lang <= 201402:
default_dialect = "c++14"
elif msvc_lang <= 201703:
default_dialect = "c++17"
elif msvc_lang > 201703:
default_dialect = "c++20"
else:
cplusplus = macros["__cplusplus"]
if cplusplus[-1] == "L":
cplusplus = cplusplus[:-1]
cpp_standard = int(cplusplus)
if cpp_standard <= 199711:
default_dialect = "c++03"
elif cpp_standard <= 201103:
default_dialect = "c++11"
elif cpp_standard <= 201402:
default_dialect = "c++14"
elif cpp_standard <= 201703:
default_dialect = "c++17"
else:
default_dialect = "c++20"
else:
default_dialect = "c++03"
self.type = compiler_type
self.version = (major_ver, minor_ver, patchlevel)
self.default_dialect = default_dialect
except Exception:
(self.type, self.version, self.default_dialect) = self.dumpVersion()
if self.type == "nvcc":
# Treat C++ as CUDA when the compiler is NVCC.
self.source_lang = "cu"
elif self.type == "clang":
# Treat C++ as clang-cuda when the compiler is Clang.
self.source_lang = "cu"
elif self.type == "hipcc":
self.source_lang = "hip"
def _basicCmdCl(
self, source_files, out, mode=CM_Default, flags=[], input_is_cxx=False
):
cmd = []
if (
self.use_ccache
and not mode == self.CM_Link
and not mode == self.CM_PreProcess
and not mode == self.CM_CheckCompileFlag
):
cmd += [os.environ.get("CMAKE_CUDA_COMPILER_LAUNCHER")]
cmd += [self.path] + ([self.first_arg] if self.first_arg != "" else [])
if isinstance(source_files, list):
cmd += source_files
elif isinstance(source_files, str):
cmd += [source_files]
else:
raise TypeError("source_files must be a string or list")
if mode == self.CM_PreProcess or mode == self.CM_CheckCompileFlag:
cmd += ["/Zs", "/options:strict"]
elif mode == self.CM_Compile:
cmd += ["/c"]
cmd += self.flags
if self.use_verify:
cmd += self.verify_flags
assert mode in [self.CM_Default, self.CM_Compile]
if self.use_modules:
cmd += self.modules_flags
if mode != self.CM_Link:
cmd += self.compile_flags
if self.use_warnings:
cmd += self.warning_flags
if (
mode != self.CM_PreProcess
and mode != self.CM_Compile
and mode != self.CM_CheckCompileFlag
):
cmd += self.link_flags
cmd += flags
if out is not None:
cmd += ["/link", '/out:"{}"'.format(out)]
return cmd
def _basicCmd(
self, source_files, out, mode=CM_Default, flags=[], input_is_cxx=False
):
if self.path.startswith("cl") and not self.path.startswith("clang"):
return self._basicCmdCl(source_files, out, mode, flags)
cmd = []
if (
self.use_ccache
and not mode == self.CM_Link
and not mode == self.CM_PreProcess
and not mode == self.CM_CheckCompileFlag
):
cmd += [os.environ.get("CMAKE_CUDA_COMPILER_LAUNCHER")]
cmd += [self.path] + ([self.first_arg] if self.first_arg != "" else [])
if out is not None:
cmd += ["-o", out]
if input_is_cxx:
cmd += ["-x", self.source_lang]
if (
self.type == "clang"
and self.source_lang == "cu"
and self.cuda_path is not None
):
cmd += ["--cuda-path=" + self.cuda_path]
if isinstance(source_files, list):
cmd += source_files
elif isinstance(source_files, str):
cmd += [source_files]
else:
raise TypeError("source_files must be a string or list")
if mode == self.CM_PreProcess:
cmd += ["-E"]
elif mode == self.CM_Compile or mode == self.CM_CheckCompileFlag:
# NOTE(HIP): -c is not used when -fsyntax-only is set.
# We get an error because of -Werror. We resolve it by passing
# -Wno-unused-command-line-argument to the compiler.
if self.type == 'hipcc' or self.type == 'hiprtcc' or self.type == 'clang++':
cmd += ['-Wno-unused-command-line-argument']
cmd += ["-c"]
cmd += self.flags
if self.use_verify:
cmd += self.verify_flags
assert mode in [self.CM_Default, self.CM_Compile]
if self.use_modules:
cmd += self.modules_flags
if mode != self.CM_Link:
cmd += self.compile_flags
if self.use_warnings:
cmd += self.warning_flags
if (
mode != self.CM_PreProcess
and mode != self.CM_Compile
and mode != self.CM_CheckCompileFlag
):
cmd += self.link_flags
cmd += flags
return cmd
def preprocessCmd(self, source_files, out=None, flags=[]):
return self._basicCmd(
source_files, out, flags=flags, mode=self.CM_PreProcess, input_is_cxx=True
)
def compileCmd(self, source_files, out=None, flags=[], mode = CM_Compile):
cmd = self._basicCmd(
source_files, out, flags=flags, mode=mode, input_is_cxx=True
) + ["-c"]
# NOTE(HIP): -c is not used when -fsyntax-only is set.
# We get an error because of -Werror. We resolve it by passing
# -Wno-unused-command-line-argument to the compiler.
if self.type == 'hipcc' or self.type == 'hiprtcc' or self.type == 'clang++':
cmd += ['-Wno-unused-command-line-argument']
return cmd
def linkCmd(self, source_files, out=None, flags=[]):
return self._basicCmd(source_files, out, flags=flags, mode=self.CM_Link)
def compileLinkCmd(self, source_files, out=None, flags=[]):
return self._basicCmd(source_files, out, flags=flags)
def preprocess(self, source_files, out=None, flags=[], cwd=None):
cmd = self.preprocessCmd(source_files, out, flags)
out, err, rc = libcudacxx.util.executeCommand(
cmd, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
return cmd, out, err, rc
def checkCompileFlag(self, source_files, out=None, flags=[], cwd=None):
cmd = self.compileCmd(source_files, out, flags, self.CM_CheckCompileFlag)
out, err, rc = libcudacxx.util.executeCommand(
cmd, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
return cmd, out, err, rc
def compile(self, source_files, out=None, flags=[], cwd=None):
cmd = self.compileCmd(source_files, out, flags, self.CM_Compile)
out, err, rc = libcudacxx.util.executeCommand(
cmd, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
return cmd, out, err, rc
def link(self, source_files, out=None, flags=[], cwd=None):
cmd = self.linkCmd(source_files, out, flags)
out, err, rc = libcudacxx.util.executeCommand(
cmd, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
return cmd, out, err, rc
def compileLink(self, source_files, out=None, flags=[], cwd=None):
cmd = self.compileLinkCmd(source_files, out, flags)
out, err, rc = libcudacxx.util.executeCommand(
cmd, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
return cmd, out, err, rc
def compileLinkTwoSteps(
self, source_file, out=None, object_file=None, flags=[], cwd=None
):
if not isinstance(source_file, str):
raise TypeError("This function only accepts a single input file")
if object_file is None:
# Create, use and delete a temporary object file if none is given.
def with_fn():
return libcudacxx.util.guardedTempFilename(suffix=".o")
else:
# Otherwise wrap the filename in a context manager function.
def with_fn():
return libcudacxx.util.nullContext(object_file)
with with_fn() as object_file:
cc_cmd, cc_stdout, cc_stderr, rc = self.compile(
source_file, object_file, flags=flags, cwd=cwd
)
if rc != 0:
return cc_cmd, cc_stdout, cc_stderr, rc
link_cmd, link_stdout, link_stderr, rc = self.link(
object_file, out=out, flags=flags, cwd=cwd
)
return (
cc_cmd + ["&&"] + link_cmd,
cc_stdout + link_stdout,
cc_stderr + link_stderr,
rc,
)
def dumpVersion(self, flags=[], cwd=None):
dumpversion_cpp = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "dumpversion.cpp"
)
def with_fn():
return libcudacxx.util.guardedTempFilename(suffix=".exe")
with with_fn() as exe:
cmd, out, err, rc = self.compileLink(
[dumpversion_cpp], out=exe, flags=flags, cwd=cwd
)
if rc != 0:
return ("unknown", (0, 0, 0), "c++03")
out, err, rc = libcudacxx.util.executeCommand(
exe, env=self.compile_env, cwd=cwd, timeout=self.compile_timeout
)
version = None
try:
# Security: Use ast.literal_eval instead of eval to prevent code injection
version = ast.literal_eval(out)
except Exception:
pass
if not (isinstance(version, tuple) and 3 == len(version)):
version = ("unknown", (0, 0, 0), "c++03")
return version
def dumpMacros(self, source_files=None, flags=[], cwd=None):
if source_files is None:
source_files = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "empty.cpp"
)
old_flags = flags
# Use MSVC-specific flags only for MSVC compiler
if self.type == 'msvc':
flags = ['/Zc:preprocessor', '/PD'] + old_flags
cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
# Older MSVC does not support dumping macros
if err.find("D9002") > 0:
raise RuntimeError("Cannot be dumped on old MSVC")
if rc != 0:
flags = [
"-Xcompiler",
"/Zc:preprocessor",
"-Xcompiler",
"/PD",
] + old_flags
cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
if err.find("D9002") > 0:
raise RuntimeError("Cannot be dumped on old MSVC")
else:
flags = ["-dM"] + flags
cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
if rc != 0:
flags = ["-Xcompiler"] + flags
cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
if rc != 0:
print(f"DEBUG: Macro dump failed with rc={rc}")
print(f"DEBUG: Command: {cmd}")
print(f"DEBUG: stdout: {out}")
print(f"DEBUG: stderr: {err}")
raise RuntimeError("Macros failed to dump")
parsed_macros = {}
lines = [line.strip() for line in out.split("\n") if line.strip()]
for line in lines:
# NVHPC also outputs the file contents from -E -dM for some reason; handle that
if not line.startswith("#define "):
continue
line = line[len("#define ") :]
macro, _, value = line.partition(" ")
parsed_macros[macro] = value
return parsed_macros
def getTriple(self):
if self.type == "msvc":
return "x86_64-pc-windows-msvc"
# hiprtcc on Windows doesn't have a real compiler executable to query
# Return default Windows triple for clang
if self.type == "hiprtcc" and platform.system() == 'Windows':
return "x86_64-pc-windows-gnu"
cmd = [self.path] + self.flags + ["-dumpmachine"]
return libcudacxx.util.capture(cmd).strip()
def hasCompileFlag(self, flag):
if isinstance(flag, list):
flags = list(flag)
else:
flags = [flag]
# Add -Werror to ensure that an unrecognized flag causes a non-zero
# exit code. -Werror is supported on all known non-nvcc compiler types.
if self.type is not None and self.type != "nvcc" and self.type != "msvc":
# NOTE(HIP): -c is not used when -fsyntax-only is set.
# We get an error because of -Werror. We resolve it by passing
# -Wno-unused-command-line-argument to the compiler.
if self.type == 'hipcc' or self.type == 'hiprtcc' or self.type == 'clang++':
flags += ['-Wno-unused-command-line-argument']
flags += ["-Werror", "-fsyntax-only"]
if self.type == "clang" and self.source_lang == "cu":
flags += ["-Wno-unused-command-line-argument"]
if self.type == 'hiprtcc':
# NOTE(HIPRTC): HIPRTC does not like compiling empty files.
# "ERROR: hiprtcLinkComplete(rtc_link_state, &codePtr, &codeSize2) failed with error HIPRTC_ERROR_LINKING".
# Instead use nothing_to_do.pass.cpp for hiprtcc.
empty_cpp = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../test/nothing_to_do.pass.cpp")
else:
empty_cpp = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "empty.cpp"
)
cmd, out, err, rc = self.checkCompileFlag(
empty_cpp, out=os.devnull, flags=flags
)
if out.find("flag is not supported with the configured host compiler") != -1:
return False
if err.find("flag is not supported with the configured host compiler") != -1:
return False
return rc == 0
def addFlagIfSupported(self, flag):
if isinstance(flag, list):
flags = list(flag)
else:
flags = [flag]
if self.hasCompileFlag(flags):
self.flags += flags
return True
else:
return False
def addCompileFlagIfSupported(self, flag):
if isinstance(flag, list):
flags = list(flag)
else:
flags = [flag]
if self.hasCompileFlag(flags):
self.compile_flags += flags
return True
else:
return False
def hasWarningFlag(self, flag):
"""
hasWarningFlag - Test if the compiler supports a given warning flag.
Unlike addCompileFlagIfSupported, this function detects when
"-Wno-<warning>" flags are unsupported. If flag is a
"-Wno-<warning>" GCC will not emit an unknown option diagnostic unless
another error is triggered during compilation.
"""
assert isinstance(flag, str)
assert flag.startswith("-W")
if not flag.startswith("-Wno-"):
return self.hasCompileFlag(flag)
flags = ["-Werror", flag]
old_use_warnings = self.use_warnings
self.useWarnings(False)
cmd = self.compileCmd("-", os.devnull, flags)
self.useWarnings(old_use_warnings)
# Remove '-v' because it will cause the command line invocation
# to be printed as part of the error output.
# TODO(EricWF): Are there other flags we need to worry about?
if "-v" in cmd:
cmd.remove("-v")
out, err, rc = libcudacxx.util.executeCommand(
cmd, input=libcudacxx.util.to_bytes("#error\n"), timeout=self.compile_timeout
)
assert rc != 0
if flag in err:
return False
return True
def addWarningFlagIfSupported(self, flag):
if self.hasWarningFlag(flag):
if flag not in self.warning_flags:
self.warning_flags += [flag]
return True
return False