Files
project_6_89d52222/cccl_upstream/libcudacxx/test/utils/libcudacxx/compiler.py
EngineX CI 56fd68e7dd [INFRA] Import NVIDIA/CCCL upstream as optimization reference library
CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
2026-07-30 09:35:51 +00:00

528 lines
19 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
#
# ===----------------------------------------------------------------------===##
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.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:
self.verify_flags = [
"-Xclang",
"-verify",
"-Xclang",
"-verify-ignore-unexpected=note",
"-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:
macros = self.dumpMacros()
compiler_type = None
major_ver = minor_ver = patchlevel = None
if "__NVCC__" in macros.keys():
compiler_type = "nvcc"
major_ver = int(macros["__CUDACC_VER_MAJOR__"])
minor_ver = int(macros["__CUDACC_VER_MINOR__"])
patchlevel = int(macros["__CUDACC_VER_BUILD__"])
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 = int(macros["__NVCOMPILER_MAJOR__"].strip())
minor_ver = int(macros["__NVCOMPILER_MINOR__"].strip())
patchlevel = int(macros["__NVCOMPILER_PATCHLEVEL__"].strip())
elif "__clang__" in macros.keys():
compiler_type = "clang"
major_ver = int(macros["__clang_major__"])
minor_ver = int(macros["__clang_minor__"])
patchlevel = int(macros["__clang_patchlevel__"])
elif "__GNUC__" in macros.keys():
compiler_type = "gcc"
major_ver = int(macros["__GNUC__"])
minor_ver = int(macros["__GNUC_MINOR__"])
patchlevel = int(macros["__GNUC_PATCHLEVEL__"])
elif "_MSC_VER" in macros.keys():
compiler_type = "msvc"
major_ver = int(macros["_MSC_FULL_VER"]) // 10000000
minor_ver = int(macros["_MSC_FULL_VER"]) // 100000 % 100
patchlevel = 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"
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:
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):
return self._basicCmd(
source_files, out, flags=flags, mode=mode, input_is_cxx=True
) + ["-c"]
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
)
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
)
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
)
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
)
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
)
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
)
version = None
try:
version = 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
# Assume MSVC flags on Windows
if platform.system() == "Windows":
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:
err_str = "Macros failed to dump:\n\n"
err_str += f"Command: {' '.join(cmd)}\n\n"
err_str += f"Exit code: {rc}\n\n"
err_str += f"stdout:\n{out}\n\n"
err_str += f"stderr:\n{err}\n"
raise RuntimeError(err_str)
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"
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":
flags += ["-Werror", "-fsyntax-only"]
if self.type == "clang" and self.source_lang == "cu":
flags += ["-Wno-unused-command-line-argument"]
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")
)
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