Skip to content
Draft
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
17 changes: 17 additions & 0 deletions .github/workflows/tests-ubuntu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,23 @@ jobs:
-B build \
-S /src/install_test
cmake --build build
rm -rf build

if [ 'xcuda' = 'x${{matrix.backend.name}}' ]
then
export CXX=$Kokkos_ROOT/bin/nvcc_wrapper
fi

cmake \
-D CMAKE_EXPORT_COMPILE_COMMANDS=ON \
-B build \
-S /src/install_test/check_macros
python3 \
/src/install_test/check_macros/check_macros.py \
--with-src /src/install_test/check_macros/with_my_lib.cpp \
--without-src /src/install_test/check_macros/without_my_lib.cpp \
--reference /src/install_test/check_macros/whitelist_macros.json \
-p ./build
EOF

docker run \
Expand Down
27 changes: 13 additions & 14 deletions include/ddc/detail/macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

#include <Kokkos_Core.hpp>

#if defined(__NVCC__)
#define DDC_MAKE_PRAGMA(X) _Pragma(#X)

# define DDC_MAKE_PRAGMA(X) _Pragma(#X)
#if defined(__NVCC__)

# if defined __NVCC_DIAG_PRAGMA_SUPPORT__
# if defined(__NVCC_DIAG_PRAGMA_SUPPORT__)
# define DDC_NV_DIAGNOSTIC_PUSH DDC_MAKE_PRAGMA(nv_diagnostic push)
# define DDC_NV_DIAGNOSTIC_POP DDC_MAKE_PRAGMA(nv_diagnostic pop)
# define DDC_NV_DIAG_SUPPRESS(X) DDC_MAKE_PRAGMA(nv_diag_suppress X)
Expand All @@ -20,22 +20,21 @@
# define DDC_NV_DIAG_SUPPRESS(X) DDC_MAKE_PRAGMA(diag_suppress X)
# endif

# define DDC_IF_NVCC_THEN_PUSH(X) DDC_NV_DIAGNOSTIC_PUSH
# define DDC_IF_NVCC_THEN_SUPPRESS(X) DDC_NV_DIAG_SUPPRESS(X)
# define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X) \
DDC_NV_DIAGNOSTIC_PUSH \
DDC_NV_DIAG_SUPPRESS(X)
# define DDC_IF_NVCC_THEN_POP DDC_NV_DIAGNOSTIC_POP

#else

# define DDC_IF_NVCC_THEN_PUSH(X)
# define DDC_IF_NVCC_THEN_SUPPRESS(X)
# define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X)
# define DDC_IF_NVCC_THEN_POP
# define DDC_NV_DIAGNOSTIC_PUSH
# define DDC_NV_DIAGNOSTIC_POP
# define DDC_NV_DIAG_SUPPRESS(X)

#endif

#define DDC_IF_NVCC_THEN_PUSH(X) DDC_NV_DIAGNOSTIC_PUSH
#define DDC_IF_NVCC_THEN_SUPPRESS(X) DDC_NV_DIAG_SUPPRESS(X)
#define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X) \
DDC_NV_DIAGNOSTIC_PUSH \
DDC_NV_DIAG_SUPPRESS(X)
#define DDC_IF_NVCC_THEN_POP DDC_NV_DIAGNOSTIC_POP

#if defined(__HIP_DEVICE_COMPILE__)
# define DDC_CURRENT_KOKKOS_SPACE Kokkos::HIPSpace
#elif defined(__CUDA_ARCH__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
// clang-format off
// NOLINTBEGIN(*)

#ifndef KOKKOSBATCHED_GBTRS_HPP_
#define KOKKOSBATCHED_GBTRS_HPP_
#pragma once

#include <KokkosBatched_Util.hpp>

Expand Down Expand Up @@ -53,7 +52,5 @@ struct SerialGbtrs {

#include "KokkosBatched_Gbtrs_Serial_Impl.hpp"

#endif // KOKKOSBATCHED_GBTRS_HPP_

// NOLINTEND(*)
// clang-format on
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
// clang-format off
// NOLINTBEGIN(*)

#ifndef KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_
#define KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_
#pragma once

#include <Kokkos_Swap.hpp>
#include <KokkosBatched_Util.hpp>
Expand Down Expand Up @@ -164,7 +163,5 @@ struct SerialGbtrs<Trans::Transpose, Algo::Level3::Unblocked> {
};
} // namespace KokkosBatched

