//===----------------------------------------------------------------------===// // // Part of CUDA Experimental in CUDA C++ Core Libraries, // under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. // //===----------------------------------------------------------------------===// #include #include // std::cerr #include // std::optional #include #include #include #include "algorithm_execution.h" #include "build_result_caching.h" #include "test_util.h" #include using BuildResultT = cccl_device_scan_build_result_t; struct scan_cleanup { CUresult operator()(BuildResultT* build_data) const noexcept { return cccl_device_scan_cleanup(build_data); } }; static std::string init_kind_as_key(cccl_init_kind_t k) { switch (k) { case cccl_init_kind_t::CCCL_NO_INIT: return "NONE"; case cccl_init_kind_t::CCCL_FUTURE_VALUE_INIT: return "FUT"; case cccl_init_kind_t::CCCL_VALUE_INIT: return "VAL"; } throw std::runtime_error("Invalid init kind"); } template std::optional make_scan_key(bool inclusive, cccl_init_kind_t init_kind) { const std::string parts[] = { KeyBuilder::type_as_key(), KeyBuilder::bool_as_key(inclusive), init_kind_as_key(init_kind)}; return KeyBuilder::join(parts); } using scan_deleter = BuildResultDeleter; using scan_build_cache_t = build_cache_t>; template auto& get_cache() { return fixture::get_or_create().get_value(); } template struct scan_build { CUresult operator()( BuildResultT* build_ptr, bool inclusive, cccl_init_kind_t init_kind, cccl_iterator_t input, cccl_iterator_t output, uint64_t, cccl_op_t op, cccl_value_t init, int cc_major, int cc_minor, const char* cub_path, const char* thrust_path, const char* libcudacxx_path, const char* ctk_path) const noexcept { return cccl_device_scan_build( build_ptr, input, output, op, init.type, inclusive, init_kind, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path); } CUresult operator()( BuildResultT* build_ptr, bool inclusive, cccl_init_kind_t init_kind, cccl_iterator_t input, cccl_iterator_t output, uint64_t, cccl_op_t op, cccl_iterator_t init, int cc_major, int cc_minor, const char* cub_path, const char* thrust_path, const char* libcudacxx_path, const char* ctk_path) const noexcept { return cccl_device_scan_build( build_ptr, input, output, op, init.value_type, inclusive, init_kind, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path); } CUresult operator()( BuildResultT* build_ptr, bool inclusive, cccl_init_kind_t init_kind, cccl_iterator_t input, cccl_iterator_t output, uint64_t, cccl_op_t op, void* /*init*/, int cc_major, int cc_minor, const char* cub_path, const char* thrust_path, const char* libcudacxx_path, const char* ctk_path) const noexcept { return cccl_device_scan_build( build_ptr, input, output, op, input.value_type, // The type is used to determine the accumulator type inclusive, init_kind, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path); } static bool should_check_sass(int cc_major) { // TODO: add a check for NVRTC version; ref nvbug 5243118 return !(Disable75SassCheck && DisableForOtherArches) && (!Disable75SassCheck || cc_major > 7) && cc_major < 9; } }; struct scan_run { template CUresult operator()( BuildResultT build, void* temp_storage, size_t* temp_storage_nbytes, bool inclusive, cccl_init_kind_t /*init_kind*/, Ts... args) const noexcept { if (inclusive) { return cccl_device_inclusive_scan(build, temp_storage, temp_storage_nbytes, args...); } else { return cccl_device_exclusive_scan(build, temp_storage, temp_storage_nbytes, args...); } } }; struct scan_run_future_value { template CUresult operator()( BuildResultT build, void* temp_storage, size_t* temp_storage_nbytes, bool inclusive, cccl_init_kind_t /*init_kind*/, Ts... args) const noexcept { if (inclusive) { return cccl_device_inclusive_scan_future_value(build, temp_storage, temp_storage_nbytes, args...); } else { return cccl_device_exclusive_scan_future_value(build, temp_storage, temp_storage_nbytes, args...); } } }; struct scan_run_no_init { template CUresult operator()( BuildResultT build, void* temp_storage, size_t* temp_storage_nbytes, bool /*inclusive*/, cccl_init_kind_t /*init_kind*/, cccl_iterator_t d_in, cccl_iterator_t d_out, uint64_t num_items, cccl_op_t op, void* /*init*/, Rest... args) const noexcept { return cccl_device_inclusive_scan_no_init( build, temp_storage, temp_storage_nbytes, d_in, d_out, num_items, op, args...); } }; template void scan(cccl_iterator_t input, cccl_iterator_t output, uint64_t num_items, cccl_op_t op, cccl_value_t init, bool inclusive, std::optional& cache, const std::optional& lookup_key) { AlgorithmExecute, scan_cleanup, scan_run, BuildCache, KeyT>( cache, lookup_key, inclusive, cccl_init_kind_t::CCCL_VALUE_INIT, input, output, num_items, op, init); } template void scan(cccl_iterator_t input, cccl_iterator_t output, uint64_t num_items, cccl_op_t op, cccl_iterator_t init, bool inclusive, std::optional& cache, const std::optional& lookup_key) { AlgorithmExecute, scan_cleanup, scan_run_future_value, BuildCache, KeyT>( cache, lookup_key, inclusive, cccl_init_kind_t::CCCL_FUTURE_VALUE_INIT, input, output, num_items, op, init); } template void scan(cccl_iterator_t input, cccl_iterator_t output, uint64_t num_items, cccl_op_t op, bool inclusive, std::optional& cache, const std::optional& lookup_key) { AlgorithmExecute, scan_cleanup, scan_run_no_init, BuildCache, KeyT>( cache, lookup_key, inclusive, cccl_init_kind_t::CCCL_NO_INIT, input, output, num_items, op, nullptr); } // ============== // Test section // ============== using integral_types = c2h::type_list; struct Scan_IntegralTypes_Fixture_Tag; C2H_TEST("Scan works with integral types", "[scan]", integral_types) { using T = c2h::get<0, TestType>; const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); const std::vector input = generate(num_items); const std::vector output(num_items, 0); pointer_t input_ptr(input); pointer_t output_ptr(output); value_t init{T{42}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items, 0); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct Scan_IntegralTypes_WellKnown_Fixture_Tag; C2H_TEST("Scan works with integral types with well-known operations", "[scan][well_known]", integral_types) { using T = c2h::get<0, TestType>; const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 16))); cccl_op_t op = make_well_known_binary_operation(); const std::vector input = generate(num_items); const std::vector output(num_items, 0); pointer_t input_ptr(input); pointer_t output_ptr(output); value_t init{T{42}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items, 0); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct InclusiveScan_IntegralTypes_Fixture_Tag; C2H_TEST("Inclusive Scan works with integral types", "[scan]", integral_types) { using T = c2h::get<0, TestType>; const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); const std::vector input = generate(num_items); const std::vector output(num_items, 0); pointer_t input_ptr(input); pointer_t output_ptr(output); value_t init{T{42}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(true, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init, true, build_cache, test_key); std::vector expected(num_items, 0); std::inclusive_scan(input.begin(), input.end(), expected.begin(), std::plus<>{}, init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct pair { short a; size_t b; bool operator==(const pair& other) const { return a == other.a && b == other.b; } }; struct Scan_CustomTypes_Fixture_Tag; C2H_TEST("Scan works with custom types", "[scan]") { const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 24))); operation_t op = make_operation("op", R"(struct pair { short a; size_t b; }; extern "C" __device__ void op(void* lhs_ptr, void* rhs_ptr, void* out_ptr) { pair* lhs = static_cast(lhs_ptr); pair* rhs = static_cast(rhs_ptr); pair* out = static_cast(out_ptr); *out = pair{ lhs->a + rhs->a, lhs->b + rhs->b }; })"); const std::vector a = generate(num_items); const std::vector b = generate(num_items); std::vector input(num_items); std::vector output(num_items); for (std::size_t i = 0; i < num_items; ++i) { input[i] = pair{a[i], b[i]}; } pointer_t input_ptr(input); pointer_t output_ptr(output); value_t init{pair{4, 2}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items, {0, 0}); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value, [](const pair& lhs, const pair& rhs) { return pair{short(lhs.a + rhs.a), lhs.b + rhs.b}; }); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct Scan_CustomTypes_WellKnown_Fixture_Tag; C2H_TEST("Scan works with custom types with well-known operations", "[scan][well_known]") { const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 24))); operation_t op_state = make_operation("op", R"(struct pair { short a; size_t b; }; extern "C" __device__ void op(void* lhs_ptr, void* rhs_ptr, void* out_ptr) { pair* lhs = static_cast(lhs_ptr); pair* rhs = static_cast(rhs_ptr); pair* out = static_cast(out_ptr); *out = pair{ lhs->a + rhs->a, lhs->b + rhs->b }; })"); cccl_op_t op = op_state; op.type = cccl_op_kind_t::CCCL_PLUS; const std::vector a = generate(num_items); const std::vector b = generate(num_items); std::vector input(num_items); std::vector output(num_items); for (std::size_t i = 0; i < num_items; ++i) { input[i] = pair{a[i], b[i]}; } pointer_t input_ptr(input); pointer_t output_ptr(output); value_t init{pair{4, 2}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items, {0, 0}); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value, [](const pair& lhs, const pair& rhs) { return pair{short(lhs.a + rhs.a), lhs.b + rhs.b}; }); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct Scan_InputIterators_Fixture_Tag; C2H_TEST("Scan works with input iterators", "[scan]") { const std::size_t num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); iterator_t> input_it = make_counting_iterator("int"); input_it.state.value = 0; pointer_t output_it(num_items); value_t init{42}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_it, output_it, num_items, op, init, false, build_cache, test_key); // vector storing a sequence of values 0, 1, 2, ..., num_items - 1 std::vector input(num_items); std::iota(input.begin(), input.end(), 0); std::vector expected(num_items); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_it)); } } struct Scan_OutputIterators_Fixture_Tag; C2H_TEST("Scan works with output iterators", "[scan]") { const int num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); iterator_t> output_it = make_random_access_iterator(iterator_kind::OUTPUT, "int", "out", " * 2"); const std::vector input = generate(num_items); pointer_t input_it(input); pointer_t inner_output_it(num_items); output_it.state.data = inner_output_it.ptr; value_t init{42}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_it, output_it, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); std::transform(expected.begin(), expected.end(), expected.begin(), [](int x) { return x * 2; }); if (num_items > 0) { REQUIRE(expected == std::vector(inner_output_it)); } } struct Scan_ReverseInputIterators_Fixture_Tag; C2H_TEST("Scan works with reverse input iterators", "[scan]") { const std::size_t num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); iterator_t> input_it = make_reverse_iterator(iterator_kind::INPUT, "int"); std::vector input = generate(num_items); pointer_t input_ptr(input); input_it.state.data = input_ptr.ptr + num_items - 1; pointer_t output_it(num_items); value_t init{42}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_it, output_it, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items); std::exclusive_scan(input.rbegin(), input.rend(), expected.begin(), init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_it)); } } struct Scan_ReverseOutputIterators_Fixture_Tag; C2H_TEST("Scan works with reverse output iterators", "[scan]") { const int num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); iterator_t> output_it = make_reverse_iterator(iterator_kind::OUTPUT, "int", "out"); const std::vector input = generate(num_items); pointer_t input_it(input); pointer_t inner_output_it(num_items); output_it.state.data = inner_output_it.ptr + num_items - 1; value_t init{42}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_it, output_it, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items); std::exclusive_scan(input.begin(), input.end(), expected.rbegin(), init.value); if (num_items > 0) { REQUIRE(expected == std::vector(inner_output_it)); } } struct Scan_InputOutputIterators_Fixture_Tag; C2H_TEST("Scan works with input and output iterators", "[scan]") { const int num_items = GENERATE(1, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); iterator_t> input_it = make_constant_iterator("int"); input_it.state.value = 1; iterator_t> output_it = make_random_access_iterator(iterator_kind::OUTPUT, "int", "out", " * 2"); pointer_t inner_output_it(num_items); output_it.state.data = inner_output_it.ptr; value_t init{42}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); scan(input_it, output_it, num_items, op, init, false, build_cache, test_key); std::vector expected(num_items, 1); std::exclusive_scan(expected.begin(), expected.end(), expected.begin(), init.value); std::transform(expected.begin(), expected.end(), expected.begin(), [](int x) { return x * 2; }); if (num_items > 0) { REQUIRE(expected == std::vector(inner_output_it)); } } C2H_TEST("Scan works with C++ source operations", "[scan]") { using T = int32_t; const std::size_t num_items = GENERATE(42, 1337, 42000); // Create operation from C++ source instead of LTO-IR std::string cpp_source = R"( extern "C" __device__ void op(void* a, void* b, void* out) { int* ia = (int*)a; int* ib = (int*)b; int* iout = (int*)out; *iout = *ia + *ib; } )"; operation_t op = make_cpp_operation("op", cpp_source); const std::vector input = generate(num_items); pointer_t input_ptr(input); pointer_t output_ptr(num_items); value_t init{T{42}}; // Test key including flag that this uses C++ source std::optional test_key = std::format("cpp_source_test_{}_{}", num_items, typeid(T).name()); auto& cache = get_cache(); std::optional cache_opt = cache; scan(input_ptr, output_ptr, num_items, op, init, false, cache_opt, test_key); const std::vector output = output_ptr; std::vector expected(num_items); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); REQUIRE(output == expected); } struct Scan_FloatingPointTypes_Fixture_Tag; using floating_point_types = c2h::type_list< #if _CCCL_HAS_NVFP16() __half, #endif float, double>; C2H_TEST("Scan works with floating point types", "[scan]", floating_point_types) { using T = c2h::get<0, TestType>; // Use small input sizes and values to avoid floating point precision issues. const std::size_t num_items = GENERATE(10, 42, 1025); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); const std::vector input(num_items, T{1}); pointer_t input_ptr(input); pointer_t output_ptr(num_items); value_t init{T{42}}; auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_VALUE_INIT); // FIXME: figure out why scan spills to lmem for double scan, true>(input_ptr, output_ptr, num_items, op, init, false, build_cache, test_key); const std::vector output = output_ptr; std::vector expected(num_items); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init.value); REQUIRE_APPROX_EQ(output, expected); } C2H_TEST("Scan works with C++ source operations using custom headers", "[scan]") { using T = int32_t; const std::size_t num_items = GENERATE(42, 1337, 42000); // Create operation from C++ source that uses the identity function from header std::string cpp_source = R"( #include "test_identity.h" extern "C" __device__ void op(void* a, void* b, void* out) { int* ia = (int*)a; int* ib = (int*)b; int* iout = (int*)out; int val_a = test_identity(*ia); int val_b = test_identity(*ib); *iout = val_a + val_b; } )"; operation_t op = make_cpp_operation("op", cpp_source); const std::vector input = generate(num_items); pointer_t input_ptr(input); pointer_t output_ptr(num_items); value_t init{T{42}}; // Test _ex version with custom build configuration const char* extra_flags[] = {"-DTEST_IDENTITY_ENABLED"}; const char* extra_dirs[] = {TEST_INCLUDE_PATH}; cccl_build_config config = make_build_config(extra_flags, 1, extra_dirs, 1); // Build with _ex version cccl_device_scan_build_result_t build{}; const auto& build_info = BuildInformation<>::init(); REQUIRE( CUDA_SUCCESS == cccl_device_scan_build_ex( &build, input_ptr, output_ptr, op, get_type_info(), true, cccl_init_kind_t::CCCL_VALUE_INIT, build_info.get_cc_major(), build_info.get_cc_minor(), build_info.get_cub_path(), build_info.get_thrust_path(), build_info.get_libcudacxx_path(), build_info.get_ctk_path(), &config)); // Execute the scan void* d_temp_storage = nullptr; size_t temp_storage_bytes = 0; REQUIRE(CUDA_SUCCESS == cccl_device_inclusive_scan( build, d_temp_storage, &temp_storage_bytes, input_ptr, output_ptr, num_items, op, init, CU_STREAM_LEGACY)); pointer_t temp_storage(temp_storage_bytes); d_temp_storage = static_cast(temp_storage.ptr); REQUIRE(CUDA_SUCCESS == cccl_device_inclusive_scan( build, d_temp_storage, &temp_storage_bytes, input_ptr, output_ptr, num_items, op, init, CU_STREAM_LEGACY)); // Verify results std::vector expected(num_items, 0); std::inclusive_scan(input.begin(), input.end(), expected.begin(), std::plus<>{}, init.value); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } // Cleanup REQUIRE(CUDA_SUCCESS == cccl_device_scan_cleanup(&build)); } struct Scan_FutureInitValue_Fixture_Tag; C2H_TEST("Scan works with future init value", "[scan]") { using T = int32_t; const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); const std::vector input = generate(num_items); const std::vector output(num_items, 0); pointer_t input_ptr(input); pointer_t output_ptr(output); T init{42}; pointer_t init_ptr(std::vector{init}); auto& build_cache = get_cache(); const auto& test_key = make_scan_key(false, cccl_init_kind_t::CCCL_FUTURE_VALUE_INIT); scan(input_ptr, output_ptr, num_items, op, init_ptr, false, build_cache, test_key); std::vector expected(num_items, 0); std::exclusive_scan(input.begin(), input.end(), expected.begin(), init); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } struct Scan_NoInitValue_Fixture_Tag; C2H_TEST("Scan works with no init value", "[scan]") { using T = uint32_t; const std::size_t num_items = GENERATE(0, 42, take(4, random(1 << 12, 1 << 16))); operation_t op = make_operation("op", get_reduce_op(get_type_info().type)); const std::vector input = generate(num_items); const std::vector output(num_items, 0); pointer_t input_ptr(input); pointer_t output_ptr(output); auto& build_cache = get_cache(); const auto& test_key = make_scan_key(true, cccl_init_kind_t::CCCL_NO_INIT); scan(input_ptr, output_ptr, num_items, op, true, build_cache, test_key); std::vector expected(num_items, 0); std::inclusive_scan(input.begin(), input.end(), expected.begin()); if (num_items > 0) { REQUIRE(expected == std::vector(output_ptr)); } } #ifndef CCCL_C_PARALLEL_V2 C2H_TEST("Scan build result has serialization metadata populated", "[scan][serialization]") { using T = int32_t; constexpr int device_id = 0; const auto& build_info = BuildInformation::init(); cccl_op_t op = make_well_known_binary_operation(); pointer_t in(1); pointer_t out(1); value_t init{T{0}}; BuildResultT build{}; REQUIRE( CUDA_SUCCESS == cccl_device_scan_build( &build, in, out, op, static_cast(init).type, /*force_inclusive=*/false, CCCL_VALUE_INIT, build_info.get_cc_major(), build_info.get_cc_minor(), build_info.get_cub_path(), build_info.get_thrust_path(), build_info.get_libcudacxx_path(), build_info.get_ctk_path())); CHECK(build.cc == build_info.get_cc_major() * 10 + build_info.get_cc_minor()); CHECK((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN)); CHECK(build.payload_size > 0); CHECK(build.runtime_policy != nullptr); CHECK(build.runtime_policy_size > 0); REQUIRE(build.init_kernel_lowered_name != nullptr); CHECK(build.init_kernel_lowered_name[0] != '\0'); REQUIRE(build.scan_kernel_lowered_name != nullptr); CHECK(build.scan_kernel_lowered_name[0] != '\0'); REQUIRE(CUDA_SUCCESS == cccl_device_scan_cleanup(&build)); } C2H_TEST("Scan compile/load round-trip", "[scan][serialization]") { using T = int32_t; constexpr int device_id = 0; const auto& build_info = BuildInformation::init(); cccl_op_t op = make_well_known_binary_operation(); pointer_t dummy_in(1); pointer_t dummy_out(1); value_t init{T{0}}; BuildResultT build{}; REQUIRE( CUDA_SUCCESS == cccl_device_scan_compile( &build, dummy_in, dummy_out, op, static_cast(init).type, /*force_inclusive=*/false, CCCL_VALUE_INIT, build_info.get_cc_major(), build_info.get_cc_minor(), build_info.get_cub_path(), build_info.get_thrust_path(), build_info.get_libcudacxx_path(), build_info.get_ctk_path(), nullptr)); REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN)); REQUIRE(build.payload_size > 0); REQUIRE(build.init_kernel_lowered_name != nullptr); REQUIRE(build.scan_kernel_lowered_name != nullptr); CHECK(build.library == nullptr); CHECK(build.init_kernel == nullptr); REQUIRE(CUDA_SUCCESS == cccl_device_scan_load(&build)); REQUIRE(build.library != nullptr); CHECK(build.init_kernel != nullptr); CHECK(build.scan_kernel != nullptr); constexpr std::size_t n = 16; const std::vector input = generate(n); pointer_t input_ptr(input); pointer_t output_ptr(n); CUstream null_stream = nullptr; size_t temp_storage_bytes = 0; REQUIRE(CUDA_SUCCESS == cccl_device_exclusive_scan( build, nullptr, &temp_storage_bytes, input_ptr, output_ptr, n, op, init, null_stream)); pointer_t temp_storage(temp_storage_bytes); REQUIRE(CUDA_SUCCESS == cccl_device_exclusive_scan( build, temp_storage.ptr, &temp_storage_bytes, input_ptr, output_ptr, n, op, init, null_stream)); std::vector expected(n); std::exclusive_scan(input.begin(), input.end(), expected.begin(), T{0}); REQUIRE(expected == std::vector(output_ptr)); REQUIRE(CUDA_SUCCESS == cccl_device_scan_cleanup(&build)); } C2H_TEST("Scan link_ltoir round-trip", "[scan][serialization]") { using T = int32_t; constexpr int device_id = 0; const auto& build_info = BuildInformation::init(); // Kernel-only compile: op has a name but no LTOIR (code_size == 0). cccl_op_t op_ko{}; op_ko.type = CCCL_STATELESS; op_ko.name = "op"; op_ko.code = nullptr; op_ko.code_size = 0; op_ko.code_type = CCCL_OP_LTOIR; pointer_t dummy_in(1); pointer_t dummy_out(1); value_t init{T{0}}; cccl_type_info accum_ti = get_type_info(); BuildResultT build{}; REQUIRE( CUDA_SUCCESS == cccl_device_scan_compile( &build, dummy_in, dummy_out, op_ko, accum_ti, /*force_inclusive=*/false, CCCL_VALUE_INIT, build_info.get_cc_major(), build_info.get_cc_minor(), build_info.get_cub_path(), build_info.get_thrust_path(), build_info.get_libcudacxx_path(), build_info.get_ctk_path(), nullptr)); // After kernel-only compile: kernel_ltoir is populated, cubin is not. REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_LTOIR)); REQUIRE(build.payload_size > 0); CHECK((build.payload_kind != CCCL_PAYLOAD_CUBIN)); CHECK(build.library == nullptr); // Compile the operator LTOIR separately. operation_t op_full = make_operation("op", get_reduce_op(get_type_info().type)); const void* op_blob = op_full.code.data(); size_t op_size = op_full.code.size(); REQUIRE(CUDA_SUCCESS == cccl_device_scan_link_ltoir(&build, &op_blob, &op_size, 1)); REQUIRE((build.payload != nullptr && build.payload_kind == CCCL_PAYLOAD_CUBIN)); REQUIRE(build.library == nullptr); REQUIRE(CUDA_SUCCESS == cccl_device_scan_load(&build)); REQUIRE(build.library != nullptr); CHECK((build.payload_kind != CCCL_PAYLOAD_LTOIR)); constexpr std::size_t n = 16; const std::vector input = generate(n); pointer_t input_ptr(input); pointer_t output_ptr(n); CUstream null_stream = nullptr; size_t temp_storage_bytes = 0; cccl_op_t op_run = op_full; cccl_value_t init_v = init; REQUIRE(CUDA_SUCCESS == cccl_device_exclusive_scan( build, nullptr, &temp_storage_bytes, input_ptr, output_ptr, n, op_run, init_v, null_stream)); pointer_t temp_storage(temp_storage_bytes); REQUIRE(CUDA_SUCCESS == cccl_device_exclusive_scan( build, temp_storage.ptr, &temp_storage_bytes, input_ptr, output_ptr, n, op_run, init_v, null_stream)); std::vector expected(n); std::exclusive_scan(input.begin(), input.end(), expected.begin(), T{0}); REQUIRE(expected == std::vector(output_ptr)); REQUIRE(CUDA_SUCCESS == cccl_device_scan_cleanup(&build)); } #endif // CCCL_C_PARALLEL_V2