diff --git a/python_bindings/src/PyEnums.cpp b/python_bindings/src/PyEnums.cpp index d518030eb860..babad378cea0 100644 --- a/python_bindings/src/PyEnums.cpp +++ b/python_bindings/src/PyEnums.cpp @@ -154,6 +154,7 @@ void define_enums(py::module &m) { .value("LLVMLargeCodeModel", Target::Feature::LLVMLargeCodeModel) .value("RVV", Target::Feature::RVV) .value("ARMv81a", Target::Feature::ARMv81a) + .value("SANCOV", Target::Feature::SANCOV) .value("FeatureEnd", Target::Feature::FeatureEnd); py::enum_(m, "TypeCode") diff --git a/src/CodeGen_LLVM.cpp b/src/CodeGen_LLVM.cpp index 76256b5cdbb5..0132d050dbcb 100644 --- a/src/CodeGen_LLVM.cpp +++ b/src/CodeGen_LLVM.cpp @@ -1147,6 +1147,21 @@ void CodeGen_LLVM::optimize_module() { OptimizationLevel level = OptimizationLevel::O3; + if (get_target().has_feature(Target::SANCOV)) { + pb.registerOptimizerLastEPCallback( + [](ModulePassManager &mpm, OptimizationLevel level) { + SanitizerCoverageOptions sancov_options; + // Mirror what -fsanitize=fuzzer-no-link would enable. + sancov_options.CoverageType = SanitizerCoverageOptions::SCK_Edge; + sancov_options.IndirectCalls = true; + sancov_options.TraceCmp = true; + sancov_options.Inline8bitCounters = true; + sancov_options.PCTable = true; + sancov_options.StackDepth = true; + mpm.addPass(ModuleSanitizerCoveragePass(sancov_options)); + }); + } + if (get_target().has_feature(Target::ASAN)) { pb.registerPipelineStartEPCallback([&](ModulePassManager &mpm, OptimizationLevel) { mpm.addPass(RequireAnalysisPass()); diff --git a/src/LLVM_Headers.h b/src/LLVM_Headers.h index 37b1266afc7f..bd66a2721253 100644 --- a/src/LLVM_Headers.h +++ b/src/LLVM_Headers.h @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Module.cpp b/src/Module.cpp index e15d902045cc..c4c7826c1e1e 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -837,7 +837,7 @@ void compile_multitarget(const std::string &fn_name, user_error << "All Targets must have matching arch-bits-os for compile_multitarget.\n"; } // Some features must match across all targets. - static const std::array must_match_features = {{ + static const std::array must_match_features = {{ Target::ASAN, Target::CPlusPlusMangling, Target::Debug, @@ -846,6 +846,7 @@ void compile_multitarget(const std::string &fn_name, Target::MSAN, Target::NoRuntime, Target::TSAN, + Target::SANCOV, Target::UserContext, }}; for (auto f : must_match_features) { diff --git a/src/Target.cpp b/src/Target.cpp index ad765b7a90b5..7d571a32ff61 100644 --- a/src/Target.cpp +++ b/src/Target.cpp @@ -384,6 +384,7 @@ const std::map feature_name_map = { {"llvm_large_code_model", Target::LLVMLargeCodeModel}, {"rvv", Target::RVV}, {"armv81a", Target::ARMv81a}, + {"sancov", Target::SANCOV}, // NOTE: When adding features to this map, be sure to update PyEnums.cpp as well. }; @@ -420,6 +421,9 @@ Target get_jit_target_from_environment() { #if __has_feature(thread_sanitizer) host.set_feature(Target::TSAN); #endif +#if __has_feature(coverage_sanitizer) + host.set_feature(Target::SANCOV); +#endif #endif string target = Internal::get_env_variable("HL_JIT_TARGET"); if (target.empty()) { @@ -994,7 +998,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result) // clang-format on // clang-format off - const std::array matching_features = {{ + const std::array matching_features = {{ ASAN, Debug, HexagonDma, @@ -1004,6 +1008,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result) SoftFloatABI, TSAN, WasmThreads, + SANCOV, }}; // clang-format on @@ -1032,7 +1037,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result) } if ((features & matching_mask) != (other.features & matching_mask)) { - Internal::debug(1) << "runtime targets must agree on SoftFloatABI, Debug, TSAN, ASAN, MSAN, HVX, HexagonDma, and HVX_shared_object\n" + Internal::debug(1) << "runtime targets must agree on SoftFloatABI, Debug, TSAN, ASAN, MSAN, HVX, HexagonDma, HVX_shared_object, SANCOV\n" << " this: " << *this << "\n" << " other: " << other << "\n"; return false; diff --git a/src/Target.h b/src/Target.h index afa48a63b5c5..6aee9a95419b 100644 --- a/src/Target.h +++ b/src/Target.h @@ -131,6 +131,7 @@ struct Target { LLVMLargeCodeModel = halide_llvm_large_code_model, RVV = halide_target_feature_rvv, ARMv81a = halide_target_feature_armv81a, + SANCOV = halide_target_feature_sancov, FeatureEnd = halide_target_feature_end }; Target() = default; diff --git a/src/runtime/HalideRuntime.h b/src/runtime/HalideRuntime.h index 1bc5ce794106..41f6764fabbf 100644 --- a/src/runtime/HalideRuntime.h +++ b/src/runtime/HalideRuntime.h @@ -1346,6 +1346,7 @@ typedef enum halide_target_feature_t { halide_llvm_large_code_model, ///< Use the LLVM large code model to compile halide_target_feature_rvv, ///< Enable RISCV "V" Vector Extension halide_target_feature_armv81a, ///< Enable ARMv8.1-a instructions + halide_target_feature_sancov, ///< Enable hooks for SanitizerCoverage support. halide_target_feature_end ///< A sentinel. Every target is considered to have this feature, and setting this feature does nothing. } halide_target_feature_t; diff --git a/test/generator/CMakeLists.txt b/test/generator/CMakeLists.txt index e5986c05bc15..0d1423de74eb 100644 --- a/test/generator/CMakeLists.txt +++ b/test/generator/CMakeLists.txt @@ -370,6 +370,10 @@ add_halide_library(metadata_tester_ucon # msan_generator.cpp halide_define_aot_test(msan FEATURES msan) +# sancov_aottest.cpp +# sancov_generator.cpp +halide_define_aot_test(sancov FEATURES sancov) + # multitarget_aottest.cpp # multitarget_generator.cpp halide_define_aot_test(multitarget diff --git a/test/generator/sancov_aottest.cpp b/test/generator/sancov_aottest.cpp new file mode 100644 index 000000000000..2ff18cf83092 --- /dev/null +++ b/test/generator/sancov_aottest.cpp @@ -0,0 +1,154 @@ +#include "HalideBuffer.h" +#include "HalideRuntime.h" +#include "sancov.h" + +#include +#include +#include +#include + +using namespace std; +using namespace Halide::Runtime; + +bool seen_8bit_counters_init = false; +bool seen_pcs_init = false; +bool seen_trace_cmp1 = false; +bool seen_trace_cmp4 = false; +bool seen_trace_cmp8 = false; +bool seen_trace_const_cmp1 = false; +bool seen_trace_const_cmp2 = false; +bool seen_trace_const_cmp4 = false; +bool seen_trace_const_cmp8 = false; +bool seen_trace_pc_indir = false; +bool seen_trace_switch = false; + +// Used by -fsanitize-coverage=stack-depth to track stack depth +__attribute__((tls_model("initial-exec"))) thread_local uintptr_t __sancov_lowest_stack; + +extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *Start, uint8_t *Stop) { + seen_8bit_counters_init = true; +} + +extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, + const uintptr_t *pcs_end) { + seen_pcs_init = true; +} + +extern "C" void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) { + seen_trace_cmp1 = true; +} + +extern "C" void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) { + seen_trace_cmp4 = true; +} + +extern "C" void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) { + seen_trace_cmp8 = true; +} + +extern "C" void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) { + seen_trace_const_cmp1 = true; +} + +extern "C" void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) { + seen_trace_const_cmp2 = true; +} + +extern "C" void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) { + seen_trace_const_cmp4 = true; +} + +extern "C" void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) { + seen_trace_const_cmp8 = true; +} + +extern "C" void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) { + seen_trace_switch = true; +} + +extern "C" void __sanitizer_cov_trace_pc_indir(uintptr_t Callee) { + seen_trace_pc_indir = true; +} + +template +void clear_out(T &image) { + image.for_each_element([&](int x, int y, int c) { + image(x, y, c) = -42; + }); +} + +template +void verify_out(const T &image) { + image.for_each_element([&](int x, int y, int c) { + int expected = 42 + c; + int actual = image(x, y, c); + if (actual != expected) { + fprintf(stderr, "Failure @ %d %d %d: expected %d, got %d\n", x, y, c, expected, actual); + exit(-1); + } + }); +} + +void verify_coverage(bool should_have_seen) { + if (seen_8bit_counters_init != true) { + fprintf(stderr, "Failure @ seen_8bit_counters_init"); + exit(-1); + } + if (seen_pcs_init != true) { + fprintf(stderr, "Failure @ seen_pcs_init"); + exit(-1); + } + if (seen_trace_cmp1 != false) { + fprintf(stderr, "Failure @ seen_trace_cmp1"); + exit(-1); + } + if (seen_trace_cmp4 != false) { + fprintf(stderr, "Failure @ seen_trace_cmp4"); + exit(-1); + } + if (seen_trace_cmp8 != false) { + fprintf(stderr, "Failure @ seen_trace_cmp8"); + exit(-1); + } + if (seen_trace_const_cmp1 != false) { + fprintf(stderr, "Failure @ seen_trace_const_cmp1"); + exit(-1); + } + if (seen_trace_const_cmp2 != false) { + fprintf(stderr, "Failure @ seen_trace_const_cmp2"); + exit(-1); + } + if (seen_trace_const_cmp4 != should_have_seen) { + fprintf(stderr, "Failure @ seen_trace_const_cmp4"); + exit(-1); + } + if (seen_trace_const_cmp8 != should_have_seen) { + fprintf(stderr, "Failure @ seen_trace_const_cmp8"); + exit(-1); + } + if (seen_trace_pc_indir != false) { + fprintf(stderr, "Failure @ seen_trace_pc_indir"); + exit(-1); + } + if (seen_trace_switch != should_have_seen) { + fprintf(stderr, "Failure @ seen_trace_switch"); + exit(-1); + } +} + +//----------------------------------------------------------------------------- + +int main() { + auto out = Buffer(4, 4, 3); + clear_out(out); + verify_coverage(/*should_have_seen=*/false); + if (sancov(out) != 0) { + fprintf(stderr, "Failure!\n"); + exit(-1); + } + verify_coverage(/*should_have_seen=*/true); + verify_out(out); + + printf("Success!\n"); + return 0; +} diff --git a/test/generator/sancov_generator.cpp b/test/generator/sancov_generator.cpp new file mode 100644 index 000000000000..eef90bdadd4d --- /dev/null +++ b/test/generator/sancov_generator.cpp @@ -0,0 +1,25 @@ +#include "Halide.h" + +namespace { + +class SANCOV : public Halide::Generator { +public: + Output> output{"output", 3}; + + void generate() { + // Currently the test just exercises Target::SANCOV + output(x, y, c) = cast(42 + c); + } + + void schedule() { + output.dim(0).set_stride(Expr()).set_extent(4).dim(1).set_extent(4).dim(2).set_extent(3); + } + +private: + // Currently the test just exercises Target::SANCOV + Var x, y, c; +}; + +} // namespace + +HALIDE_REGISTER_GENERATOR(SANCOV, sancov)