#endif // KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_

// NOLINTEND(*)
// clang-format on
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
// clang-format off
// NOLINTBEGIN(*)

#ifndef KOKKOSBATCHED_GETRS_HPP_
#define KOKKOSBATCHED_GETRS_HPP_
#pragma once

#include <KokkosBatched_Util.hpp>

Expand Down Expand Up @@ -42,7 +41,5 @@ struct SerialGetrs {

#include "KokkosBatched_Getrs_Serial_Impl.hpp"

#endif // KOKKOSBATCHED_GETRS_HPP_

// NOLINTEND(*)
// clang-format on
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
// clang-format off
// NOLINTBEGIN(*)

#ifndef KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_
#define KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_
#pragma once

#include <KokkosBatched_Util.hpp>
#include <KokkosBatched_Trsm_Decl.hpp>
Expand Down Expand Up @@ -93,7 +92,5 @@ struct SerialGetrs<Trans::Transpose, Algo::Level3::Unblocked> {
};
} // namespace KokkosBatched

#endif // KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_

// NOLINTEND(*)
// clang-format on
11 changes: 11 additions & 0 deletions install_test/check_macros/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (C) The DDC development team, see COPYRIGHT.md file
#
# SPDX-License-Identifier: MIT

cmake_minimum_required(VERSION 3.22)
project(test-ddc-macros LANGUAGES CXX)

find_package(DDC 0.8 REQUIRED COMPONENTS fft pdi splines)

add_library(check-macros with_my_lib.cpp without_my_lib.cpp)
target_link_libraries(check-macros PRIVATE DDC::core DDC::fft DDC::pdi DDC::splines)
221 changes: 221 additions & 0 deletions install_test/check_macros/check_macros.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
#!/usr/bin/env python3

# Copyright (C) The DDC development team, see COPYRIGHT.md file
#
# SPDX-License-Identifier: MIT

"""
Compare the difference in preprocessor macros between two source files
(one with a library and one without), and check if that diff matches a reference.

Uses compile_commands.json to simulate compilation and extract preprocessor macros.
The reference file must be a JSON file containing a list of expected #define macros.
"""

import json
import re
import subprocess
import shlex
from pathlib import Path
import argparse
import sys


def extract_command(entry):
"""
Modify compile command to only run preprocessor: remove -c/-o, add -dM -E.

Args:
entry (dict): An entry from compile_commands.json

Returns:
tuple[list[str], Path]: The modified command and working directory
"""
parts = shlex.split(entry["command"])
new_parts = []
skip_next = False
for part in parts:
if skip_next:
skip_next = False
continue
if part == "-c":
continue
if part == "-o":
skip_next = True
continue
new_parts.append(part)
new_parts += ["-dM", "-E", entry["file"]]
return new_parts, Path(entry["directory"])


def run_preprocessor(command, cwd):
"""
Run the preprocessor and return unique macro lines.

Args:
command (list[str]): Compiler command
cwd (Path): Working directory

Returns:
set[str]: Set of macro lines
"""
result = subprocess.run(
command, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
)
if result.returncode != 0:
raise RuntimeError(f"Command failed:\n{' '.join(command)}\n{result.stderr}")
return set(result.stdout.splitlines())


def find_entry(commands, source_path):
"""
Find compile command for the given source file.

Args:
commands (list[dict]): compile_commands.json content
source_path (Path): Path to source file

Returns:
dict: Matching compile command entry
"""
for entry in commands:
if Path(entry["file"]) == source_path:
return entry
raise ValueError(f"Source file {source_path} not found in compile_commands.json")


def prepare_commands(args):
"""
Load compilation database and extract preprocessor commands for both source files.

Args:
args (argparse.Namespace): Parsed command-line arguments

Returns:
tuple[set[str], set[str]]: Macros with and without the library
"""
commands = json.loads((args.p / "compile_commands.json").read_text())

entry_with = find_entry(commands, args.with_src.resolve())
entry_without = find_entry(commands, args.without_src.resolve())

cmd_with, dir_with = extract_command(entry_with)
cmd_without, dir_without = extract_command(entry_without)

