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
118 changes: 118 additions & 0 deletions server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,39 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

option(DFLASH27B_COVERAGE
"Instrument C and C++ host code for LLVM source-based coverage" OFF)
if(DFLASH27B_COVERAGE)
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang" OR
NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR
"DFLASH27B_COVERAGE requires Clang for both C and C++ "
"(configure with -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++)")
endif()

file(GLOB _dflash_llvm_tool_dirs
"/usr/lib/llvm-*/bin"
"/usr/local/opt/llvm*/bin")
find_program(DFLASH27B_LLVM_COV_EXECUTABLE
NAMES llvm-cov
HINTS ${_dflash_llvm_tool_dirs}
REQUIRED)
find_program(DFLASH27B_LLVM_PROFDATA_EXECUTABLE
NAMES llvm-profdata
HINTS ${_dflash_llvm_tool_dirs}
REQUIRED)
unset(_dflash_llvm_tool_dirs)

# CUDA/HIP device compilation is intentionally excluded: LLVM's
# source-based coverage records host-side C and C++ execution only.
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-fprofile-instr-generate>
$<$<COMPILE_LANGUAGE:C,CXX>:-fcoverage-mapping>)
add_link_options(
$<$<LINK_LANGUAGE:C>:-fprofile-instr-generate>
$<$<LINK_LANGUAGE:CXX>:-fprofile-instr-generate>)
endif()

# MSVC: treat source files as UTF-8 (the codebase contains UTF-8 string
# literals such as tags). Without this, MSVC defaults to the system
# code page and emits C4819 warnings or garbles multi-byte literals.
Expand Down Expand Up @@ -1881,6 +1914,91 @@ if(DFLASH27B_TESTS)
COMMENT "No unit-test binaries are enabled in this configuration"
)
endif()

if(DFLASH27B_COVERAGE)
set(_coverage_report_dir "${CMAKE_CURRENT_BINARY_DIR}/coverage")
set(_coverage_profile_dir "${_coverage_report_dir}/raw")
set(_coverage_object_list "${CMAKE_CURRENT_BINARY_DIR}/coverage_objects.txt")
set(_coverage_objects)
set(_coverage_deps)
set(_coverage_ctest_exclude)
foreach(_coverage_target IN LISTS _check_deps)
if(TARGET ${_coverage_target})
get_target_property(_coverage_sources ${_coverage_target} SOURCES)
set(_coverage_has_device_source FALSE)
foreach(_coverage_source IN LISTS _coverage_sources)
get_source_file_property(_coverage_source_language
"${_coverage_source}" LANGUAGE)
if(_coverage_source MATCHES "\\.cu$" OR
_coverage_source_language STREQUAL "CUDA" OR
_coverage_source_language STREQUAL "HIP")
set(_coverage_has_device_source TRUE)
break()
endif()
endforeach()
unset(_coverage_source)
unset(_coverage_source_language)

if(_coverage_has_device_source)
# Host-side LLVM coverage cannot instrument CUDA sources.
# Skip their standalone CTest entry and do not build them.
list(APPEND _coverage_ctest_exclude "^${_coverage_target}$")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a device-backed test has a renamed or discovered CTest name, this regex does not exclude it. CTest then runs an executable that coverage deliberately did not build; exclude the actual names, including ${target}.* discovered tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/CMakeLists.txt, line 1945:

<comment>When a device-backed test has a renamed or discovered CTest name, this regex does not exclude it. CTest then runs an executable that `coverage` deliberately did not build; exclude the actual names, including `${target}.*` discovered tests.</comment>

<file context>
@@ -1881,6 +1914,91 @@ if(DFLASH27B_TESTS)
+                if(_coverage_has_device_source)
+                    # Host-side LLVM coverage cannot instrument CUDA sources.
+                    # Skip their standalone CTest entry and do not build them.
+                    list(APPEND _coverage_ctest_exclude "^${_coverage_target}$")
+                else()
+                    list(APPEND _coverage_deps ${_coverage_target})
</file context>

else()
list(APPEND _coverage_deps ${_coverage_target})
list(APPEND _coverage_objects "$<TARGET_FILE:${_coverage_target}>")
endif()
unset(_coverage_has_device_source)
unset(_coverage_sources)
endif()
endforeach()

if(NOT _coverage_objects)
message(FATAL_ERROR
"DFLASH27B_COVERAGE requires at least one registered test target")
endif()

file(GENERATE
OUTPUT "${_coverage_object_list}"
CONTENT "$<JOIN:${_coverage_objects},\n>\n")

if(_coverage_ctest_exclude)
list(JOIN _coverage_ctest_exclude "|" _coverage_ctest_exclude_regex)
else()
set(_coverage_ctest_exclude_regex "")
endif()

