diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c6d0a379bf3..96a8df419caf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,13 @@ enable_testing() # Set up project-wide properties ## +# Import useful standard modules +include(CMakeDependentOption) +include(CheckCXXSymbolExists) + # Make our custom helpers available throughout the project via include(). list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) include(HalideGeneratorHelpers) -include(CMakeDependentOption) # Build Halide as a shared lib by default, but still honor command-line settings. option(BUILD_SHARED_LIBS "Build shared libraries" ON) @@ -67,6 +70,14 @@ if (Halide_CCACHE_BUILD) message(STATUS "Enabling ccache usage for building.") endif () +# Detect whether or not ASAN is enabled +check_cxx_symbol_exists(HALIDE_INTERNAL_USING_ASAN "${Halide_SOURCE_DIR}/src/Util.h" Halide_ASAN_ENABLED) +if (Halide_ASAN_ENABLED) + set(Halide_ANY_SANITIZERS_ENABLED 1) +else () + set(Halide_ANY_SANITIZERS_ENABLED 0) +endif () + # Enable the SPIR-V target if requested (must declare before processing dependencies) option(TARGET_SPIRV "Include SPIR-V target" OFF) diff --git a/CMakePresets.json b/CMakePresets.json index 8e71d3c1efcf..248380ae3d66 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -12,6 +12,15 @@ "binaryDir": "build/${presetName}", "installDir": "install/${presetName}" }, + { + "name": "ci", + "hidden": true, + "inherits": "default", + "toolchainFile": "${sourceDir}/cmake/toolchain.${presetName}.cmake", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, { "name": "windows-only", "hidden": true, @@ -145,6 +154,37 @@ "BUILD_SHARED_LIBS": "NO", "Halide_BUNDLE_LLVM": "YES" } + }, + { + "name": "linux-x64-asan", + "inherits": "ci", + "displayName": "ASAN (Linux x64)", + "description": "Build everything with ASAN enabled", + "cacheVariables": { + "LLVM_ROOT": "$penv{LLVM_ROOT}" + } + } + ], + "buildPresets": [ + { + "name": "linux-x64-asan", + "configurePreset": "linux-x64-asan", + "displayName": "ASAN (Linux x64)", + "description": "Build everything with ASAN enabled" + } + ], + "testPresets": [ + { + "name": "linux-x64-asan", + "configurePreset": "linux-x64-asan", + "displayName": "ASAN (Linux x64)", + "description": "Test everything with ASAN enabled", + "environment": { + "ASAN_OPTIONS": "detect_leaks=0:detect_container_overflow=0" + }, + "output": { + "outputOnFailure": true + } } ] } diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index ace6f8e89d42..0c8b682f8071 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -5,50 +5,56 @@ cmake_minimum_required(VERSION 3.22) project(Halide_apps) +enable_testing() + if (WIN32) option(ENABLE_APPS_HANNK "Build apps/hannk" OFF) else () option(ENABLE_APPS_HANNK "Build apps/hannk" ON) endif () -enable_testing() +function(add_app app_name) + string(TOUPPER "ENABLE_APPS_${app_name}" opt) + option(${opt} "Build apps/${app_name}" ON) + if (${opt}) + add_subdirectory(${app_name}) + endif () +endfunction() -# add_subdirectory(HelloAndroid) # TODO(#5374): missing CMake build -# add_subdirectory(HelloAndroidCamera2) # TODO(#5374): missing CMake build -# add_subdirectory(HelloPyTorch) # TODO(#5374): missing CMake build -# add_subdirectory(HelloWasm) # TODO(#5374): missing CMake build -# add_subdirectory(HelloiOS) # TODO(#5374): missing CMake build -# add_subdirectory(auto_viz) # TODO(#5374): missing CMake build -add_subdirectory(bgu) -add_subdirectory(bilateral_grid) -add_subdirectory(blur) -add_subdirectory(c_backend) -add_subdirectory(camera_pipe) -add_subdirectory(conv_layer) -add_subdirectory(cuda_mat_mul) -add_subdirectory(depthwise_separable_conv) -add_subdirectory(fft) -if (ENABLE_APPS_HANNK) - add_subdirectory(hannk) -endif () -add_subdirectory(harris) -# add_subdirectory(hexagon_benchmarks) # TODO(#5374): missing CMake build -# add_subdirectory(hexagon_dma) # TODO(#5374): missing CMake build -add_subdirectory(hist) -add_subdirectory(iir_blur) -add_subdirectory(interpolate) -add_subdirectory(lens_blur) -add_subdirectory(linear_algebra) -# add_subdirectory(linear_blur) # TODO(#5374): missing CMake build -add_subdirectory(local_laplacian) -add_subdirectory(max_filter) -add_subdirectory(nl_means) -# add_subdirectory(nn_ops) # TODO(#5374): missing CMake build -# add_subdirectory(onnx) # TODO(#5374): missing CMake build -# add_subdirectory(openglcompute) # TODO(#5374): missing CMake build -add_subdirectory(resize) -# add_subdirectory(resnet_50) # TODO(#5374): missing CMake build -# add_subdirectory(simd_op_check) # TODO(#5374): missing CMake build -add_subdirectory(stencil_chain) -add_subdirectory(unsharp) -add_subdirectory(wavelet) +# add_app(HelloAndroid) # TODO(#5374): missing CMake build +# add_app(HelloAndroidCamera2) # TODO(#5374): missing CMake build +# add_app(HelloPyTorch) # TODO(#5374): missing CMake build +# add_app(HelloWasm) # TODO(#5374): missing CMake build +# add_app(HelloiOS) # TODO(#5374): missing CMake build +# add_app(auto_viz) # TODO(#5374): missing CMake build +add_app(bgu) +add_app(bilateral_grid) +add_app(blur) +add_app(c_backend) +add_app(camera_pipe) +add_app(conv_layer) +add_app(cuda_mat_mul) +add_app(depthwise_separable_conv) +add_app(fft) +add_app(hannk) +add_app(harris) +# add_app(hexagon_benchmarks) # TODO(#5374): missing CMake build +# add_app(hexagon_dma) # TODO(#5374): missing CMake build +add_app(hist) +add_app(iir_blur) +add_app(interpolate) +add_app(lens_blur) +add_app(linear_algebra) +# add_app(linear_blur) # TODO(#5374): missing CMake build +add_app(local_laplacian) +add_app(max_filter) +add_app(nl_means) +# add_app(nn_ops) # TODO(#5374): missing CMake build +# add_app(onnx) # TODO(#5374): missing CMake build +# add_app(openglcompute) # TODO(#5374): missing CMake build +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/CMakePresets.json b/apps/CMakePresets.json new file mode 100644 index 000000000000..788ffeb0b6e1 --- /dev/null +++ b/apps/CMakePresets.json @@ -0,0 +1,60 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 22, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "hidden": true, + "binaryDir": "build/${presetName}", + "installDir": "install/${presetName}" + }, + { + "name": "ci", + "hidden": true, + "inherits": "default", + "toolchainFile": "${sourceDir}/../cmake/toolchain.${presetName}.cmake", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "linux-x64-asan", + "inherits": "ci", + "displayName": "ASAN (Linux x64)", + "description": "Build everything with ASAN enabled", + "cacheVariables": { + "LLVM_ROOT": "$penv{LLVM_ROOT}", + "ENABLE_APPS_BGU": "OFF" + } + } + ], + "buildPresets": [ + { + "name": "linux-x64-asan", + "configurePreset": "linux-x64-asan", + "displayName": "ASAN (Linux x64)", + "description": "Build everything with ASAN enabled", + "environment": { + "ASAN_OPTIONS": "detect_leaks=0:detect_container_overflow=0" + } + } + ], + "testPresets": [ + { + "name": "linux-x64-asan", + "configurePreset": "linux-x64-asan", + "displayName": "ASAN (Linux x64)", + "description": "Test everything with ASAN enabled", + "environment": { + "ASAN_OPTIONS": "detect_leaks=0:detect_container_overflow=0" + }, + "output": { + "outputOnFailure": true + } + } + ] +} diff --git a/apps/fft/CMakeLists.txt b/apps/fft/CMakeLists.txt index d126c60b6647..95de33cec237 100644 --- a/apps/fft/CMakeLists.txt +++ b/apps/fft/CMakeLists.txt @@ -45,7 +45,7 @@ set_tests_properties(fft_aot_test PROPERTIES foreach (i IN ITEMS 8 12 16 24 32 48) add_test(NAME bench${i}x${i} COMMAND bench_fft ${i} ${i} "${CMAKE_CURRENT_BINARY_DIR}") - set_tests_properties(bench${i}x${i} + set_tests_properties(bench${i}x${i} PROPERTIES LABELS fft ENVIRONMENT "PATH=$>") diff --git a/cmake/HalideGeneratorHelpers.cmake b/cmake/HalideGeneratorHelpers.cmake index 67cdaa04f9b7..2bcac8bba4c5 100644 --- a/cmake/HalideGeneratorHelpers.cmake +++ b/cmake/HalideGeneratorHelpers.cmake @@ -80,10 +80,11 @@ function(_Halide_try_load_generators) # Communicate found information to the caller set(${ARG_PACKAGE_NAME}_FOUND "${${ARG_PACKAGE_NAME}_FOUND}" PARENT_SCOPE) - if (NOT ${ARG_PACKAGE_NAME}_FOUND AND CMAKE_CROSSCOMPILING) + if (NOT ${ARG_PACKAGE_NAME}_FOUND AND CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR) message(WARNING - "${ARG_PACKAGE_NAME} were not found and it looks like you are cross-compiling. " - "This is likely to fail. Please set -D${ARG_PACKAGE_NAME}_ROOT=... at the CMake " + "'${ARG_PACKAGE_NAME}' was not found and it looks like you " + "are cross-compiling without an emulator. This is likely to " + "fail. Please set -D${ARG_PACKAGE_NAME}_ROOT=... at the CMake " "command line to the build directory of a host-built ${PROJECT_NAME}.") endif () endif () diff --git a/cmake/toolchain.linux-x64-asan.cmake b/cmake/toolchain.linux-x64-asan.cmake new file mode 100644 index 000000000000..b582b5791715 --- /dev/null +++ b/cmake/toolchain.linux-x64-asan.cmake @@ -0,0 +1,32 @@ +# Toolchain for compiling with ASAN enabled on a Linux-x86-64 host. +# This is done as a "crosscompile" because we must use our LLVM version +# of clang for *all* compilation (rather than using it just for bitcode +# and letting the host compiler, eg gcc, handle everything else); ASAN +# essentially requires everything to be compiled with matching versions +# of the same compiler. +# +# Note: requires LLVM to be built with -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" +# +# Note: only tested with LLVM/Clang 16 as of this comment. Earlier versions +# may likely work but are not tested. + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR i686) + +set(CMAKE_C_COMPILER ${LLVM_ROOT}/bin/clang) +set(CMAKE_CXX_COMPILER ${LLVM_ROOT}/bin/clang++) +set(CMAKE_LINKER ${LLVM_ROOT}/bin/ld.lld) + +set(CMAKE_C_FLAGS_INIT "-fsanitize=address") +set(CMAKE_CXX_FLAGS_INIT "-fsanitize=address") + +set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=${CMAKE_LINKER}") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=${CMAKE_LINKER}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=${CMAKE_LINKER}") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +set(CMAKE_CROSSCOMPILING_EMULATOR /usr/bin/env) diff --git a/python_bindings/CMakeLists.txt b/python_bindings/CMakeLists.txt index 119ae9bbbea3..bcf9e3efcf77 100644 --- a/python_bindings/CMakeLists.txt +++ b/python_bindings/CMakeLists.txt @@ -50,9 +50,25 @@ if (NOT Halide_ENABLE_RTTI OR NOT Halide_ENABLE_EXCEPTIONS) endif () ## -# A helper for creating tests with correct PYTHONPATH +# A helper for creating tests with correct PYTHONPATH and sanitizer preloading ## +if (Halide_ASAN_ENABLED) + if (NOT DEFINED Halide_Python_ASAN_LIBRARY) + # TODO: this assumes clang-on-Linux, we could be smarter here and check + # CMAKE_CXX_COMPILER_ID to behave differently on GNU, AppleClang, or + # MSVC. + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} "-print-file-name=libclang_rt.asan.so" + OUTPUT_VARIABLE Halide_Python_ASAN_LIBRARY + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif () + + set(Halide_Python_ASAN_LIBRARY "${Halide_Python_ASAN_LIBRARY}" + CACHE FILEPATH "Library to preload when running Python tests.") +endif () + function(add_python_test) cmake_parse_arguments(ARG "" "FILE;LABEL" "PYTHONPATH;ENVIRONMENT" ${ARGN}) @@ -60,6 +76,13 @@ function(add_python_test) list(TRANSFORM ARG_PYTHONPATH PREPEND "PYTHONPATH=path_list_prepend:") list(PREPEND ARG_ENVIRONMENT "HL_TARGET=${Halide_TARGET}") + if (Halide_Python_ASAN_LIBRARY) + if (APPLE) + list(PREPEND ARG_ENVIRONMENT "DYLD_INSERT_LIBRARIES=${Halide_Python_ASAN_LIBRARY}") + else () + list(PREPEND ARG_ENVIRONMENT "LD_PRELOAD=${Halide_Python_ASAN_LIBRARY}") + endif () + endif () cmake_path(GET ARG_FILE STEM test_name) set(test_name "${ARG_LABEL}_${test_name}") diff --git a/src/Target.cpp b/src/Target.cpp index dd0e0d5ab416..7c6dbc64f5e0 100644 --- a/src/Target.cpp +++ b/src/Target.cpp @@ -567,19 +567,18 @@ Target get_target_from_environment() { Target get_jit_target_from_environment() { Target host = get_host_target(); host.set_feature(Target::JIT); -#if defined(__has_feature) -#if __has_feature(address_sanitizer) +// Note, we must include Util.h for these to be defined properly (or not) +#ifdef HALIDE_INTERNAL_USING_ASAN host.set_feature(Target::ASAN); #endif -#if __has_feature(memory_sanitizer) +#ifdef HALIDE_INTERNAL_USING_MSAN host.set_feature(Target::MSAN); #endif -#if __has_feature(thread_sanitizer) +#ifdef HALIDE_INTERNAL_USING_TSAN host.set_feature(Target::TSAN); #endif -#if __has_feature(coverage_sanitizer) +#ifdef HALIDE_INTERNAL_USING_COVSAN host.set_feature(Target::SanitizerCoverage); -#endif #endif string target = Internal::get_env_variable("HL_JIT_TARGET"); if (target.empty()) { diff --git a/src/Util.cpp b/src/Util.cpp index d99d5eb4a3a0..954f1378f726 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -661,6 +661,9 @@ size_t get_compiler_stack_size() { namespace Internal { +#ifdef HALIDE_INTERNAL_USING_ASAN +// nothing +#else namespace { // We can't reliably pass arguments through makecontext, because // the calling convention involves an invalid function pointer @@ -668,6 +671,7 @@ namespace { // platforms, so we use a thread local to pass arguments. thread_local void *run_with_large_stack_arg = nullptr; } // namespace +#endif void run_with_large_stack(const std::function &action) { if (stack_size.size == 0) { @@ -677,7 +681,6 @@ void run_with_large_stack(const std::function &action) { } #if _WIN32 - // Only exists for its address, which is used to compute remaining stack space. ULONG_PTR approx_stack_pos; @@ -719,6 +722,14 @@ void run_with_large_stack(const std::function &action) { #else // On posixy systems we have makecontext / swapcontext +#ifdef HALIDE_INTERNAL_USING_ASAN + // ... unless we are compiling under ASAN, in which case we + // will get a zillion warnings about ASAN not supporting makecontext/swapcontext + // and the possibility of false positives. Just skip the extra stack space, I guess? + action(); + return; +#else + #ifdef HALIDE_WITH_EXCEPTIONS struct Args { const std::function &run; @@ -783,6 +794,8 @@ void run_with_large_stack(const std::function &action) { } #endif +#endif // not ADDRESS_SANITIZER + #endif } diff --git a/src/Util.h b/src/Util.h index 71228eb0da3b..00489b1b3f34 100644 --- a/src/Util.h +++ b/src/Util.h @@ -45,6 +45,32 @@ #define HALIDE_NO_USER_CODE_INLINE HALIDE_NEVER_INLINE #endif +// Clang uses __has_feature() for sanitizers... +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define HALIDE_INTERNAL_USING_ASAN +#endif +#if __has_feature(memory_sanitizer) +#define HALIDE_INTERNAL_USING_MSAN +#endif +#if __has_feature(thread_sanitizer) +#define HALIDE_INTERNAL_USING_TSAN +#endif +#if __has_feature(coverage_sanitizer) +#define HALIDE_INTERNAL_USING_COVSAN +#endif +#if __has_feature(undefined_behavior_sanitizer) +#define HALIDE_INTERNAL_USING_UBSAN +#endif +#endif + +// ...but GCC/MSVC don't like __has_feature, so handle them separately. +// (Only AddressSanitizer for now, not sure if any others are well-supported +// outside of Clang. +#if defined(__SANITIZE_ADDRESS__) && !defined(HALIDE_INTERNAL_USING_ASAN) +#define HALIDE_INTERNAL_USING_ASAN +#endif + namespace Halide { /** Load a plugin in the form of a dynamic library (e.g. for custom autoschedulers). diff --git a/test/correctness/tracing_stack.cpp b/test/correctness/tracing_stack.cpp index d3fe04d548b5..6ea21e48ce57 100644 --- a/test/correctness/tracing_stack.cpp +++ b/test/correctness/tracing_stack.cpp @@ -64,6 +64,13 @@ void signal_handler(int signum) { } // namespace int main(int argc, char **argv) { +#ifdef HALIDE_INTERNAL_USING_ASAN + // ASAN also needs to intercept the SIGSEGV signal handler; + // we could probably make these work together, but it's + // also probably not worth the effort. + printf("[SKIP] tracing_stack does not run under ASAN.\n"); + return 0; +#endif signal(SIGSEGV, signal_handler); signal(SIGBUS, signal_handler); diff --git a/test/correctness/unroll_huge_mux.cpp b/test/correctness/unroll_huge_mux.cpp index 9a6307d68414..233ee038c4e8 100644 --- a/test/correctness/unroll_huge_mux.cpp +++ b/test/correctness/unroll_huge_mux.cpp @@ -3,6 +3,11 @@ using namespace Halide; int main(int argc, char **argv) { +#ifdef HALIDE_INTERNAL_USING_ASAN + printf("[SKIP] unroll_huge_mux requires set_compiler_stack_size() to work properly, which is disabled under ASAN.\n"); + return 0; +#endif + Func f; Var x; diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 0e462530ff9b..d1e869d97f07 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -1,3 +1,9 @@ +if (Halide_ANY_SANITIZERS_ENABLED) + # All sanitizers impact performance, so don't even bother with this test suite + message(STATUS "Skipping all performance testing because at least one Sanitizer is enabled.") + return() +endif() + tests(GROUPS performance SOURCES async_gpu.cpp diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index 11265d2647c5..7c1b1f656132 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -56,13 +56,10 @@ if (TARGET_NVPTX) # so we can build the final executable. add_tutorial(lesson_10_aot_compilation_generate.cpp) - # LLVM may leak memory during Halide compilation. If projects are built with address sanitizer enabled, - # this may cause generators to fail, making it hard to use Halide and address sanitizer at the same time. - # To work around this, we execute generators with an environment setting to disable leak checking. set(FILTER_LIB "lesson_10_halide${CMAKE_STATIC_LIBRARY_SUFFIX}") add_custom_command(OUTPUT lesson_10_halide.h "${FILTER_LIB}" DEPENDS lesson_10_aot_compilation_generate - COMMAND ${CMAKE_COMMAND} -E env "ASAN_OPTIONS=detect_leaks=0" $ + COMMAND lesson_10_aot_compilation_generate VERBATIM) add_custom_target(exec_lesson_10_aot_compilation_generate DEPENDS lesson_10_halide.h "${FILTER_LIB}")