Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

419 lines
12 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Util to invoke C/C++ compilers in the system."""
import os
import shutil
import subprocess
# pylint: disable=invalid-name
import sys
from . import tar as _tar
from . import utils as _utils
def _is_linux_like():
return (
sys.platform == "darwin"
or sys.platform.startswith("linux")
or sys.platform.startswith("freebsd")
)
def _is_windows_like():
return sys.platform == "win32"
def get_cc():
"""Return the path to the default C/C++ compiler.
Returns
-------
out: Optional[str]
The path to the default C/C++ compiler, or None if none was found.
"""
if not _is_linux_like():
return None
env_cxx = os.environ.get("CXX") or os.environ.get("CC")
if env_cxx:
return env_cxx
cc_names = ["g++", "gcc", "clang++", "clang", "c++", "cc"]
dirs_in_path = os.get_exec_path()
for cc in cc_names:
for d in dirs_in_path:
cc_path = os.path.join(d, cc)
if os.path.isfile(cc_path) and os.access(cc_path, os.X_OK):
return cc_path
return None
def create_shared(output, objects, options=None, cc=None, cwd=None, ccache_env=None):
"""Create shared library.
Parameters
----------
output : str
The target shared library.
objects : List[str]
List of object files.
options : List[str]
The list of additional options string.
cc : Optional[str]
The compiler command.
cwd : Optional[str]
The current working directory.
ccache_env : Optional[Dict[str, str]]
The environment variable for ccache. Set `None` to disable ccache by default.
"""
cc = cc or get_cc()
if _is_linux_like():
_linux_compile(output, objects, options, cc, cwd, ccache_env, compile_shared=True)
elif _is_windows_like():
_windows_compile(output, objects, options, cwd, ccache_env)
else:
raise ValueError("Unsupported platform")
def _linux_ar(output, inputs, ar):
ar = ar or "ar"
libname = os.path.basename(output)
if not libname.startswith("lib"):
libname = "lib" + libname
temp = _utils.tempdir()
temp_output = temp.relpath(libname)
cmd = [ar, "-crs", temp_output]
# handles the case where some input files are tar of objects
# unpack them and return the list of files inside
objects = _tar.normalize_file_list_by_unpacking_tars(temp, inputs)
cmd += objects
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(out, _) = proc.communicate()
if proc.returncode != 0:
msg = "AR error:\n"
msg += out.decode("utf-8", errors="replace")
msg += "\nCommand line: " + " ".join(cmd)
raise RuntimeError(msg)
shutil.move(temp_output, output)
def create_staticlib(output, inputs, ar=None):
"""Create static library.
Parameters
----------
output : str
The target shared library.
inputs : List[str]
List of inputs files. Each input file can be a tarball
of objects or an object file.
ar : Optional[str]
Path to the ar command to be used
"""
if _is_linux_like():
return _linux_ar(output, inputs, ar)
else:
raise ValueError("Unsupported platform")
def create_executable(output, objects, options=None, cc=None, cwd=None, ccache_env=None):
"""Create executable binary.
Parameters
----------
output : str
The target executable.
objects : List[str]
List of object files.
options : List[str]
The list of additional options string.
cc : Optional[str]
The compiler command.
cwd : Optional[str]
The urrent working directory.
ccache_env : Optional[Dict[str, str]]
The environment variable for ccache. Set `None` to disable ccache by default.
"""
cc = cc or get_cc()
if _is_linux_like():
_linux_compile(output, objects, options, cc, cwd, ccache_env)
elif _is_windows_like():
_windows_compile(output, objects, options, cwd, ccache_env)
else:
raise ValueError("Unsupported platform")
def get_global_symbol_section_map(path, *, nm=None) -> dict[str, str]:
"""Get global symbols from a library via nm -g
Parameters
----------
path : str
The library path
nm: str
The path to nm command
Returns
-------
symbol_section_map: Dict[str, str]
A map from defined global symbol to their sections
"""
if nm is None:
if not _is_linux_like():
raise ValueError("Unsupported platform")
nm = "nm"
symbol_section_map = {}
if not os.path.isfile(path):
raise FileNotFoundError(f"{path} does not exist")
cmd = [nm, "-gU", path]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(out, _) = proc.communicate()
if proc.returncode != 0:
msg = "Runtime error:\n"
msg += out.decode("utf-8", errors="replace")
raise RuntimeError(msg)
for line in out.decode("utf-8", errors="replace").split("\n"):
data = line.strip().split()
if len(data) != 3:
continue
symbol = data[-1]
section = data[-2]
symbol_section_map[symbol] = section
return symbol_section_map
def get_target_by_dump_machine(compiler):
"""Functor of get_target_triple that can get the target triple using compiler.
Parameters
----------
compiler : Optional[str]
The compiler.
Returns
-------
out: Callable
A function that can get target triple according to dumpmachine option of compiler.
"""
def get_target_triple():
"""Get target triple according to dumpmachine option of compiler."""
if compiler:
cmd = [compiler, "-dumpmachine"]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(out, _) = proc.communicate()
if proc.returncode != 0:
msg = "dumpmachine error:\n"
msg += out.decode("utf-8", errors="replace")
return None
return out.decode("utf-8", errors="replace")
return None
return get_target_triple
# assign so as default output format
create_shared.output_format = "so" if sys.platform != "win32" else "dll"
create_shared.get_target_triple = get_target_by_dump_machine(os.environ.get("CXX", get_cc()))
def cross_compiler(
compile_func, options=None, output_format=None, get_target_triple=None, add_files=None
):
"""Create a cross compiler function by specializing compile_func with options.
This function can be used to construct compile functions that
can be passed to AutoTVM measure or export_library.
Parameters
----------
compile_func : Union[str, Callable[[str, str, Optional[str]], None]]
Function that performs the actual compilation
options : Optional[List[str]]
List of additional optional string.
output_format : Optional[str]
Library output format.
get_target_triple: Optional[Callable]
Function that can target triple according to dumpmachine option of compiler.
add_files: Optional[List[str]]
List of paths to additional object, source, library files
to pass as part of the compilation.
Returns
-------
fcompile : Callable[[str, str, Optional[str]], None]
A compilation function that can be passed to export_library.
Examples
--------
.. code-block:: python
from tvm.support import cc, ndk
# export using arm gcc
mod = build_runtime_module()
mod.export_library(path_dso,
fcompile=cc.cross_compiler("arm-linux-gnueabihf-gcc"))
# specialize ndk compilation options.
specialized_ndk = cc.cross_compiler(
ndk.create_shared,
["--sysroot=/path/to/sysroot", "-shared", "-fPIC", "-lm"])
mod.export_library(path_dso, fcompile=specialized_ndk)
"""
base_options = [] if options is None else options
kwargs = {}
add_files = [] if add_files is None else add_files
# handle case where compile_func is the name of the cc
if isinstance(compile_func, str):
kwargs = {"cc": compile_func}
compile_func = create_shared
def _fcompile(outputs, objects, options=None):
all_options = base_options
if options is not None:
all_options += options
compile_func(outputs, objects + add_files, options=all_options, **kwargs)
if not output_format and hasattr(compile_func, "output_format"):
output_format = compile_func.output_format
output_format = output_format if output_format else "so"
if not get_target_triple and hasattr(compile_func, "get_target_triple"):
get_target_triple = compile_func.get_target_triple
_fcompile.output_format = output_format
_fcompile.get_target_triple = get_target_triple
return _fcompile
def _linux_compile(
output, objects, options, compile_cmd, cwd=None, ccache_env=None, compile_shared=False
):
cmd = [compile_cmd]
if compile_cmd != "nvcc":
if compile_shared or output.endswith(".so") or output.endswith(".dylib"):
cmd += ["-shared", "-fPIC"]
if sys.platform == "darwin":
cmd += ["-undefined", "dynamic_lookup"]
elif output.endswith(".obj"):
cmd += ["-c"]
else:
if compile_shared or output.endswith(".so") or output.endswith(".dylib"):
cmd += ["-shared"]
cmd += ["-o", output]
if options:
cmd += options
if isinstance(objects, str):
cmd += [objects]
else:
cmd += objects
env = None
if ccache_env is not None:
if shutil.which("ccache"):
cmd.insert(0, "ccache")
env = os.environ.copy()
env.update(ccache_env)
else:
raise ValueError("ccache not found")
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, env=env)
(out, _) = proc.communicate()
if proc.returncode != 0:
msg = "Compilation error:\n"
msg += out.decode("utf-8", errors="replace")
msg += "\nCommand line: " + " ".join(cmd)
raise RuntimeError(msg)
def _windows_compile(output, objects, options, cwd=None, ccache_env=None):
compiler = os.getenv("TVM_WIN_CC", default="clang")
win_target = os.getenv("TVM_WIN_TARGET", default="x86_64")
cmd = [compiler]
cmd += ["-O2"]
cmd += ["--target=" + win_target]
if output.endswith(".so") or output.endswith(".dll"):
cmd += ["-shared"]
elif output.endswith(".obj"):
cmd += ["-c"]
if isinstance(objects, str):
objects = [objects]
cmd += ["-o", output]
cmd += objects
if options:
cmd += options
env = None
if ccache_env is not None:
if shutil.which("ccache"):
cmd.insert(0, "ccache")
env = os.environ.copy()
env.update(ccache_env)
else:
raise ValueError("ccache not found")
try:
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, env=env
)
(out, _) = proc.communicate()
except FileNotFoundError:
raise RuntimeError(
"Can not find the LLVM clang for Windows clang.exe)."
"Make sure it's installed"
" and the installation directory is in the %PATH% environment "
"variable. Prebuilt binaries can be found at: https://llvm.org/"
)
if proc.returncode != 0:
msg = "Compilation error:\n"
msg += " ".join(cmd) + "\n"
msg += out.decode("utf-8", errors="replace")
raise RuntimeError(msg)