# This target intentionally depends on the test binaries rather than
# `check`: CTest must run with LLVM_PROFILE_FILE set to capture data.
add_custom_target(coverage
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_coverage_report_dir}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${_coverage_profile_dir}"
COMMAND ${CMAKE_COMMAND}
"-DCTEST_EXECUTABLE=${CMAKE_CTEST_COMMAND}"
"-DBUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}"
"-DCTEST_EXCLUDE_REGEX=${_coverage_ctest_exclude_regex}"
"-DLLVM_COV=${DFLASH27B_LLVM_COV_EXECUTABLE}"
"-DLLVM_PROFDATA=${DFLASH27B_LLVM_PROFDATA_EXECUTABLE}"
"-DPROFILE_DIR=${_coverage_profile_dir}"
"-DPROFILE_DATA=${_coverage_report_dir}/coverage.profdata"
"-DOBJECT_LIST_FILE=${_coverage_object_list}"
"-DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/src"
"-DHTML_DIR=${_coverage_report_dir}/html"
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RunCoverageTests.cmake"
DEPENDS ${_coverage_deps}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: On a fresh build, coverage does not build every executable that its CTest invocation runs. Add every executable-backed CTest target to the coverage dependencies, or create the coverage target after the complete test target list is known.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/CMakeLists.txt, line 1987:

<comment>On a fresh build, `coverage` does not build every executable that its CTest invocation runs. Add every executable-backed CTest target to the coverage dependencies, or create the coverage target after the complete test target list is known.</comment>

<file context>
@@ -1881,6 +1914,91 @@ if(DFLASH27B_TESTS)
+                "-DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/src"
+                "-DHTML_DIR=${_coverage_report_dir}/html"
+                -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RunCoverageTests.cmake"
+            DEPENDS ${_coverage_deps}
+            USES_TERMINAL
+            VERBATIM
</file context>

USES_TERMINAL
VERBATIM
COMMENT "Running CTest and generating LLVM coverage reports")

unset(_coverage_report_dir)
unset(_coverage_profile_dir)
unset(_coverage_object_list)
unset(_coverage_objects)
unset(_coverage_deps)
unset(_coverage_ctest_exclude)
unset(_coverage_ctest_exclude_regex)
unset(_coverage_target)
endif()

unset(_check_deps)
unset(_raw_unit_test_targets)

Expand Down
69 changes: 69 additions & 0 deletions server/cmake/GenerateCoverageReport.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
foreach(_coverage_required_var IN ITEMS
LLVM_COV
LLVM_PROFDATA
PROFILE_DIR
PROFILE_DATA
OBJECT_LIST_FILE
SOURCE_DIR
HTML_DIR)
if(NOT DEFINED ${_coverage_required_var} OR
"${${_coverage_required_var}}" STREQUAL "")
message(FATAL_ERROR "${_coverage_required_var} is required")
endif()
endforeach()
unset(_coverage_required_var)

file(GLOB _coverage_raw_profiles "${PROFILE_DIR}/*.profraw")
if(NOT _coverage_raw_profiles)
message(FATAL_ERROR
"CTest produced no LLVM profiles in ${PROFILE_DIR}")
endif()

execute_process(
COMMAND "${LLVM_PROFDATA}" merge -sparse ${_coverage_raw_profiles}
-o "${PROFILE_DATA}"
RESULT_VARIABLE _coverage_merge_result
)
if(NOT _coverage_merge_result EQUAL 0)
message(FATAL_ERROR "llvm-profdata failed with exit code ${_coverage_merge_result}")
endif()

file(STRINGS "${OBJECT_LIST_FILE}" _coverage_objects)
if(NOT _coverage_objects)
message(FATAL_ERROR "No coverage objects were generated in ${OBJECT_LIST_FILE}")
endif()

list(GET _coverage_objects 0 _coverage_main_object)
list(REMOVE_AT _coverage_objects 0)
set(_coverage_object_args)
foreach(_coverage_object IN LISTS _coverage_objects)
list(APPEND _coverage_object_args -object "${_coverage_object}")
endforeach()
unset(_coverage_object)

execute_process(
COMMAND "${LLVM_COV}" report "${_coverage_main_object}"
${_coverage_object_args}
"-instr-profile=${PROFILE_DATA}"
"${SOURCE_DIR}"
RESULT_VARIABLE _coverage_report_result
)
if(NOT _coverage_report_result EQUAL 0)
message(FATAL_ERROR "llvm-cov report failed with exit code ${_coverage_report_result}")
endif()

execute_process(
COMMAND "${LLVM_COV}" show "${_coverage_main_object}"
${_coverage_object_args}
"-instr-profile=${PROFILE_DATA}"
-format=html
"-output-dir=${HTML_DIR}"
"${SOURCE_DIR}"
RESULT_VARIABLE _coverage_html_result
)
if(NOT _coverage_html_result EQUAL 0)
message(FATAL_ERROR "llvm-cov show failed with exit code ${_coverage_html_result}")
endif()

message(STATUS "Coverage summary: ${PROFILE_DATA}")
message(STATUS "Coverage HTML report: ${HTML_DIR}/index.html")
26 changes: 26 additions & 0 deletions server/cmake/RunCoverageTests.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
foreach(_coverage_required_var IN ITEMS
CTEST_EXECUTABLE
BUILD_DIR
PROFILE_DIR
CTEST_EXCLUDE_REGEX)
if(NOT DEFINED ${_coverage_required_var})
message(FATAL_ERROR "${_coverage_required_var} is required")
endif()
endforeach()
unset(_coverage_required_var)

