Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ jobs:
echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
fi

# Windows builds may use Clang-CL which needs llvm-profdata for PGO. MSVC
# PGO does not require LLVM.
- name: Install LLVM (Windows)
if: runner.os == 'Windows'
run: |
choco install llvm -y
echo "C:\Program Files\LLVM\bin" >> $env:GITHUB_PATH

- name: Install cibuildwheel
run: python -m pip install cibuildwheel

Expand Down
11 changes: 9 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ if(ENABLE_LTO)
set(LTO_FLAG "-flto")
set(LTO_LINKER_FLAGS "-flto -fuse-linker-plugin -ffat-lto-objects")
elseif(USING_MSVC)
# MSVC uses /GL for compilation and /LTCG for linking
# Alternatively, we can use CMake's INTERPROCEDURAL_OPTIMIZATION
set(LTO_FLAG "/GL")
set(LTO_LINKER_FLAGS "/LTCG")
endif()
Expand All @@ -139,6 +137,10 @@ if(ENABLE_PGO_GENERATE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-generate")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-generate")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-generate")
elseif(MSVC)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /GENPROFILE")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /GENPROFILE")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /GENPROFILE")
else()
message(WARNING "PGO profile generation is not supported with compiler: ${CMAKE_CXX_COMPILER_ID}")
endif()
Expand All @@ -160,6 +162,11 @@ if(ENABLE_PGO_USE)
message(STATUS "PGO: Using GCC profile data from build directory")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-use -fprofile-correction")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-use -fprofile-correction")
elseif(MSVC)
message(STATUS "PGO: Using MSVC profile data from build directory")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /USEPROFILE")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /USEPROFILE")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /USEPROFILE")
else()
message(WARNING "PGO profile use is not supported with compiler: ${CMAKE_CXX_COMPILER_ID}")
endif()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ archs = ["arm64"]
environment = { CINDERX_ENABLE_PGO = "1", CINDERX_ENABLE_LTO = "1" }

[tool.cibuildwheel.windows]
environment = { CINDERX_ENABLE_LTO = "1" }
environment = { CINDERX_ENABLE_PGO = "1", CINDERX_ENABLE_LTO = "1" }
46 changes: 33 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ def compute_package_version() -> str:


@lru_cache(maxsize=1)
def get_compiler() -> tuple[str, str]:
def get_compiler() -> tuple[str | None, str | None]:
"""
Prefers GCC if a new enough version is installed as this is what the
cibuildwheel environment uses.

Returns:
A tuple of (c_compiler, cxx_compiler) paths.
A tuple of (c_compiler, cxx_compiler) paths, or (None, None) to use
CMake's default compiler detection.
"""
cc_path = os.environ.get("CC")
cxx_path = os.environ.get("CXX")
Expand Down Expand Up @@ -124,6 +125,12 @@ def get_compiler() -> tuple[str, str]:
print(f"Using Clang: {clang_path}, {clangxx_path}")
return (clang_path, clangxx_path)

# On Windows, if no compiler is explicitly found, let CMake use its default
# (which will be MSVC with the appropriate generator)
if platform.system() == "Windows":
print("No explicit compiler found, using CMake default (MSVC on Windows)")
return (None, None)

raise RuntimeError("Cannot find suitable C/C++ compiler (tried gcc and clang)")


Expand Down Expand Up @@ -160,7 +167,9 @@ def print_section(title: str) -> None:
print(separator)

cc, _ = get_compiler()
is_clang = "clang" in cc
is_clang = cc and "clang" in cc
is_windows = platform.system() == "Windows"
is_msvc = is_windows and not is_clang

print_section("PGO STAGE 1/3: Building with profile generation instrumentation")

Expand Down Expand Up @@ -220,7 +229,7 @@ def main():
}
if is_clang:
workload_args["cwd"] = clang_pgo_dir
subprocess.run(workload_cmd, **workload_args)
subprocess.run(workload_cmd, **workload_args)

if is_clang:
print_section("PGO STAGE 2b: Merging profile data")
Expand All @@ -244,6 +253,9 @@ def main():
] + profraw_files
subprocess.run(merge_cmd, check=True)
print(f"Merged profile written to {clang_merged_profile}")
elif is_msvc:
print_section("PGO STAGE 2b: MSVC profile data")
print("MSVC automatically merges .pgc files into a .pgd database")

print_section("PGO STAGE 3/3: Rebuilding with profile-guided optimizations")

Expand All @@ -264,20 +276,22 @@ def main():

cmake_files = os.path.join(self.build_temp, "CMakeFiles")
if os.path.exists(cmake_files):
print(f" Cleaning {cmake_files} (preserving .gcda/.gcno for GCC PGO)")
print(
f" Cleaning {cmake_files} (preserving .gcda/.gcno for GCC PGO, .pgc/.pgd for MSVC PGO)"
)
for root, _dirs, files in os.walk(cmake_files, topdown=False):
for f in files:
file_path = os.path.join(root, f)
# Preserve GCC PGO profiling files
if f.endswith((".gcda", ".gcno")):
# Preserve PGO profiling files.
if f.endswith((".gcda", ".gcno", ".pgc", ".pgd")):
continue
os.remove(file_path)
# Remove empty directories, but only if they don't contain
# preserved files (the topdown=False walk handles this)
# Remove empty directories, but only if they don't contain
# preserved files (the topdown=False walk handles this)
try:
os.rmdir(root) # Only removes if empty
except OSError:
pass # Directory not empty, contains .gcda/.gcno files
pass # Directory not empty, contains preserved files

# Remove all object files and libraries to force rebuild
for rm_root in (self.build_temp, self.build_lib):
Expand All @@ -287,7 +301,9 @@ def main():
if "pgo_data" in root:
continue
for f in files:
if f.endswith((".o", ".so", ".a")):
if f.endswith(
(".o", ".so", ".a", ".obj", ".lib", ".pyd", ".dll", ".exp")
):
file_path = os.path.join(root, f)
print(f" Removing {file_path}")
os.remove(file_path)
Expand Down Expand Up @@ -391,11 +407,15 @@ def _run_cmake(self, extension: CMakeExtension) -> None:
cmake_args += [
f"-DCMAKE_BUILD_TYPE={build_type}",
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={ext_dir}",
f"-DCMAKE_C_COMPILER={cc}",
f"-DCMAKE_CXX_COMPILER={cxx}",
f"-DCMAKE_VERBOSE_MAKEFILE:BOOL={verbose_makefile}",
]

# Only pass explicit compiler if specified (None means use CMake defaults).
if cc:
cmake_args.append(f"-DCMAKE_C_COMPILER={cc}")
if cxx:
cmake_args.append(f"-DCMAKE_CXX_COMPILER={cxx}")

if self.cinderx_pgo_stage == PgoStage.GENERATE:
cmake_args.append("-DENABLE_PGO_GENERATE=ON")
cmake_args.append("-DENABLE_PGO_USE=OFF")
Expand Down
Loading