From 9ffd07dc526beb343404b5dd744b03f98533532a Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 14:35:22 -0400 Subject: [PATCH 01/14] Remove the simd_op_check app This app built standalone driver binaries by linking the per-op object files and headers that the simd_op_check correctness test emitted into its output directory. That test now JITs each case via compile_to_callable and emits only assembly, so the objects and headers the driver links against are no longer produced and the app can no longer be built. Fixes #8741 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 1 - apps/simd_op_check/Makefile | 45 ------------ apps/simd_op_check/driver.cpp | 131 ---------------------------------- 3 files changed, 177 deletions(-) delete mode 100644 apps/simd_op_check/Makefile delete mode 100644 apps/simd_op_check/driver.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 6beaba0ba3e9..30b378852311 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -75,7 +75,6 @@ add_app(nl_means) add_app(onnx) add_app(resize) # add_app(resnet_50) # TODO(#5374): missing CMake build -# add_app(simd_op_check) # TODO(#5374): missing CMake build add_app(stencil_chain) add_app(unsharp) add_app(wavelet) diff --git a/apps/simd_op_check/Makefile b/apps/simd_op_check/Makefile deleted file mode 100644 index 6e2f51725fd2..000000000000 --- a/apps/simd_op_check/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -include ../support/Makefile.inc - -CXX-hexagon-32-noos-hvx_128 ?= $(HL_HEXAGON_TOOLS)/bin/hexagon-clang++ - -CXXFLAGS-hexagon-32-noos-hvx_128 ?= -mhvx -mhvx-length=128B -G0 - -LDFLAGS-hexagon-32-noos-hvx_128 ?= -L../../src/runtime/hexagon_remote/bin/v60/ -lsim_qurt - -all: \ - $(BIN)/driver-host \ - $(BIN)/driver-arm-64-android \ - $(BIN)/driver-arm-32-android \ - $(BIN)/driver-hexagon-32-noos-hvx_128 \ - -hvx_128: $(BIN)/driver-hexagon-32-noos-hvx_128 - -arm_32: $(BIN)/driver-arm-32-android -arm_64: $(BIN)/driver-arm-64-android - -host: $(BIN)/driver-host - -$(BIN)/hexagon-32-noos-%/filters.h: - @mkdir -p $(@D) - make -C ../../ bin/correctness_simd_op_check_hvx - cd $(BIN)/hexagon-32-noos-$* && HL_TARGET=hexagon-32-noos-$* LD_LIBRARY_PATH=../../../../bin:$$LD_LIBRARY_PATH ../../../../bin/correctness_simd_op_check_hvx - cat $(BIN)/hexagon-32-noos-$*/test_*.h > $(BIN)/hexagon-32-noos-$*/filter_headers.h - echo "filter filters[] = {" > $(BIN)/hexagon-32-noos-$*/filters.h - cd $(BIN)/hexagon-32-noos-$*; for f in test_*.h; do n=$${f/.h/}; echo '{"'$${n}'", &'$${n}'},'; done >> filters.h - echo '{NULL, NULL}};' >> $(BIN)/hexagon-32-noos-$*/filters.h - -$(BIN)/%/filters.h: - @mkdir -p $(@D) - make -C ../../ bin/correctness_simd_op_check - cd $(BIN)/$* && HL_TARGET=$* LD_LIBRARY_PATH=../../../../bin:$$LD_LIBRARY_PATH ../../../../bin/correctness_simd_op_check - cat $(BIN)/$*/test_*.h > $(BIN)/$*/filter_headers.h - echo "filter filters[] = {" > $(BIN)/$*/filters.h - cd $(BIN)/$*; for f in test_*.h; do n=$${f/.h/}; echo '{"'$${n}'", &'$${n}'},'; done >> filters.h - echo '{NULL, NULL}};' >> $(BIN)/$*/filters.h - -$(BIN)/driver-%: driver.cpp $(BIN)/%/filters.h - @mkdir -p $(@D) - $(CXX-$*) $(CXXFLAGS-$*) -I ../../include $(OPTIMIZE) -I $(BIN)/$* driver.cpp $(BIN)/$*/test_*.o $(BIN)/$*/simd_op_check_runtime.o -o $@ $(LDFLAGS-$) - -clean: - rm -rf $(BIN) diff --git a/apps/simd_op_check/driver.cpp b/apps/simd_op_check/driver.cpp deleted file mode 100644 index fc29c0488c4d..000000000000 --- a/apps/simd_op_check/driver.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "filter_headers.h" -#include -#include -#include - -#ifndef __APPLE__ -extern "C" void *memalign(size_t alignment, size_t size); -#endif - -struct filter { - const char *name; - int (*fn)(halide_buffer_t *, // float32 - halide_buffer_t *, // float64 - halide_buffer_t *, // float16 - halide_buffer_t *, // bfloat16 - halide_buffer_t *, // int8 - halide_buffer_t *, // uint8 - halide_buffer_t *, // int16 - halide_buffer_t *, // uint16 - halide_buffer_t *, // int32 - halide_buffer_t *, // uint32 - halide_buffer_t *, // int64 - halide_buffer_t *, // uint64 - halide_buffer_t *); // output -}; - -template -T rand_value() { - return (T)(rand() * 0.125) - 100; -} - -// Even on android, we want errors to stdout -extern "C" void halide_print(void *, const char *msg) { - printf("%s\n", msg); -} - -template -halide_buffer_t make_buffer(int w, int h, halide_type_t halide_type) { - T *mem = NULL; -#ifdef __APPLE__ - // memalign() isn't present on OSX, but posix_memalign is - int result = posix_memalign((void **)&mem, 128, w * h * sizeof(T)); - if (result != 0 || mem == NULL) { - exit(1); - } -#else - mem = (T *)memalign(128, w * h * sizeof(T)); - if (mem == NULL) { - exit(1); - } -#endif - - halide_buffer_t buf = {0}; - buf.dim = (halide_dimension_t *)malloc(sizeof(halide_dimension_t) * 2); - buf.host = (uint8_t *)mem; - buf.dim[0].extent = w; - buf.dim[1].extent = h; - buf.type = halide_type; - buf.dim[0].stride = 1; - buf.dim[1].stride = w; - buf.dim[0].min = -128; - buf.dim[1].min = 0; - - for (int i = 0; i < w * h; i++) { - mem[i] = rand_value(); - } - - return buf; -} - -#include "filters.h" - -int main(int argc, char **argv) { - const int W = 1024, H = 128; - bool error = false; - // Make some input buffers - halide_buffer_t bufs[] = { - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_t(halide_type_float, 16)), - make_buffer(W, H, halide_type_t(halide_type_bfloat, 16)), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of()), - make_buffer(W, H, halide_type_of())}; - - halide_buffer_t out = make_buffer(1, 1, halide_type_of()); - - double *out_value = (double *)(out.host); - - for (int i = 0; filters[i].fn; i++) { - filter f = filters[i]; - printf("Testing %s\n", f.name); - f.fn(bufs + 0, - bufs + 1, - bufs + 2, - bufs + 3, - bufs + 4, - bufs + 5, - bufs + 6, - bufs + 7, - bufs + 8, - bufs + 9, - bufs + 10, - bufs + 11, - &out); - if (*out_value) { - printf("Error: %f\n", *out_value); - error = true; - } - } - - for (int i = 0; i < sizeof(bufs) / sizeof(halide_buffer_t); i++) { - free(bufs[i].dim); - free(bufs[i].host); - } - free(out.dim); - free(out.host); - - if (!error) { - printf("Success!\n"); - return 0; - } else { - printf("Error occurred\n"); - return -1; - } -} From 9c4562480d429bbee101ce18dc9db771d256f3c6 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 14:35:45 -0400 Subject: [PATCH 02/14] Add a CMake build for the resnet_50 app Fixes #8740 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 2 +- apps/resnet_50/CMakeLists.txt | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 apps/resnet_50/CMakeLists.txt diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 30b378852311..d592a5136211 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -74,7 +74,7 @@ add_app(nl_means) # add_app(nn_ops) # TODO(#5374): missing CMake build add_app(onnx) add_app(resize) -# add_app(resnet_50) # TODO(#5374): missing CMake build +add_app(resnet_50) add_app(stencil_chain) add_app(unsharp) add_app(wavelet) diff --git a/apps/resnet_50/CMakeLists.txt b/apps/resnet_50/CMakeLists.txt new file mode 100644 index 000000000000..3c4fcaca5a96 --- /dev/null +++ b/apps/resnet_50/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.28) +project(resnet_50) + +enable_testing() + +# Set up language settings +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +# Find Halide +find_package(Halide REQUIRED) + +# Generator +add_halide_generator(resnet50.generator SOURCES Resnet50Generator.cpp) + +# Filter +add_halide_library(resnet50 FROM resnet50.generator) + +# Main executable +add_executable(resnet_50_process process.cpp) +target_link_libraries(resnet_50_process PRIVATE Halide::ImageIO Halide::Tools resnet50) + +# NOTE: Like the Makefile, we only test that resnet_50 builds. Actually running +# process requires the PyTorch/torchvision weights produced by load_weights.py, +# which we don't want to pull in as a build/test dependency here. From ef336d430b7ffe565c4654a6bff5c8842091de95 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 14:36:06 -0400 Subject: [PATCH 03/14] Add a CMake build for the auto_viz app Also cast the resampling kernel's tap count to int before using it as an RDom extent, which must be integral. Fixes #8732 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 2 +- apps/auto_viz/CMakeLists.txt | 53 +++++++++++++++++++++++ apps/auto_viz/auto_viz_demo_generator.cpp | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 apps/auto_viz/CMakeLists.txt diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index d592a5136211..886192b925b2 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -41,7 +41,7 @@ if (Halide_TARGET MATCHES "wasm") endif () # keep-sorted start case=no ignore_prefixes=# -# add_app(auto_viz) # TODO(#5374): missing CMake build +add_app(auto_viz) add_app(bgu) add_app(bilateral_grid) add_app(blur) diff --git a/apps/auto_viz/CMakeLists.txt b/apps/auto_viz/CMakeLists.txt new file mode 100644 index 000000000000..1e647a5af288 --- /dev/null +++ b/apps/auto_viz/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.28) +project(auto_viz) + +enable_testing() + +# Set up language settings +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +# Find Halide +find_package(Halide REQUIRED) + +# Generator +add_halide_generator(auto_viz_demo.generator SOURCES auto_viz_demo_generator.cpp) + +# Filters: one library per (schedule, direction) pair, all built with tracing +# enabled so the pipelines can be visualized with HalideTraceViz. +set(variants "") +foreach (schedule IN ITEMS naive lessnaive complex) + add_halide_library( + auto_viz_demo_${schedule}_up + FROM auto_viz_demo.generator + GENERATOR auto_viz_demo + FEATURES trace_all + PARAMS schedule_type=${schedule} upsample=true + ) + add_halide_library( + auto_viz_demo_${schedule}_down + FROM auto_viz_demo.generator + GENERATOR auto_viz_demo + FEATURES trace_all + PARAMS schedule_type=${schedule} upsample=false + ) + list(APPEND variants auto_viz_demo_${schedule}_up auto_viz_demo_${schedule}_down) +endforeach () + +# Main executable +add_executable(auto_viz_demo auto_viz_demo.cpp) +target_link_libraries(auto_viz_demo PRIVATE Halide::ImageIO ${variants}) + +# Test that the app actually works! +set(IMAGE ${CMAKE_CURRENT_LIST_DIR}/../images/rgb_small.png) +if (EXISTS ${IMAGE}) + configure_file(${IMAGE} rgb_small.png COPYONLY) + add_test(NAME auto_viz_demo COMMAND auto_viz_demo rgb_small.png out.png -s naive -f 4.0) + set_tests_properties( + auto_viz_demo + PROPERTIES + LABELS auto_viz + SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" + ) +endif () diff --git a/apps/auto_viz/auto_viz_demo_generator.cpp b/apps/auto_viz/auto_viz_demo_generator.cpp index 81082294ac90..fc670f2a8ac8 100644 --- a/apps/auto_viz/auto_viz_demo_generator.cpp +++ b/apps/auto_viz/auto_viz_demo_generator.cpp @@ -65,7 +65,7 @@ class AutoVizDemo : public Halide::Generator { Expr beginx = cast(ceil(sourcex - kernel_radius)); Expr beginy = cast(ceil(sourcey - kernel_radius)); - RDom r(0, kernel_taps); + RDom r(0, cast(kernel_taps)); auto kernel = [](Expr x) -> Expr { Expr xx = abs(x); From 2424920bde4ae35f6bea142873bad29044cd8701 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 14:36:57 -0400 Subject: [PATCH 04/14] Add a CMake build for the hexagon_dma app Also fix two problems that kept the pipelines from building: the output copy_to_device() calls defaulted to the GPU device API, so pin them to HexagonDma; and the YUV harness called embed() on statically-2-D Runtime::Buffers, so give the chroma planes dynamic dimensionality. Fixes #8734 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 2 +- apps/hexagon_dma/CMakeLists.txt | 139 ++++++++++++++++++ .../pipeline_raw_linear_interleaved_basic.cpp | 2 +- .../hexagon_dma/pipeline_yuv_linear_basic.cpp | 4 +- apps/hexagon_dma/process_yuv_linear_basic.cpp | 8 +- 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 apps/hexagon_dma/CMakeLists.txt diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 886192b925b2..2115b2b90a1b 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -61,7 +61,7 @@ add_app(HelloBaremetal) # add_app(HelloiOS) # don't build HelloiOS here because it isn't universal. # add_app(HelloPyTorch) # TODO(#5374): missing CMake build add_app(hexagon_benchmarks) -# add_app(hexagon_dma) # TODO(#5374): missing CMake build +add_app(hexagon_dma) add_app(hist) add_app(iir_blur) add_app(interpolate) diff --git a/apps/hexagon_dma/CMakeLists.txt b/apps/hexagon_dma/CMakeLists.txt new file mode 100644 index 000000000000..b0b871e59c75 --- /dev/null +++ b/apps/hexagon_dma/CMakeLists.txt @@ -0,0 +1,139 @@ +cmake_minimum_required(VERSION 3.28) +project(hexagon_dma) + +enable_testing() + +# Set up language settings +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +# Find Halide +find_package(Halide REQUIRED) + +# The pipelines use the hexagon_dma scheduling directives, which require the +# hexagon_dma target feature. They are exercised on the host by linking against +# mock_dma_implementation.cpp, which stands in for the real Hexagon DMA driver. + +# Build the full RO+RW schedule matrix when requested (mirrors the Makefile's +# SCHEDULE_ALL). By default we build only the RW async/split/split_async +# schedules, which is what `make all` produces. +option(HEXAGON_DMA_SCHEDULE_ALL "Build the full RO+RW schedule matrix" OFF) + +set(rw_schedules async split split_async) +set(ro_schedules "") +if (HEXAGON_DMA_SCHEDULE_ALL) + list(PREPEND rw_schedules basic fold) + set(ro_schedules basic fold async split split_async) +endif () + +# The pipeline function names use "basic", but the generator's schedule +# GeneratorParam spells that value "none". +function(_dma_sched_value out label) + if (label STREQUAL "basic") + set(${out} none PARENT_SCOPE) + else () + set(${out} ${label} PARENT_SCOPE) + endif () +endfunction() + +## +# YUV pipelines (nv12 = 8-bit, p010 = 16-bit) +## + +add_halide_generator(pipeline_yuv_linear_basic.generator SOURCES pipeline_yuv_linear_basic.cpp) + +set(nv12_types input_y.type=uint8 input_uv.type=uint8 output_y.type=uint8 output_uv.type=uint8) +set(p010_types input_y.type=uint16 input_uv.type=uint16 output_y.type=uint16 output_uv.type=uint16) + +set(yuv_libs "") +foreach (type_name IN ITEMS nv12 p010) + foreach (label IN LISTS rw_schedules) + _dma_sched_value(sched ${label}) + add_halide_library( + pipeline_${type_name}_linear_rw_${label} + FROM pipeline_yuv_linear_basic.generator + GENERATOR pipeline_yuv_linear_basic + FEATURES hexagon_dma + PARAMS schedule=${sched} use_dma_for_output=true ${${type_name}_types} + ) + list(APPEND yuv_libs pipeline_${type_name}_linear_rw_${label}) + endforeach () + foreach (label IN LISTS ro_schedules) + _dma_sched_value(sched ${label}) + add_halide_library( + pipeline_${type_name}_linear_ro_${label} + FROM pipeline_yuv_linear_basic.generator + GENERATOR pipeline_yuv_linear_basic + FEATURES hexagon_dma + PARAMS schedule=${sched} use_dma_for_output=false ${${type_name}_types} + ) + list(APPEND yuv_libs pipeline_${type_name}_linear_ro_${label}) + endforeach () +endforeach () + +## +# RAW interleaved pipeline (fixed uint8) +## + +add_halide_generator( + pipeline_raw_linear_interleaved_basic.generator + SOURCES pipeline_raw_linear_interleaved_basic.cpp +) + +set(raw_libs "") +foreach (label IN LISTS rw_schedules) + _dma_sched_value(sched ${label}) + add_halide_library( + pipeline_raw_linear_interleaved_rw_${label} + FROM pipeline_raw_linear_interleaved_basic.generator + GENERATOR pipeline_raw_linear_interleaved_basic + FEATURES hexagon_dma + PARAMS schedule=${sched} use_dma_for_output=true + ) + list(APPEND raw_libs pipeline_raw_linear_interleaved_rw_${label}) +endforeach () +foreach (label IN LISTS ro_schedules) + _dma_sched_value(sched ${label}) + add_halide_library( + pipeline_raw_linear_interleaved_ro_${label} + FROM pipeline_raw_linear_interleaved_basic.generator + GENERATOR pipeline_raw_linear_interleaved_basic + FEATURES hexagon_dma + PARAMS schedule=${sched} use_dma_for_output=false + ) + list(APPEND raw_libs pipeline_raw_linear_interleaved_ro_${label}) +endforeach () + +## +# Processing executables (run on the host against the mock DMA driver) +## + +add_executable(process_yuv_linear_basic process_yuv_linear_basic.cpp mock_dma_implementation.cpp) +target_link_libraries(process_yuv_linear_basic PRIVATE Halide::Tools ${yuv_libs}) + +add_executable( + process_raw_linear_interleaved_basic + process_raw_linear_interleaved_basic.cpp + mock_dma_implementation.cpp +) +target_link_libraries(process_raw_linear_interleaved_basic PRIVATE Halide::Tools ${raw_libs}) + +if (HEXAGON_DMA_SCHEDULE_ALL) + target_compile_definitions(process_yuv_linear_basic PRIVATE SCHEDULE_ALL) + target_compile_definitions(process_raw_linear_interleaved_basic PRIVATE SCHEDULE_ALL) +endif () + +# Test that the apps actually work! +add_test(NAME process_yuv_linear_basic COMMAND process_yuv_linear_basic 256 128 async rw nv12) +add_test( + NAME process_raw_linear_interleaved_basic + COMMAND process_raw_linear_interleaved_basic 256 128 async rw +) +set_tests_properties( + process_yuv_linear_basic process_raw_linear_interleaved_basic + PROPERTIES + LABELS hexagon_dma + PASS_REGULAR_EXPRESSION "Success!" + SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" +) diff --git a/apps/hexagon_dma/pipeline_raw_linear_interleaved_basic.cpp b/apps/hexagon_dma/pipeline_raw_linear_interleaved_basic.cpp index ffc1b97d019c..e5c410fdbf9c 100644 --- a/apps/hexagon_dma/pipeline_raw_linear_interleaved_basic.cpp +++ b/apps/hexagon_dma/pipeline_raw_linear_interleaved_basic.cpp @@ -42,7 +42,7 @@ class DmaPipeline : public Generator { // Do some common scheduling here. if (use_dma_for_output) { - output.copy_to_device(); + output.copy_to_device(DeviceAPI::HexagonDma); } output diff --git a/apps/hexagon_dma/pipeline_yuv_linear_basic.cpp b/apps/hexagon_dma/pipeline_yuv_linear_basic.cpp index 00a1ef349ddc..e53030a5cd06 100644 --- a/apps/hexagon_dma/pipeline_yuv_linear_basic.cpp +++ b/apps/hexagon_dma/pipeline_yuv_linear_basic.cpp @@ -54,8 +54,8 @@ class DmaPipeline : public Generator { // Do some common scheduling here. if (use_dma_for_output) { - output_y.copy_to_device(); - output_uv.copy_to_device(); + output_y.copy_to_device(DeviceAPI::HexagonDma); + output_uv.copy_to_device(DeviceAPI::HexagonDma); } output_y diff --git a/apps/hexagon_dma/process_yuv_linear_basic.cpp b/apps/hexagon_dma/process_yuv_linear_basic.cpp index fb9cc38ad94c..800dfd08a7f3 100644 --- a/apps/hexagon_dma/process_yuv_linear_basic.cpp +++ b/apps/hexagon_dma/process_yuv_linear_basic.cpp @@ -119,8 +119,8 @@ inline int process_pipeline(T const &type, const int width, const int height, // Setup Halide input buffer with the test buffer Halide::Runtime::Buffer input_validation(data_in, width, height, 2); Halide::Runtime::Buffer input(nullptr, width, (3 * height) / 2); - Halide::Runtime::Buffer input_y = input.cropped(1, 0, height); // Luma plane only - Halide::Runtime::Buffer input_uv = input.cropped(1, height, height / 2); // Chroma plane only, with reduced height + Halide::Runtime::Buffer input_y = input.cropped(1, 0, height); // Luma plane only + Halide::Runtime::Buffer input_uv = input.cropped(1, height, height / 2); // Chroma plane only, with reduced height // describe the UV interleaving for 4:2:0 format input_uv.embed(2, 0); @@ -131,8 +131,8 @@ inline int process_pipeline(T const &type, const int width, const int height, // Setup Halide output buffer Halide::Runtime::Buffer output(width, (3 * height) / 2); - Halide::Runtime::Buffer output_y = output.cropped(1, 0, height); // Luma plane only - Halide::Runtime::Buffer output_uv = output.cropped(1, height, (height / 2)); // Chroma plane only, with reduced height + Halide::Runtime::Buffer output_y = output.cropped(1, 0, height); // Luma plane only + Halide::Runtime::Buffer output_uv = output.cropped(1, height, (height / 2)); // Chroma plane only, with reduced height // describe the UV interleaving for 4:2:0 format output_uv.embed(2, 0); From 4c6823feabd6b5cfa74d39518ee72da0a76d4067 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 19:15:34 -0400 Subject: [PATCH 05/14] Add a CMake build for the HelloPyTorch app Build the Halide ops with pytorch_wrapper output and synthesize the pybind extension in CMake instead of setup.py. The app configures only when Python and PyTorch are found, taking the extension's include and library paths from torch.utils.cpp_extension so it works against a CUDA-enabled wheel without a matching standalone CUDA toolkit. Its CUDA ops are gated behind HELLO_PYTORCH_CUDA. Fixes #8729 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 2 +- apps/HelloPyTorch/CMakeLists.txt | 284 +++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 apps/HelloPyTorch/CMakeLists.txt diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 2115b2b90a1b..d038233559a7 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -59,7 +59,7 @@ add_app(harris) # add_app(HelloAndroidCamera2) # TODO(#5374): missing CMake build add_app(HelloBaremetal) # add_app(HelloiOS) # don't build HelloiOS here because it isn't universal. -# add_app(HelloPyTorch) # TODO(#5374): missing CMake build +add_app(HelloPyTorch) add_app(hexagon_benchmarks) add_app(hexagon_dma) add_app(hist) diff --git a/apps/HelloPyTorch/CMakeLists.txt b/apps/HelloPyTorch/CMakeLists.txt new file mode 100644 index 000000000000..b59d558f9beb --- /dev/null +++ b/apps/HelloPyTorch/CMakeLists.txt @@ -0,0 +1,284 @@ +cmake_minimum_required(VERSION 3.28) +project(HelloPyTorch) + +enable_testing() + +# Set up language settings +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Find Halide +find_package(Halide REQUIRED) + +# This app wraps Halide-generated ops in a PyTorch C++ extension, so it needs +# Python and PyTorch. Skip cleanly if either is missing, the same way apps/onnx +# gates on its optional dependencies. +find_package(Python 3 COMPONENTS Interpreter Development.Module) +if (NOT Python_FOUND) + message(WARNING "HelloPyTorch: could NOT find Python 3; skipping") + return() +endif () + +# CUDA ops can only be exercised on a machine with a CUDA-enabled PyTorch and a +# GPU, which CPU-only CI does not have. Keep them opt-in. +option(HELLO_PYTORCH_CUDA "Build the CUDA variants of the HelloPyTorch ops" OFF) +if (HELLO_PYTORCH_CUDA) + set(torch_cuda_arg True) +else () + set(torch_cuda_arg False) +endif () + +# Pull the extension's build settings straight from torch, exactly as +# torch.utils.cpp_extension (and hence setup.py) does. We deliberately avoid +# find_package(Torch): its Caffe2 config force-enables the CUDA language and +# demands a standalone CUDA toolkit matching the wheel's version, even though +# this wrapper has no CUDA sources of its own -- which makes it fail to configure +# against a CUDA-enabled wheel unless an exactly-matching toolkit is installed. +execute_process( + COMMAND + "${Python_EXECUTABLE}" + -c "import torch.utils.cpp_extension as C; print(';'.join(C.include_paths(${torch_cuda_arg})))" + OUTPUT_VARIABLE Torch_INCLUDE_DIRS + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE Torch_probe_result + ERROR_VARIABLE Torch_probe_error +) +if (NOT Torch_probe_result EQUAL 0) + message(WARNING "HelloPyTorch: PyTorch is not usable; skipping\n${Torch_probe_error}") + return() +endif () +execute_process( + COMMAND + "${Python_EXECUTABLE}" + -c "import torch.utils.cpp_extension as C; print(';'.join(C.library_paths(${torch_cuda_arg})))" + OUTPUT_VARIABLE Torch_LIBRARY_DIRS + OUTPUT_STRIP_TRAILING_WHITESPACE +) +execute_process( + COMMAND + "${Python_EXECUTABLE}" -c "import torch; print(1 if torch._C._GLIBCXX_USE_CXX11_ABI else 0)" + OUTPUT_VARIABLE Torch_CXX11_ABI + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message(STATUS "HelloPyTorch: using Torch headers from ${Torch_INCLUDE_DIRS}") + +# Resolve the torch shared libraries the extension links against (the set +# CppExtension/CUDAExtension link by default). +set(torch_lib_names c10 torch torch_cpu torch_python) +if (HELLO_PYTORCH_CUDA) + list(APPEND torch_lib_names torch_cuda c10_cuda) + find_package(CUDAToolkit REQUIRED) +endif () +set(torch_libs "") +foreach (name IN LISTS torch_lib_names) + find_library(Torch_${name}_LIBRARY NAMES ${name} PATHS ${Torch_LIBRARY_DIRS} REQUIRED) + list(APPEND torch_libs "${Torch_${name}_LIBRARY}") +endforeach () + +## +# Halide generator and ops +## + +add_halide_generator(add.generator SOURCES src/add_generator.cpp) + +# Type-parameter sets, mirroring the Makefile. +set(add_types_f32 input_a.type=float32 input_b.type=float32 output.type=float32) +set(add_types_f64 input_a.type=float64 input_b.type=float64 output.type=float64) +set(add_grad_types_f32 + input_a.type=float32 input_b.type=float32 d_input_a.type=float32 d_input_b.type=float32 + d_output.type=float32 +) +set(add_grad_types_f64 + input_a.type=float64 input_b.type=float64 d_input_a.type=float64 d_input_b.type=float64 + d_output.type=float64 +) + +# List of ops exposed by the extension; populated as libraries are added. +set(op_libs "") + +# CPU ops ------------------------------------------------------------------- + +# Forward op (add) and hand-written gradient (add_grad). +add_halide_library( + add_float32 + FROM add.generator + GENERATOR add + PARAMS ${add_types_f32} + PYTORCH_WRAPPER add_float32.pytorch.h +) +add_halide_library( + add_float64 + FROM add.generator + GENERATOR add + PARAMS ${add_types_f64} + PYTORCH_WRAPPER add_float64.pytorch.h +) +add_halide_library( + add_grad_float32 + FROM add.generator + GENERATOR add_grad + PARAMS ${add_grad_types_f32} + PYTORCH_WRAPPER add_grad_float32.pytorch.h +) +add_halide_library( + add_grad_float64 + FROM add.generator + GENERATOR add_grad + PARAMS ${add_grad_types_f64} + PYTORCH_WRAPPER add_grad_float64.pytorch.h +) + +# Halide-autodifferentiated gradient (add with -d 1 + Li2018 autoscheduler). +add_halide_library( + add_halidegrad_float32 + FROM add.generator + GENERATOR add + GRADIENT_DESCENT + AUTOSCHEDULER Halide::Li2018 + PARAMS ${add_types_f32} + PYTORCH_WRAPPER add_halidegrad_float32.pytorch.h +) +add_halide_library( + add_halidegrad_float64 + FROM add.generator + GENERATOR add + GRADIENT_DESCENT + AUTOSCHEDULER Halide::Li2018 + PARAMS ${add_types_f64} + PYTORCH_WRAPPER add_halidegrad_float64.pytorch.h +) + +list(APPEND op_libs + add_float32 add_float64 add_grad_float32 add_grad_float64 add_halidegrad_float32 + add_halidegrad_float64 +) + +# CUDA ops ------------------------------------------------------------------ + +if (HELLO_PYTORCH_CUDA) + # The PyTorch CUDA wrapper requires the user_context feature so Halide's and + # PyTorch's GPU memory managers can talk to each other. + set(cuda_features cuda cuda_capability_61 user_context) + + add_halide_library( + add_cuda_float32 + FROM add.generator + GENERATOR add + FEATURES ${cuda_features} + PARAMS ${add_types_f32} + PYTORCH_WRAPPER add_cuda_float32.pytorch.h + ) + add_halide_library( + add_cuda_float64 + FROM add.generator + GENERATOR add + FEATURES ${cuda_features} + PARAMS ${add_types_f64} + PYTORCH_WRAPPER add_cuda_float64.pytorch.h + ) + add_halide_library( + add_grad_cuda_float32 + FROM add.generator + GENERATOR add_grad + FEATURES ${cuda_features} + PARAMS ${add_grad_types_f32} + PYTORCH_WRAPPER add_grad_cuda_float32.pytorch.h + ) + add_halide_library( + add_grad_cuda_float64 + FROM add.generator + GENERATOR add_grad + FEATURES ${cuda_features} + PARAMS ${add_grad_types_f64} + PYTORCH_WRAPPER add_grad_cuda_float64.pytorch.h + ) + add_halide_library( + add_halidegrad_cuda_float32 + FROM add.generator + GENERATOR add + GRADIENT_DESCENT + AUTOSCHEDULER Halide::Li2018 + FEATURES ${cuda_features} + PARAMS ${add_types_f32} + PYTORCH_WRAPPER add_halidegrad_cuda_float32.pytorch.h + ) + add_halide_library( + add_halidegrad_cuda_float64 + FROM add.generator + GENERATOR add + GRADIENT_DESCENT + AUTOSCHEDULER Halide::Li2018 + FEATURES ${cuda_features} + PARAMS ${add_types_f64} + PYTORCH_WRAPPER add_halidegrad_cuda_float64.pytorch.h + ) + + list(APPEND op_libs + add_cuda_float32 add_cuda_float64 add_grad_cuda_float32 add_grad_cuda_float64 + add_halidegrad_cuda_float32 add_halidegrad_cuda_float64 + ) +endif () + +## +# Synthesize the pybind wrapper (equivalent to setup.py's generate_pybind_wrapper) +## + +set(wrapper "#include \"torch/extension.h\"\n") +if (HELLO_PYTORCH_CUDA) + string(APPEND wrapper "#include \"HalidePyTorchCudaHelpers.h\"\n") +endif () +string(APPEND wrapper "#include \"HalidePyTorchHelpers.h\"\n") +foreach (op IN LISTS op_libs) + string(APPEND wrapper "#include \"${op}.pytorch.h\"\n") +endforeach () +string(APPEND wrapper "\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n") +foreach (op IN LISTS op_libs) + string(APPEND wrapper + " m.def(\"${op}\", &${op}_th_, \"PyTorch wrapper of the Halide pipeline ${op}\");\n" + ) +endforeach () +string(APPEND wrapper "}\n") + +set(wrapper_cpp "${CMAKE_CURRENT_BINARY_DIR}/pybind_wrapper.cpp") +file(GENERATE OUTPUT "${wrapper_cpp}" CONTENT "${wrapper}") + +## +# The Python extension module +## + +Python_add_library(halide_ops MODULE WITH_SOABI "${wrapper_cpp}") +set_target_properties(halide_ops PROPERTIES OUTPUT_NAME halide_ops) +target_compile_definitions( + halide_ops + PRIVATE TORCH_EXTENSION_NAME=halide_ops _GLIBCXX_USE_CXX11_ABI=${Torch_CXX11_ABI} +) +target_include_directories(halide_ops PRIVATE ${Torch_INCLUDE_DIRS}) +target_link_libraries(halide_ops PRIVATE ${op_libs} ${torch_libs}) +# Let the freshly built extension find the torch shared libraries at load time. +set_target_properties(halide_ops PROPERTIES BUILD_RPATH "${Torch_LIBRARY_DIRS}") +if (HELLO_PYTORCH_CUDA) + target_link_libraries(halide_ops PRIVATE CUDA::cuda_driver) +endif () + +## +# Test: run test.py against the freshly built extension. +## +# `python -m unittest` puts the working directory on sys.path, so running from +# the source dir makes `modules` and `test` importable; the built extension is +# picked up via PYTHONPATH. +add_test( + NAME hello_pytorch + COMMAND ${Python_EXECUTABLE} -m unittest test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +set_tests_properties( + hello_pytorch + PROPERTIES + LABELS HelloPyTorch + ENVIRONMENT "PYTHONPATH=$" + PASS_REGULAR_EXPRESSION "OK" + SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" + TIMEOUT 300 +) From cafa88a05304439bd8af8e06bf457c563676df07 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Tue, 21 Jul 2026 01:13:07 -0400 Subject: [PATCH 06/14] Remove the nn_ops app's stale CMake placeholder The nn_ops app is no longer present in the tree, so drop its dangling commented-out add_app() TODO from apps/CMakeLists.txt. Fixes #8736 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index d038233559a7..b75ba3246334 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -71,7 +71,6 @@ add_app(linear_blur) add_app(local_laplacian) add_app(max_filter) add_app(nl_means) -# add_app(nn_ops) # TODO(#5374): missing CMake build add_app(onnx) add_app(resize) add_app(resnet_50) From 846c6bf119df822804b00ddd8fff040fab157661 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 21:11:11 -0400 Subject: [PATCH 07/14] Add CMake/CTest support for running tests under valgrind and Intel SDE Provide CMake-native equivalents for rerunning the test suite under an external tool, using CTest's own mechanisms rather than bespoke scripting. Valgrind maps onto CTest's MemCheck action. A new Halide_ENABLE_MEMCHECK option pulls in the CTest module (so `ctest -T memcheck` works in the build tree with no dashboard submission) and configures the memory checker via MEMORYCHECK_COMMAND_OPTIONS and MEMORYCHECK_SUPPRESSIONS_FILE (test/valgrind.supp). The options omit --leak-check=full on purpose: Halide's JIT triggers one-time LLVM ORC allocations that valgrind reports as "possibly lost", which are noise rather than Halide bugs. correctness_tracing_stack does an intentional out-of-bounds read, so it is tagged with a no_memcheck label and excluded via -LE no_memcheck. Intel SDE maps onto CMAKE_CROSSCOMPILING_EMULATOR, which prefixes every add_test-registered command with the emulator, so no per-test enumeration is needed. This works at the current 3.28 CMake floor. New presets drive both: `valgrind` (the test preset applies the label exclusion; add -T memcheck at the ctest invocation) and `avx512-cannonlake` / `avx512-knights-landing` (sde -cnl -- / sde -knl --). Co-Authored-By: Claude Opus 4.8 (1M context) --- CMakeLists.txt | 26 ++++++++++- CMakePresets.json | 78 +++++++++++++++++++++++++++++++++ doc/BuildingHalideWithCMake.md | 53 +++++++++++++++++++--- test/correctness/CMakeLists.txt | 5 +++ test/valgrind.supp | 9 ++++ 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 test/valgrind.supp diff --git a/CMakeLists.txt b/CMakeLists.txt index 69796c26b13b..849edc85f4cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,31 @@ project( HOMEPAGE_URL "https://halide-lang.org" ) -enable_testing() +## +# Enable testing. When Halide_ENABLE_MEMCHECK is set (see the `valgrind` preset), +# pull in the full CTest module so that `ctest -T memcheck` reruns the whole +# registered test suite under valgrind in the build tree. Otherwise stick to the +# lighter enable_testing(). +## + +option(Halide_ENABLE_MEMCHECK + "Configure the build tree so 'ctest -T memcheck' runs tests under valgrind" OFF +) +if (Halide_ENABLE_MEMCHECK) + # Deliberately no --leak-check=full: Halide's JIT triggers one-time LLVM + # ORC allocations that valgrind reports as "possibly lost", which are noise + # rather than Halide bugs. These options catch the errors that matter + # (invalid reads/writes, use of uninitialized values). + set(MEMORYCHECK_COMMAND_OPTIONS "--error-exitcode=1 --track-origins=yes" + CACHE STRING "Options passed to valgrind by 'ctest -T memcheck'" + ) + set(MEMORYCHECK_SUPPRESSIONS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/test/valgrind.supp" + CACHE FILEPATH "Valgrind suppressions file for 'ctest -T memcheck'" + ) + include(CTest) +else () + enable_testing() +endif () ## # Disable find_package(Halide) inside the build diff --git a/CMakePresets.json b/CMakePresets.json index 6692d18b300c..a6cae1f65e9d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -268,9 +268,57 @@ "WITH_TEST_FUZZ": "YES", "BUILD_SHARED_LIBS": "NO" } + }, + { + "name": "valgrind", + "inherits": "debug", + "displayName": "Valgrind (ctest -T memcheck)", + "description": "Debug build wired up so 'ctest -T memcheck' reruns the test suite under valgrind", + "cacheVariables": { + "Halide_ENABLE_MEMCHECK": "ON" + } + }, + { + "name": "sde", + "hidden": true, + "inherits": "release", + "description": "Base preset for running the test suite under Intel SDE emulation" + }, + { + "name": "avx512-cannonlake", + "inherits": "sde", + "displayName": "AVX-512 (Intel SDE, Cannon Lake)", + "description": "Run every registered test under 'sde -cnl --' to emulate a Cannon Lake AVX-512 CPU", + "cacheVariables": { + "CMAKE_CROSSCOMPILING_EMULATOR": "sde;-cnl;--" + } + }, + { + "name": "avx512-knights-landing", + "inherits": "sde", + "displayName": "AVX-512 (Intel SDE, Knights Landing)", + "description": "Run every registered test under 'sde -knl --' to emulate a Knights Landing AVX-512 CPU", + "cacheVariables": { + "CMAKE_CROSSCOMPILING_EMULATOR": "sde;-knl;--" + } } ], "buildPresets": [ + { + "name": "valgrind", + "configurePreset": "valgrind", + "displayName": "Valgrind (ctest -T memcheck)" + }, + { + "name": "avx512-cannonlake", + "configurePreset": "avx512-cannonlake", + "displayName": "AVX-512 (Intel SDE, Cannon Lake)" + }, + { + "name": "avx512-knights-landing", + "configurePreset": "avx512-knights-landing", + "displayName": "AVX-512 (Intel SDE, Knights Landing)" + }, { "name": "macOS-coverage", "configurePreset": "macOS-coverage", @@ -282,6 +330,36 @@ } ], "testPresets": [ + { + "name": "valgrind", + "configurePreset": "valgrind", + "displayName": "Valgrind (ctest -T memcheck)", + "description": "Excludes the intentional-OOB tracing_stack test; combine with '-T memcheck' to run under valgrind", + "filter": { + "exclude": { + "label": "no_memcheck" + } + }, + "output": { + "outputOnFailure": true + } + }, + { + "name": "avx512-cannonlake", + "configurePreset": "avx512-cannonlake", + "displayName": "AVX-512 (Intel SDE, Cannon Lake)", + "output": { + "outputOnFailure": true + } + }, + { + "name": "avx512-knights-landing", + "configurePreset": "avx512-knights-landing", + "displayName": "AVX-512 (Intel SDE, Knights Landing)", + "output": { + "outputOnFailure": true + } + }, { "name": "macOS-coverage", "configurePreset": "macOS-coverage", diff --git a/doc/BuildingHalideWithCMake.md b/doc/BuildingHalideWithCMake.md index 1f27697de431..4bf6a788406b 100644 --- a/doc/BuildingHalideWithCMake.md +++ b/doc/BuildingHalideWithCMake.md @@ -397,6 +397,41 @@ To use these, you must build LLVM with additional options: -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" ``` +### Valgrind and Intel SDE presets + +These presets rerun the ordinary test suite under an external tool, using +CTest's native mechanisms. + +The `valgrind` preset reruns every registered test under [valgrind] via CTest's +built-in [MemCheck][ctest_memcheck] action. It configures a Debug build with +`Halide_ENABLE_MEMCHECK=ON`, which pulls in the [CTest module][ctest_module] so +that `ctest -T memcheck` works directly in the build tree (no CDash/dashboard +submission required): + +```shell +$ cmake --preset valgrind +$ cmake --build --preset valgrind +$ ctest --preset valgrind -T memcheck +``` + +The `valgrind` test preset excludes the `no_memcheck` label. Valgrind (and its +options and suppressions file) is configured via the standard +`MEMORYCHECK_COMMAND`, `MEMORYCHECK_COMMAND_OPTIONS`, and +`MEMORYCHECK_SUPPRESSIONS_FILE` (`test/valgrind.supp`) cache variables. + +The `avx512-cannonlake` and `avx512-knights-landing` presets rerun every test +under [Intel SDE][intel_sde] to emulate an AVX-512-capable CPU on a host that +lacks one. They set +[`CMAKE_CROSSCOMPILING_EMULATOR`][cmake_crosscompiling_emulator] to +`sde -cnl --` and `sde -knl --` respectively, so every `add_test`-registered +executable runs under the emulator automatically. `sde` must be on your `PATH`: + +```shell +$ cmake --preset avx512-cannonlake +$ cmake --build --preset avx512-cannonlake +$ ctest --preset avx512-cannonlake +``` + ## Build options Halide reads and understands several options that can configure the build. The @@ -431,12 +466,13 @@ The following options are _advanced_ and should not be required in typical workflows. Generally, these are used by Halide's own CI infrastructure, or as escape hatches for third-party packagers. -| Option | Default | Description | -| --------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -| `Halide_CCACHE_BUILD` | `OFF` | Use ccache with Halide-recommended settings to accelerate rebuilds. | -| `Halide_CCACHE_PARAMS` | `CCACHE_CPP2=yes CCACHE_HASHDIR=yes CCACHE_SLOPPINESS=pch_defines` | Options to pass to `ccache` when using `Halide_CCACHE_BUILD`. | -| `Halide_VERSION_OVERRIDE` | `${Halide_VERSION}` | Override the VERSION for libHalide. | -| `Halide_SOVERSION_OVERRIDE` | `${Halide_VERSION_MAJOR}` | Override the SOVERSION for libHalide. Expects a positive integer (i.e. not a version). | +| Option | Default | Description | +| --------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | +| `Halide_CCACHE_BUILD` | `OFF` | Use ccache with Halide-recommended settings to accelerate rebuilds. | +| `Halide_ENABLE_MEMCHECK` | `OFF` | Pull in the CTest module so `ctest -T memcheck` reruns the tests under valgrind. See the `valgrind` preset. | +| `Halide_CCACHE_PARAMS` | `CCACHE_CPP2=yes CCACHE_HASHDIR=yes CCACHE_SLOPPINESS=pch_defines` | Options to pass to `ccache` when using `Halide_CCACHE_BUILD`. | +| `Halide_VERSION_OVERRIDE` | `${Halide_VERSION}` | Override the VERSION for libHalide. | +| `Halide_SOVERSION_OVERRIDE` | `${Halide_VERSION_MAJOR}` | Override the SOVERSION for libHalide. Expects a positive integer (i.e. not a version). | The following options control whether to build certain test subsets. They only apply when `WITH_TESTS=ON`: @@ -578,9 +614,12 @@ On this test system (an M3 MacBook Pro), the build is three times faster, with a [cmake-install]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project [cmake-user-interaction]: https://cmake.org/cmake/help/latest/guide/user-interaction/index.html#setting-build-variables [cmake_build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html +[cmake_crosscompiling_emulator]: https://cmake.org/cmake/help/latest/variable/CMAKE_CROSSCOMPILING_EMULATOR.html [cmake_presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html [codestylecmake.md]: ./CodeStyleCMake.md [compiling in different directories]: https://ccache.dev/manual/4.11.3.html#_compiling_in_different_directories +[ctest_memcheck]: https://cmake.org/cmake/help/latest/manual/ctest.1.html#dashboard-client-steps +[ctest_module]: https://cmake.org/cmake/help/latest/module/CTest.html [doxygen]: https://www.doxygen.nl/index.html [doxygen-download]: https://www.doxygen.nl/download.html [eigen3cmake]: https://eigen.tuxfamily.org/dox/TopicCMakeGuide.html @@ -595,6 +634,7 @@ On this test system (an M3 MacBook Pro), the build is three times faster, with a [flatbuffers]: https://github.com/google/flatbuffers [halidecmakepackage.md]: ./HalideCMakePackage.md [homebrew]: https://brew.sh +[intel_sde]: https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html [lld]: https://lld.llvm.org/ [llvm]: https://github.com/llvm/llvm-project [msvc-cmd]: https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line @@ -608,6 +648,7 @@ On this test system (an M3 MacBook Pro), the build is three times faster, with a [python]: https://www.python.org/downloads/ [snap store]: https://snapcraft.io/cmake [v8]: https://v8.dev +[valgrind]: https://valgrind.org/ [vcpkg]: https://github.com/Microsoft/vcpkg [vcpkg-overlay]: https://learn.microsoft.com/en-us/vcpkg/concepts/overlay-ports [vcvarsall]: https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line#developer_command_file_locations diff --git a/test/correctness/CMakeLists.txt b/test/correctness/CMakeLists.txt index 3a05d2e9bece..141c14ff1788 100644 --- a/test/correctness/CMakeLists.txt +++ b/test/correctness/CMakeLists.txt @@ -448,6 +448,11 @@ tests( # keep-sorted end ) +# tracing_stack does an intentional out-of-bounds read, so exempt it from +# valgrind. Runs under `ctest -T memcheck` should exclude the label with +# `-LE no_memcheck` (the `valgrind` test preset already does this). +set_property(TEST correctness_tracing_stack APPEND PROPERTY LABELS no_memcheck) + # Make sure the test that needs Halide::ImageIO has it target_link_libraries(correctness_image_io PRIVATE Halide::ImageIO) diff --git a/test/valgrind.supp b/test/valgrind.supp new file mode 100644 index 000000000000..b9170f5537f3 --- /dev/null +++ b/test/valgrind.supp @@ -0,0 +1,9 @@ +# Valgrind suppressions for `ctest -T memcheck` (see Halide_ENABLE_MEMCHECK and +# the `valgrind` CMake preset), which reruns every correctness test under +# valgrind. +# +# This file starts empty. The default memcheck options do not enable full leak +# checking, so the one-time LLVM ORC-JIT allocations that Halide's JIT triggers +# are not reported. Add entries here if you widen the options (e.g. add +# `--leak-check=full`) and need to silence errors originating outside Halide's +# own code (LLVM, the C++ runtime, or a GPU/driver stack). From 00b756c70b29932feeade4300d5d01a0a85f8e7a Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 21:47:21 -0400 Subject: [PATCH 08/14] Add RunGen benchmark targets to the benchmark apps Give the six benchmark apps (bilateral_grid, camera_pipe, lens_blur, local_laplacian, nl_means, stencil_chain) a CMake-native benchmark path. Each app now emits a REGISTRATION source from its primary filter, builds an _rungen executable (linking Halide::RunGenMain + the filter, following the existing apps/HelloWasm pattern), and registers an _benchmark CTest that runs the filter with --benchmarks=all --estimate_all --parsable_output. Every benchmark test is labeled benchmark_apps, so `ctest -L benchmark_apps` runs all of them at once. The label is benchmark_apps rather than plain benchmark because ctest -L matches labels as a regex: a bare `benchmark` would also select the unrelated hexagon_benchmarks app, whose label contains that substring. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/bilateral_grid/CMakeLists.txt | 11 +++++++++++ apps/camera_pipe/CMakeLists.txt | 12 +++++++++++- apps/lens_blur/CMakeLists.txt | 12 +++++++++++- apps/local_laplacian/CMakeLists.txt | 16 +++++++++++++++- apps/nl_means/CMakeLists.txt | 12 +++++++++++- apps/stencil_chain/CMakeLists.txt | 16 +++++++++++++++- 6 files changed, 74 insertions(+), 5 deletions(-) diff --git a/apps/bilateral_grid/CMakeLists.txt b/apps/bilateral_grid/CMakeLists.txt index 4450afa19f02..5a1d3980a020 100644 --- a/apps/bilateral_grid/CMakeLists.txt +++ b/apps/bilateral_grid/CMakeLists.txt @@ -22,6 +22,7 @@ add_halide_generator( add_halide_library( bilateral_grid FROM bilateral_grid.generator + REGISTRATION bilateral_grid_registration STMT bilateral_grid_STMT SCHEDULE bilateral_grid_SCHEDULE ) @@ -63,3 +64,13 @@ if (EXISTS ${IMAGE}) SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(bilateral_grid_rungen ${bilateral_grid_registration}) +target_link_libraries(bilateral_grid_rungen PRIVATE Halide::RunGenMain bilateral_grid) +add_test( + NAME bilateral_grid_benchmark + COMMAND bilateral_grid_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(bilateral_grid_benchmark PROPERTIES LABELS "bilateral_grid;benchmark_apps") diff --git a/apps/camera_pipe/CMakeLists.txt b/apps/camera_pipe/CMakeLists.txt index 6e441b5302bf..c44c16121d94 100644 --- a/apps/camera_pipe/CMakeLists.txt +++ b/apps/camera_pipe/CMakeLists.txt @@ -19,7 +19,7 @@ add_halide_generator( ) # Filters -add_halide_library(camera_pipe FROM camera_pipe.generator) +add_halide_library(camera_pipe FROM camera_pipe.generator REGISTRATION camera_pipe_registration) add_halide_library( camera_pipe_auto_schedule FROM camera_pipe.generator @@ -53,3 +53,13 @@ if (EXISTS ${IMAGE}) SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(camera_pipe_rungen ${camera_pipe_registration}) +target_link_libraries(camera_pipe_rungen PRIVATE Halide::RunGenMain camera_pipe) +add_test( + NAME camera_pipe_benchmark + COMMAND camera_pipe_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(camera_pipe_benchmark PROPERTIES LABELS "camera_pipe;benchmark_apps") diff --git a/apps/lens_blur/CMakeLists.txt b/apps/lens_blur/CMakeLists.txt index 1cb9098de80a..8219f17c2124 100644 --- a/apps/lens_blur/CMakeLists.txt +++ b/apps/lens_blur/CMakeLists.txt @@ -15,7 +15,7 @@ find_package(Halide REQUIRED) add_halide_generator(lens_blur.generator SOURCES lens_blur_generator.cpp) # Filters -add_halide_library(lens_blur FROM lens_blur.generator) +add_halide_library(lens_blur FROM lens_blur.generator REGISTRATION lens_blur_registration) add_halide_library( lens_blur_auto_schedule FROM lens_blur.generator @@ -59,3 +59,13 @@ if (EXISTS ${IMAGE}) ) endif () endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(lens_blur_rungen ${lens_blur_registration}) +target_link_libraries(lens_blur_rungen PRIVATE Halide::RunGenMain lens_blur) +add_test( + NAME lens_blur_benchmark + COMMAND lens_blur_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(lens_blur_benchmark PROPERTIES LABELS "lens_blur;benchmark_apps") diff --git a/apps/local_laplacian/CMakeLists.txt b/apps/local_laplacian/CMakeLists.txt index d4caaf5673de..bb0dcaf31b36 100644 --- a/apps/local_laplacian/CMakeLists.txt +++ b/apps/local_laplacian/CMakeLists.txt @@ -19,7 +19,11 @@ add_halide_generator( ) # Filters -add_halide_library(local_laplacian FROM local_laplacian.generator) +add_halide_library( + local_laplacian + FROM local_laplacian.generator + REGISTRATION local_laplacian_registration +) add_halide_library( local_laplacian_auto_schedule FROM local_laplacian.generator @@ -55,3 +59,13 @@ if (EXISTS ${IMAGE}) SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(local_laplacian_rungen ${local_laplacian_registration}) +target_link_libraries(local_laplacian_rungen PRIVATE Halide::RunGenMain local_laplacian) +add_test( + NAME local_laplacian_benchmark + COMMAND local_laplacian_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(local_laplacian_benchmark PROPERTIES LABELS "local_laplacian;benchmark_apps") diff --git a/apps/nl_means/CMakeLists.txt b/apps/nl_means/CMakeLists.txt index f71f180fa0de..59a0d338aa7a 100644 --- a/apps/nl_means/CMakeLists.txt +++ b/apps/nl_means/CMakeLists.txt @@ -15,7 +15,7 @@ find_package(Halide REQUIRED) add_halide_generator(nl_means.generator SOURCES nl_means_generator.cpp) # Filters -add_halide_library(nl_means FROM nl_means.generator) +add_halide_library(nl_means FROM nl_means.generator REGISTRATION nl_means_registration) add_halide_library( nl_means_auto_schedule FROM nl_means.generator @@ -40,3 +40,13 @@ if (EXISTS ${IMAGE}) SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(nl_means_rungen ${nl_means_registration}) +target_link_libraries(nl_means_rungen PRIVATE Halide::RunGenMain nl_means) +add_test( + NAME nl_means_benchmark + COMMAND nl_means_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(nl_means_benchmark PROPERTIES LABELS "nl_means;benchmark_apps") diff --git a/apps/stencil_chain/CMakeLists.txt b/apps/stencil_chain/CMakeLists.txt index 5f18b5f95a3f..938a19bd20da 100644 --- a/apps/stencil_chain/CMakeLists.txt +++ b/apps/stencil_chain/CMakeLists.txt @@ -15,7 +15,11 @@ find_package(Halide REQUIRED) add_halide_generator(stencil_chain.generator SOURCES stencil_chain_generator.cpp) # Filters -add_halide_library(stencil_chain FROM stencil_chain.generator) +add_halide_library( + stencil_chain + FROM stencil_chain.generator + REGISTRATION stencil_chain_registration +) add_halide_library( stencil_chain_auto_schedule FROM stencil_chain.generator @@ -46,3 +50,13 @@ if (EXISTS ${IMAGE}) SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () + +# RunGen-based benchmark. Run every app benchmark at once with +# `ctest -L benchmark_apps`. +add_executable(stencil_chain_rungen ${stencil_chain_registration}) +target_link_libraries(stencil_chain_rungen PRIVATE Halide::RunGenMain stencil_chain) +add_test( + NAME stencil_chain_benchmark + COMMAND stencil_chain_rungen --benchmarks=all --estimate_all --parsable_output +) +set_tests_properties(stencil_chain_benchmark PROPERTIES LABELS "stencil_chain;benchmark_apps") From 0ee26db65effcf758d14ca5e1e9c68e16f0e9544 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Mon, 20 Jul 2026 23:39:27 -0400 Subject: [PATCH 09/14] Fix hexagon_remote install layout to mirror the committed bin/ tree The build-from-source Hexagon remote runtime (Halide_BUILD_HEXAGON_REMOTE_RUNTIME) installed its artifacts to a flat bin/, so its output did not match the committed src/runtime/hexagon_remote/bin/ subtree. Fix the install destinations so the runtime can be rebuilt from source and installed straight back over the committed blobs: - qurt artifacts -> bin/, where is the toolchain's HEXAGON_ARCH (e.g. v65), guarded against being unset rather than hardcoded. - android host libs -> bin/arm--android, from the NDK toolchain's pointer size. - the simulator-host lib -> bin/host (it previously had no install rule). - the qaic-generated IDL sources -> bin/src. - everything tagged COMPONENT Halide_Hexagon, destinations under CMAKE_INSTALL_BINDIR (GNUInstallDirs). The qurt/android pieces are ExternalProjects, whose install steps run at build time and which `cmake --install` does not descend into, so they now install into one shared staging tree that a single install(DIRECTORY ... TYPE BIN) relays into the real prefix. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/runtime/hexagon_remote/CMakeLists.txt | 43 +++++++++++++++++++ .../hexagon_remote/android/CMakeLists.txt | 9 +++- .../hexagon_remote/qurt/CMakeLists.txt | 15 ++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/runtime/hexagon_remote/CMakeLists.txt b/src/runtime/hexagon_remote/CMakeLists.txt index a1019be06b00..9b215bde410b 100644 --- a/src/runtime/hexagon_remote/CMakeLists.txt +++ b/src/runtime/hexagon_remote/CMakeLists.txt @@ -1,4 +1,5 @@ include(ExternalProject) +include(GNUInstallDirs) find_package(HexagonSDK REQUIRED) @@ -40,11 +41,20 @@ if (_hexagon_build_type) list(APPEND common_cache_args "-DCMAKE_BUILD_TYPE:STRING=${_hexagon_build_type}") endif () +# The ExternalProject install steps run during *this* project's build, writing +# into INSTALL_DIR (see -DCMAKE_INSTALL_PREFIX= above); the outer +# `cmake --install` does not descend into sub-projects. Point every sub-build at +# one shared staging tree so their qurt/android artifacts merge into a single +# bin/ layout, then relay that tree into the real install prefix with the +# install(DIRECTORY) rule near the end of this file. +set(HEXAGON_REMOTE_STAGE "${CMAKE_CURRENT_BINARY_DIR}/stage") + ExternalProject_Add( hexagon_remote-qurt SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qurt" CMAKE_CACHE_ARGS "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${HEXAGON_TOOLCHAIN}" ${common_cache_args} PREFIX hexagon + INSTALL_DIR "${HEXAGON_REMOTE_STAGE}" DEPENDS halide_hexagon_remote_idl CONFIGURE_HANDLED_BY_BUILD ON ) @@ -61,6 +71,7 @@ foreach (abi bits IN ZIP_LISTS arm_abis arm_bits) "-DANDROID_PLATFORM:STRING=21" ${common_cache_args} PREFIX arm-${bits}-android + INSTALL_DIR "${HEXAGON_REMOTE_STAGE}" DEPENDS halide_hexagon_remote_idl CONFIGURE_HANDLED_BY_BUILD ON ) @@ -71,6 +82,38 @@ target_compile_features(halide_hexagon_host PRIVATE cxx_std_17) target_include_directories(halide_hexagon_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) target_link_libraries(halide_hexagon_host PRIVATE HexagonSDK::wrapper) +# The simulator-host variant is a normal target of this project, so its +# install rule is honored directly by `cmake --install`. It mirrors the +# committed src/runtime/hexagon_remote/bin/host layout. +install( + TARGETS halide_hexagon_host + DESTINATION "${CMAKE_INSTALL_BINDIR}/host" + COMPONENT Halide_Hexagon +) + +# The qaic-generated IDL header/skel/stub complete the committed +# src/runtime/hexagon_remote/bin/src layout. +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/halide_hexagon_remote.h" + "${CMAKE_CURRENT_BINARY_DIR}/halide_hexagon_remote_skel.c" + "${CMAKE_CURRENT_BINARY_DIR}/halide_hexagon_remote_stub.c" + DESTINATION "${CMAKE_INSTALL_BINDIR}/src" + COMPONENT Halide_Hexagon +) + +# Relay the staged qurt/android artifacts (produced by the ExternalProject +# install steps during the build) into the real install tree. `cmake --install` +# does not descend into ExternalProjects, so without this only the host variant +# above would be installed. USE_SOURCE_PERMISSIONS keeps the executable bit on +# hexagon_sim_remote. +install( + DIRECTORY "${HEXAGON_REMOTE_STAGE}/bin/" + TYPE BIN + COMPONENT Halide_Hexagon + USE_SOURCE_PERMISSIONS +) + add_custom_target(hexagon_remote) add_dependencies( hexagon_remote diff --git a/src/runtime/hexagon_remote/android/CMakeLists.txt b/src/runtime/hexagon_remote/android/CMakeLists.txt index 6d4f3b4f7d20..90eacccc9ca3 100644 --- a/src/runtime/hexagon_remote/android/CMakeLists.txt +++ b/src/runtime/hexagon_remote/android/CMakeLists.txt @@ -29,4 +29,11 @@ target_include_directories( ) target_link_libraries(halide_hexagon_host PRIVATE fastrpc::cdsprpc log) -install(TARGETS halide_hexagon_host DESTINATION bin) +# Mirror the committed src/runtime/hexagon_remote/bin/arm--android layout. +# The bit width comes from the Android NDK toolchain's pointer size. +math(EXPR _android_bits "8 * ${CMAKE_SIZEOF_VOID_P}") +install( + TARGETS halide_hexagon_host + DESTINATION bin/arm-${_android_bits}-android + COMPONENT Halide_Hexagon +) diff --git a/src/runtime/hexagon_remote/qurt/CMakeLists.txt b/src/runtime/hexagon_remote/qurt/CMakeLists.txt index 84bbe4c80a49..8693a32b4eb6 100644 --- a/src/runtime/hexagon_remote/qurt/CMakeLists.txt +++ b/src/runtime/hexagon_remote/qurt/CMakeLists.txt @@ -31,4 +31,17 @@ add_library( target_include_directories(halide_hexagon_remote_skel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../..) target_include_directories(halide_hexagon_remote_skel SYSTEM PRIVATE ${HALIDE_HEXAGON_REMOTE_IDL}) -install(TARGETS sim_qurt hexagon_sim_remote halide_hexagon_remote_skel DESTINATION bin) +# HEXAGON_ARCH (e.g. v65/v66/v68) is set by the Hexagon SDK toolchain file. +# Install into a per-arch subdirectory so the layout mirrors the committed +# src/runtime/hexagon_remote/bin/ tree. +if (NOT HEXAGON_ARCH) + message( + FATAL_ERROR "HEXAGON_ARCH was not set by the Hexagon toolchain; " + "cannot determine the install subdirectory." + ) +endif () +install( + TARGETS sim_qurt hexagon_sim_remote halide_hexagon_remote_skel + DESTINATION bin/${HEXAGON_ARCH} + COMPONENT Halide_Hexagon +) From 071797a2c2b3aa4473d71b03feede43f967a0079 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Tue, 21 Jul 2026 10:49:34 -0400 Subject: [PATCH 10/14] Pin apps/hexagon_dma's Halide libraries to host, skip mock DMA driver on MSVC CI configures the ambient Halide_TARGET to GPU-testing targets like host-cuda when exercising the apps test label, and since this app's add_halide_library calls didn't specify TARGETS, they inherited it, producing e.g. host-cuda-hexagon_dma. That combination JIT-compiles the whole combined runtime just to auto-detect the host's CUDA compute capability, which then fails to link against hexagon_dma's mock-only driver symbols. These pipelines never touch any GPU device API, so pin them to a plain host target instead. Also skip the mock_dma_implementation.cpp-based executables and tests under MSVC, since its WEAK == __attribute__((weak)) macro doesn't compile there. --- apps/hexagon_dma/CMakeLists.txt | 72 +++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/apps/hexagon_dma/CMakeLists.txt b/apps/hexagon_dma/CMakeLists.txt index b0b871e59c75..2fd1c6145a09 100644 --- a/apps/hexagon_dma/CMakeLists.txt +++ b/apps/hexagon_dma/CMakeLists.txt @@ -15,6 +15,14 @@ find_package(Halide REQUIRED) # hexagon_dma target feature. They are exercised on the host by linking against # mock_dma_implementation.cpp, which stands in for the real Hexagon DMA driver. +# These pipelines don't touch any GPU/device APIs, so always build them for a +# plain "host-hexagon_dma" target rather than inheriting the ambient +# Halide_TARGET. CI configures Halide_TARGET to things like host-cuda or +# host-vulkan-... to exercise GPU apps, and combining hexagon_dma with an +# unrelated device API breaks target auto-detection (e.g. querying the host's +# CUDA compute capability JIT-compiles the whole combined runtime, which then +# fails to link against hexagon_dma's driver-only symbols). + # Build the full RO+RW schedule matrix when requested (mirrors the Makefile's # SCHEDULE_ALL). By default we build only the RW async/split/split_async # schedules, which is what `make all` produces. @@ -54,6 +62,7 @@ foreach (type_name IN ITEMS nv12 p010) pipeline_${type_name}_linear_rw_${label} FROM pipeline_yuv_linear_basic.generator GENERATOR pipeline_yuv_linear_basic + TARGETS host FEATURES hexagon_dma PARAMS schedule=${sched} use_dma_for_output=true ${${type_name}_types} ) @@ -65,6 +74,7 @@ foreach (type_name IN ITEMS nv12 p010) pipeline_${type_name}_linear_ro_${label} FROM pipeline_yuv_linear_basic.generator GENERATOR pipeline_yuv_linear_basic + TARGETS host FEATURES hexagon_dma PARAMS schedule=${sched} use_dma_for_output=false ${${type_name}_types} ) @@ -88,6 +98,7 @@ foreach (label IN LISTS rw_schedules) pipeline_raw_linear_interleaved_rw_${label} FROM pipeline_raw_linear_interleaved_basic.generator GENERATOR pipeline_raw_linear_interleaved_basic + TARGETS host FEATURES hexagon_dma PARAMS schedule=${sched} use_dma_for_output=true ) @@ -99,6 +110,7 @@ foreach (label IN LISTS ro_schedules) pipeline_raw_linear_interleaved_ro_${label} FROM pipeline_raw_linear_interleaved_basic.generator GENERATOR pipeline_raw_linear_interleaved_basic + TARGETS host FEATURES hexagon_dma PARAMS schedule=${sched} use_dma_for_output=false ) @@ -109,31 +121,41 @@ endforeach () # Processing executables (run on the host against the mock DMA driver) ## -add_executable(process_yuv_linear_basic process_yuv_linear_basic.cpp mock_dma_implementation.cpp) -target_link_libraries(process_yuv_linear_basic PRIVATE Halide::Tools ${yuv_libs}) +# mock_dma_implementation.cpp stands in for the real Hexagon DMA driver by +# providing weak definitions of its entry points, using the GCC/Clang-only +# WEAK == __attribute__((weak)) from src/runtime/hexagon_dma_pool.h and +# src/runtime/mini_hexagon_dma.h. MSVC has no equivalent for these headers' +# use of __attribute__, so just skip the mock-driver-backed executables and +# tests there; the pipelines themselves still build fine. +if (NOT MSVC) + add_executable( + process_yuv_linear_basic process_yuv_linear_basic.cpp mock_dma_implementation.cpp + ) + target_link_libraries(process_yuv_linear_basic PRIVATE Halide::Tools ${yuv_libs}) -add_executable( - process_raw_linear_interleaved_basic - process_raw_linear_interleaved_basic.cpp - mock_dma_implementation.cpp -) -target_link_libraries(process_raw_linear_interleaved_basic PRIVATE Halide::Tools ${raw_libs}) + add_executable( + process_raw_linear_interleaved_basic + process_raw_linear_interleaved_basic.cpp + mock_dma_implementation.cpp + ) + target_link_libraries(process_raw_linear_interleaved_basic PRIVATE Halide::Tools ${raw_libs}) -if (HEXAGON_DMA_SCHEDULE_ALL) - target_compile_definitions(process_yuv_linear_basic PRIVATE SCHEDULE_ALL) - target_compile_definitions(process_raw_linear_interleaved_basic PRIVATE SCHEDULE_ALL) -endif () + if (HEXAGON_DMA_SCHEDULE_ALL) + target_compile_definitions(process_yuv_linear_basic PRIVATE SCHEDULE_ALL) + target_compile_definitions(process_raw_linear_interleaved_basic PRIVATE SCHEDULE_ALL) + endif () -# Test that the apps actually work! -add_test(NAME process_yuv_linear_basic COMMAND process_yuv_linear_basic 256 128 async rw nv12) -add_test( - NAME process_raw_linear_interleaved_basic - COMMAND process_raw_linear_interleaved_basic 256 128 async rw -) -set_tests_properties( - process_yuv_linear_basic process_raw_linear_interleaved_basic - PROPERTIES - LABELS hexagon_dma - PASS_REGULAR_EXPRESSION "Success!" - SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" -) + # Test that the apps actually work! + add_test(NAME process_yuv_linear_basic COMMAND process_yuv_linear_basic 256 128 async rw nv12) + add_test( + NAME process_raw_linear_interleaved_basic + COMMAND process_raw_linear_interleaved_basic 256 128 async rw + ) + set_tests_properties( + process_yuv_linear_basic process_raw_linear_interleaved_basic + PROPERTIES + LABELS hexagon_dma + PASS_REGULAR_EXPRESSION "Success!" + SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" + ) +endif () From 2a445e06cb7cd16dd78669089276f8c8dcc6b58a Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Tue, 21 Jul 2026 12:39:26 -0400 Subject: [PATCH 11/14] Discard auto_viz_demo's trace output in its CTest test auto_viz's pipelines are built with trace_all, but auto_viz_demo.cpp never sets HL_TRACE_FILE, so Halide's default trace handler falls back to formatting every load/store/produce/consume event as plain text via halide_print. For even the small test image that's tens of millions of lines (multiple GB) of stdout. Direct execution is merely slow, but a test runner that captures/streams that output (e.g. `ctest -V`, as the buildbot uses) can burn through the entire test timeout on it. Point HL_TRACE_FILE at the null device for the auto_viz_demo test so it exercises the same binary trace-writing path (still a meaningful check that trace_all pipelines run) while discarding the output almost instantly. --- apps/auto_viz/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/auto_viz/CMakeLists.txt b/apps/auto_viz/CMakeLists.txt index 1e647a5af288..8f5292f6a668 100644 --- a/apps/auto_viz/CMakeLists.txt +++ b/apps/auto_viz/CMakeLists.txt @@ -40,6 +40,13 @@ add_executable(auto_viz_demo auto_viz_demo.cpp) target_link_libraries(auto_viz_demo PRIVATE Halide::ImageIO ${variants}) # Test that the app actually works! + +if (WIN32) + set(_auto_viz_trace_sink "NUL") +else () + set(_auto_viz_trace_sink "/dev/null") +endif () + set(IMAGE ${CMAKE_CURRENT_LIST_DIR}/../images/rgb_small.png) if (EXISTS ${IMAGE}) configure_file(${IMAGE} rgb_small.png COPYONLY) @@ -48,6 +55,7 @@ if (EXISTS ${IMAGE}) auto_viz_demo PROPERTIES LABELS auto_viz + ENVIRONMENT "HL_TRACE_FILE=${_auto_viz_trace_sink}" SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" ) endif () From ad606e31023317b7a6c65f4dd99676643b1068b6 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 22 Jul 2026 11:35:49 -0400 Subject: [PATCH 12/14] Fix wrong function pointer for auto_viz's complex/down variant The down row of the dispatch table pointed at auto_viz_demo_complex_up instead of _down. Expand the CTest coverage to all six (schedule, direction) variants; per discussion with @shoaibkamil, that alone can't catch this class of bug since the program still exits 0 with a valid image either way -- verifying output correctness is tracked as separate future work. Co-Authored-By: Claude Sonnet 5 --- apps/auto_viz/CMakeLists.txt | 27 +++++++++++++++++++-------- apps/auto_viz/auto_viz_demo.cpp | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/auto_viz/CMakeLists.txt b/apps/auto_viz/CMakeLists.txt index 8f5292f6a668..d0f63a38a93c 100644 --- a/apps/auto_viz/CMakeLists.txt +++ b/apps/auto_viz/CMakeLists.txt @@ -50,12 +50,23 @@ endif () set(IMAGE ${CMAKE_CURRENT_LIST_DIR}/../images/rgb_small.png) if (EXISTS ${IMAGE}) configure_file(${IMAGE} rgb_small.png COPYONLY) - add_test(NAME auto_viz_demo COMMAND auto_viz_demo rgb_small.png out.png -s naive -f 4.0) - set_tests_properties( - auto_viz_demo - PROPERTIES - LABELS auto_viz - ENVIRONMENT "HL_TRACE_FILE=${_auto_viz_trace_sink}" - SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" - ) + # Exercise every (schedule, direction) combination so a wrong function + # pointer in one variant's dispatch table can't hide behind another. + foreach (schedule IN ITEMS naive lessnaive complex) + add_test( + NAME auto_viz_demo_${schedule}_up + COMMAND auto_viz_demo rgb_small.png out_${schedule}_up.png -s ${schedule} -f 4.0 + ) + add_test( + NAME auto_viz_demo_${schedule}_down + COMMAND auto_viz_demo rgb_small.png out_${schedule}_down.png -s ${schedule} -f 0.25 + ) + set_tests_properties( + auto_viz_demo_${schedule}_up auto_viz_demo_${schedule}_down + PROPERTIES + LABELS auto_viz + ENVIRONMENT "HL_TRACE_FILE=${_auto_viz_trace_sink}" + SKIP_REGULAR_EXPRESSION "\\[SKIP\\]" + ) + endforeach () endif () diff --git a/apps/auto_viz/auto_viz_demo.cpp b/apps/auto_viz/auto_viz_demo.cpp index 3129a8ed8667..180c0e13aaad 100644 --- a/apps/auto_viz/auto_viz_demo.cpp +++ b/apps/auto_viz/auto_viz_demo.cpp @@ -61,7 +61,7 @@ int main(int argc, char **argv) { &auto_viz_demo_complex_up}, {&auto_viz_demo_naive_down, &auto_viz_demo_lessnaive_down, - &auto_viz_demo_complex_up}, + &auto_viz_demo_complex_down}, }; int schedule_idx = 0; From 6fa112d444f0170489539fb6d0378f72b21ca699 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 22 Jul 2026 11:36:17 -0400 Subject: [PATCH 13/14] Address review feedback on HelloPyTorch wrapper and valgrind docs - HelloPyTorch/CMakeLists.txt: PYTORCH_WRAPPER names a variable to receive the generated header's path, not a filename. Add add_pytorch_op() to capture it properly and auto-populate op_libs and the wrapper #include list, instead of relying on filename convention. - Clarify why CMakeLists.txt doesn't probe for a valgrind binary itself (CTest's memcheck driver already handles that). - Tighten the valgrind test preset's description. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 5 +- CMakePresets.json | 2 +- apps/HelloPyTorch/CMakeLists.txt | 87 +++++++++++--------------------- 3 files changed, 35 insertions(+), 59 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 849edc85f4cd..c79e6d7609fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,10 @@ project( # Enable testing. When Halide_ENABLE_MEMCHECK is set (see the `valgrind` preset), # pull in the full CTest module so that `ctest -T memcheck` reruns the whole # registered test suite under valgrind in the build tree. Otherwise stick to the -# lighter enable_testing(). +# lighter enable_testing(). We don't probe for a valgrind binary ourselves: +# CTest's memcheck driver already locates (or complains about) MEMORYCHECK_COMMAND +# on its own, and configuring still succeeds if it's missing -- `ctest -T memcheck` +# simply fails at test time in that case. ## option(Halide_ENABLE_MEMCHECK diff --git a/CMakePresets.json b/CMakePresets.json index a6cae1f65e9d..f453d2f64cf6 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -334,7 +334,7 @@ "name": "valgrind", "configurePreset": "valgrind", "displayName": "Valgrind (ctest -T memcheck)", - "description": "Excludes the intentional-OOB tracing_stack test; combine with '-T memcheck' to run under valgrind", + "description": "Selects the tests to run under valgrind; CTest presets can't select the '-T memcheck' action on their own, so pass it explicitly: 'ctest --preset valgrind -T memcheck'", "filter": { "exclude": { "label": "no_memcheck" diff --git a/apps/HelloPyTorch/CMakeLists.txt b/apps/HelloPyTorch/CMakeLists.txt index b59d558f9beb..2b68cc1126af 100644 --- a/apps/HelloPyTorch/CMakeLists.txt +++ b/apps/HelloPyTorch/CMakeLists.txt @@ -95,64 +95,47 @@ set(add_grad_types_f64 d_output.type=float64 ) -# List of ops exposed by the extension; populated as libraries are added. +# List of ops exposed by the extension, and the PyTorch wrapper headers they +# generate; both are populated by add_pytorch_op() as libraries are added. set(op_libs "") +set(op_wrapper_headers "") + +# add_halide_library()'s PYTORCH_WRAPPER argument names a variable to receive +# the generated wrapper header's path, not a filename -- see add_halide_library's +# docs. This wraps it so callers don't have to manage that variable (or +# op_libs/op_wrapper_headers) by hand. +function(add_pytorch_op TARGET) + add_halide_library(${TARGET} ${ARGN} PYTORCH_WRAPPER "${TARGET}_pytorch_wrapper") + list(APPEND op_libs "${TARGET}") + list(APPEND op_wrapper_headers "${${TARGET}_pytorch_wrapper}") + set(op_libs "${op_libs}" PARENT_SCOPE) + set(op_wrapper_headers "${op_wrapper_headers}" PARENT_SCOPE) +endfunction() # CPU ops ------------------------------------------------------------------- # Forward op (add) and hand-written gradient (add_grad). -add_halide_library( - add_float32 - FROM add.generator - GENERATOR add - PARAMS ${add_types_f32} - PYTORCH_WRAPPER add_float32.pytorch.h -) -add_halide_library( - add_float64 - FROM add.generator - GENERATOR add - PARAMS ${add_types_f64} - PYTORCH_WRAPPER add_float64.pytorch.h -) -add_halide_library( - add_grad_float32 - FROM add.generator - GENERATOR add_grad - PARAMS ${add_grad_types_f32} - PYTORCH_WRAPPER add_grad_float32.pytorch.h -) -add_halide_library( - add_grad_float64 - FROM add.generator - GENERATOR add_grad - PARAMS ${add_grad_types_f64} - PYTORCH_WRAPPER add_grad_float64.pytorch.h -) +add_pytorch_op(add_float32 FROM add.generator GENERATOR add PARAMS ${add_types_f32}) +add_pytorch_op(add_float64 FROM add.generator GENERATOR add PARAMS ${add_types_f64}) +add_pytorch_op(add_grad_float32 FROM add.generator GENERATOR add_grad PARAMS ${add_grad_types_f32}) +add_pytorch_op(add_grad_float64 FROM add.generator GENERATOR add_grad PARAMS ${add_grad_types_f64}) # Halide-autodifferentiated gradient (add with -d 1 + Li2018 autoscheduler). -add_halide_library( +add_pytorch_op( add_halidegrad_float32 FROM add.generator GENERATOR add GRADIENT_DESCENT AUTOSCHEDULER Halide::Li2018 PARAMS ${add_types_f32} - PYTORCH_WRAPPER add_halidegrad_float32.pytorch.h ) -add_halide_library( +add_pytorch_op( add_halidegrad_float64 FROM add.generator GENERATOR add GRADIENT_DESCENT AUTOSCHEDULER Halide::Li2018 PARAMS ${add_types_f64} - PYTORCH_WRAPPER add_halidegrad_float64.pytorch.h -) - -list(APPEND op_libs - add_float32 add_float64 add_grad_float32 add_grad_float64 add_halidegrad_float32 - add_halidegrad_float64 ) # CUDA ops ------------------------------------------------------------------ @@ -162,39 +145,35 @@ if (HELLO_PYTORCH_CUDA) # PyTorch's GPU memory managers can talk to each other. set(cuda_features cuda cuda_capability_61 user_context) - add_halide_library( + add_pytorch_op( add_cuda_float32 FROM add.generator GENERATOR add FEATURES ${cuda_features} PARAMS ${add_types_f32} - PYTORCH_WRAPPER add_cuda_float32.pytorch.h ) - add_halide_library( + add_pytorch_op( add_cuda_float64 FROM add.generator GENERATOR add FEATURES ${cuda_features} PARAMS ${add_types_f64} - PYTORCH_WRAPPER add_cuda_float64.pytorch.h ) - add_halide_library( + add_pytorch_op( add_grad_cuda_float32 FROM add.generator GENERATOR add_grad FEATURES ${cuda_features} PARAMS ${add_grad_types_f32} - PYTORCH_WRAPPER add_grad_cuda_float32.pytorch.h ) - add_halide_library( + add_pytorch_op( add_grad_cuda_float64 FROM add.generator GENERATOR add_grad FEATURES ${cuda_features} PARAMS ${add_grad_types_f64} - PYTORCH_WRAPPER add_grad_cuda_float64.pytorch.h ) - add_halide_library( + add_pytorch_op( add_halidegrad_cuda_float32 FROM add.generator GENERATOR add @@ -202,9 +181,8 @@ if (HELLO_PYTORCH_CUDA) AUTOSCHEDULER Halide::Li2018 FEATURES ${cuda_features} PARAMS ${add_types_f32} - PYTORCH_WRAPPER add_halidegrad_cuda_float32.pytorch.h ) - add_halide_library( + add_pytorch_op( add_halidegrad_cuda_float64 FROM add.generator GENERATOR add @@ -212,12 +190,6 @@ if (HELLO_PYTORCH_CUDA) AUTOSCHEDULER Halide::Li2018 FEATURES ${cuda_features} PARAMS ${add_types_f64} - PYTORCH_WRAPPER add_halidegrad_cuda_float64.pytorch.h - ) - - list(APPEND op_libs - add_cuda_float32 add_cuda_float64 add_grad_cuda_float32 add_grad_cuda_float64 - add_halidegrad_cuda_float32 add_halidegrad_cuda_float64 ) endif () @@ -230,8 +202,9 @@ if (HELLO_PYTORCH_CUDA) string(APPEND wrapper "#include \"HalidePyTorchCudaHelpers.h\"\n") endif () string(APPEND wrapper "#include \"HalidePyTorchHelpers.h\"\n") -foreach (op IN LISTS op_libs) - string(APPEND wrapper "#include \"${op}.pytorch.h\"\n") +foreach (header IN LISTS op_wrapper_headers) + cmake_path(GET header FILENAME header_filename) + string(APPEND wrapper "#include \"${header_filename}\"\n") endforeach () string(APPEND wrapper "\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n") foreach (op IN LISTS op_libs) From 0ab3198f5f6d39f99364397b0b7cb3706e8a57b8 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 22 Jul 2026 11:40:53 -0400 Subject: [PATCH 14/14] Drop comment explaining why auto_viz tests all variants Nothing about the loop needs explaining beyond what the code already says; the comment only made sense as a rationale tied to a bug that's no longer relevant to the present code. Co-Authored-By: Claude Sonnet 5 --- apps/auto_viz/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/auto_viz/CMakeLists.txt b/apps/auto_viz/CMakeLists.txt index d0f63a38a93c..3b8b7030a5f1 100644 --- a/apps/auto_viz/CMakeLists.txt +++ b/apps/auto_viz/CMakeLists.txt @@ -50,8 +50,6 @@ endif () set(IMAGE ${CMAKE_CURRENT_LIST_DIR}/../images/rgb_small.png) if (EXISTS ${IMAGE}) configure_file(${IMAGE} rgb_small.png COPYONLY) - # Exercise every (schedule, direction) combination so a wrong function - # pointer in one variant's dispatch table can't hide behind another. foreach (schedule IN ITEMS naive lessnaive complex) add_test( NAME auto_viz_demo_${schedule}_up