set(ENV{LLVM_PROFILE_FILE} "${PROFILE_DIR}/%m_%p.profraw")
set(_coverage_ctest_args --test-dir "${BUILD_DIR}" --output-on-failure)
if(NOT "${CTEST_EXCLUDE_REGEX}" STREQUAL "")
list(APPEND _coverage_ctest_args -E "${CTEST_EXCLUDE_REGEX}")
endif()
execute_process(
COMMAND "${CTEST_EXECUTABLE}" ${_coverage_ctest_args}
RESULT_VARIABLE _coverage_ctest_result)
unset(_coverage_ctest_args)

include("${CMAKE_CURRENT_LIST_DIR}/GenerateCoverageReport.cmake")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When ctest fails in a way that produces no profiles, GenerateCoverageReport.cmake aborts first with "CTest produced no LLVM profiles", so the real ctest exit code and this script's "CTest failed" message are never surfaced. Check _coverage_ctest_result immediately after execute_process and only run the report generation when ctest succeeded, or at least report the ctest failure before including the generator.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/cmake/RunCoverageTests.cmake, line 22:

<comment>When ctest fails in a way that produces no profiles, GenerateCoverageReport.cmake aborts first with "CTest produced no LLVM profiles", so the real ctest exit code and this script's "CTest failed" message are never surfaced. Check `_coverage_ctest_result` immediately after execute_process and only run the report generation when ctest succeeded, or at least report the ctest failure before including the generator.</comment>

<file context>
@@ -0,0 +1,26 @@
+    RESULT_VARIABLE _coverage_ctest_result)
+unset(_coverage_ctest_args)
+
+include("${CMAKE_CURRENT_LIST_DIR}/GenerateCoverageReport.cmake")
+
+if(NOT _coverage_ctest_result EQUAL 0)
</file context>


if(NOT _coverage_ctest_result EQUAL 0)
message(FATAL_ERROR "CTest failed with exit code ${_coverage_ctest_result}")
endif()
50 changes: 50 additions & 0 deletions server/docs/COVERAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Host C/C++ Coverage

The `coverage` target produces an LLVM source-based coverage report for host
C and C++ code. CUDA and HIP source files are intentionally excluded.

## Prerequisites

- Clang and matching `llvm-cov` and `llvm-profdata` executables
- The usual backend build prerequisites (CUDA or HIP)

On Debian or Ubuntu, install the LLVM toolchain package matching the Clang
version used for the build.

## Generate the report

Configure a separate Debug build so coverage instrumentation does not affect
the normal build:

```bash
cd server
cmake -S . -B build-coverage \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DDFLASH27B_COVERAGE=ON \
-DDFLASH27B_TESTS=ON
cmake --build build-coverage --target coverage -j
```

For CUDA builds where `nvcc` is not on `PATH`, set `CUDACXX` before
configuring:

```bash
CUDACXX=/usr/local/cuda/bin/nvcc cmake -S . -B build-coverage ...
```

The target runs CTest with `LLVM_PROFILE_FILE` configured, merges the emitted
profiles, prints a text summary, and writes the HTML report to:

```text
server/build-coverage/coverage/html/index.html
```

The merged profile is `server/build-coverage/coverage/coverage.profdata`.

## Test failures

The HTML report is generated even when CTest fails, then the `coverage` target
returns CTest's failure status. Review the test failures before treating the
report as a passing coverage run.
6 changes: 6 additions & 0 deletions server/src/deepseek4/deepseek4_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3427,7 +3427,13 @@ static bool ds4_cpu_has_f16c() {
static int supported = -1;
if (supported < 0) {
__builtin_cpu_init();
#if defined(__clang__) && __clang_major__ < 15
// Clang 14 does not accept "f16c" in __builtin_cpu_supports().
// AVX2-capable x86 CPUs also provide F16C.
supported = __builtin_cpu_supports("avx2") ? 1 : 0;
#else
supported = (__builtin_cpu_supports("avx2") && __builtin_cpu_supports("f16c")) ? 1 : 0;
#endif
}
return supported == 1;
}
Expand Down
2 changes: 0 additions & 2 deletions server/test/test_feature_gate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,6 @@ void test_feature_warnings_report_inert_decode_tunables() {
vw.verify_width = 8;
CHECK(!warns_about(warn_result(vw, "laguna"), "--verify-width"));
CHECK(warns_about(warn_result(vw, "qwen35"), "--verify-width"));

BackendArgs db;
db.model_path = "/nonexistent/model.gguf";
db.draft_path = "/nonexistent/draft.gguf";
Expand Down Expand Up @@ -677,7 +676,6 @@ void test_model_capability_tables() {
CHECK(arch_supports_paged_attention("qwen35", false));
CHECK(!arch_supports_paged_attention("qwen35", true));
CHECK(!arch_supports_paged_attention("qwen35moe", false));

CHECK(arch_supports_draft_block_size("qwen35", false));
CHECK(!arch_supports_draft_block_size("qwen35", true));
CHECK(!arch_supports_draft_block_size("qwen35moe", false));
Expand Down
Loading