print(f"Running preprocessor for: {args.with_src.name}")
macros_with = run_preprocessor(cmd_with, dir_with)

print(f"Running preprocessor for: {args.without_src.name}")
macros_without = run_preprocessor(cmd_without, dir_without)

macros_with_diff = macros_with - macros_without
macros_without_diff = macros_without - macros_with

# to be removed as soon as possible
whitelist_macros = {
"#define KOKKOS_IMPL_PUBLIC_INCLUDE ",
"#define KOKKOS_IMPL_PUBLIC_INCLUDE_PROFILING_SCOPEDREGION ",
}

if macros_without_diff - whitelist_macros:
print(macros_without_diff)
raise ValueError("Set should be empty")

return macros_with_diff, macros_without_diff


def extract_macro_diff(macros_with, macros_without):
"""
Compute the difference in #define macros between with/without source.

Args:
macros_with (set[str]): Macros when library is included
macros_without (set[str]): Macros without library

Returns:
list[str]: Sorted list of macro differences
"""
macro_pattern = r"#define\s+([A-Za-z_][A-Za-z0-9_]*)(\s+.*)?"
define_with = set()
for line in macros_with:
match = re.match(macro_pattern, line.strip())
if match:
define_with.add(str(match.group(1)))
else:
raise ValueError(f"Unhandled macro {line}")
define_without = set()
for line in macros_without:
match = re.match(macro_pattern, line.strip())
if match:
define_without.add(str(match.group(1)))
else:
raise ValueError(f"Unhandled macro {line}")
return sorted(define_with - define_without)


def compare_macro_diff(actual_diff, reference_file):
"""
Compare computed macro diff to expected one from JSON reference file.

Args:
actual_diff (list[str]): Macros computed by diff
reference_file (Path): Path to JSON reference

Returns:
bool: True if matches, False otherwise
"""
try:
ref_macros = set(json.loads(reference_file.read_text()))
except Exception as exception:
raise ValueError(f"Error reading reference JSON file: {exception}") from exception

added = sorted([m for m in actual_diff if m not in ref_macros])
removed = sorted([m for m in ref_macros if m not in actual_diff])

has_error = False

if added:
print("\n--- Unexpected Macros (in diff, not in reference) ---")
print("\n".join(added))
has_error = True

if removed:
print("\n--- Missing Macros (in reference, not in diff) ---")
print("\n".join(removed))
has_error = True

if not has_error:
print("\n✅ Macro diff matches the reference exactly.")
return not has_error


def main():
"""Main entry point: run preprocessor and compare macro diff with reference."""
parser = argparse.ArgumentParser(
description="Compare macro diff against a JSON reference file."
)
parser.add_argument("--with-src", required=True, type=Path, help="Source file with the library")
parser.add_argument(
"--without-src", required=True, type=Path, help="Source file without the library"
)
parser.add_argument(
"--reference", required=True, type=Path, help="Reference JSON file with expected macro diff"
)
parser.add_argument(
"-p",
default="build",
type=Path,
help="Path to build directory",
)

args = parser.parse_args()

macros_with, macros_without = prepare_commands(args)
actual_diff = extract_macro_diff(macros_with, macros_without)
success = compare_macro_diff(actual_diff, args.reference)

sys.exit(0 if success else 1)


if __name__ == "__main__":
main()
14 changes: 14 additions & 0 deletions install_test/check_macros/whitelist_macros.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
"DDC_BUILD_DEPRECATED_CODE",
"DDC_BUILD_DOUBLE_PRECISION",
"DDC_CURRENT_KOKKOS_SPACE",
"DDC_IF_NVCC_THEN_POP",
"DDC_IF_NVCC_THEN_PUSH",
"DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS",
"DDC_IF_NVCC_THEN_SUPPRESS",
"DDC_MAKE_PRAGMA",
"DDC_MDSPAN_ACCESS_OP",
"DDC_NV_DIAGNOSTIC_POP",
"DDC_NV_DIAGNOSTIC_PUSH",
"DDC_NV_DIAG_SUPPRESS"
]
3 changes: 3 additions & 0 deletions install_test/check_macros/whitelist_macros.json.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright (C) The DDC development team, see COPYRIGHT.md file

SPDX-License-Identifier: MIT
Loading